Compare commits

...

358 Commits
4.6.0 ... 4.8.0

Author SHA1 Message Date
5d12dfa368 docs(ci): Update protocol.md - c428201 [skip ci] 2020-05-26 21:49:10 +00:00
c4282011fa Merge pull request #516 from Palakis/feature/SetSceneTransitionOverride
Requests: Add scene transition override requests
2020-05-26 23:48:33 +02:00
9ddc22fa94 request(SetSceneTransitionOverride): override duration only if it is explicity set so or not already defined 2020-05-26 23:36:18 +02:00
517788e86a request(scenes): refactor Set/Get/RemoveSceneTransitionOverride + use current transition duration 2020-05-26 23:26:37 +02:00
ed29ec528d docs(ci): Update protocol.md - 481b26d [skip ci] 2020-05-26 11:58:51 +00:00
481b26d09d Merge pull request #508 from Palakis/feature/setsourcename
Requests: Add SetSourceName (renaming sources)
2020-05-26 13:58:10 +02:00
da66ad6d41 Merge branch '4.x-current' into feature/setsourcename 2020-05-26 01:41:11 -07:00
1f75b305c3 docs(ci): Update protocol.md - 0802d03 [skip ci] 2020-05-26 07:42:47 +00:00
0802d03d74 Merge pull request #509 from Palakis/enhancement/sourcerenamed-isscene
Events: Add sourceType string to SourceRenamed
2020-05-26 09:42:03 +02:00
3970674003 Merge pull request #510 from Palakis/fix/fixwarnings
General: Fix a few warnings
2020-05-22 09:11:27 +02:00
19b33a8558 Requests: Add scene transition override requests
Adds these requests:
- `SetSceneTransitionOverride`
- `RemoveSceneTransitionOverride`
- `GetSceneTransitionOverride`
2020-05-18 14:44:25 -07:00
7bb91da1d0 Events: Add sourceType string to SourceRenamed
Since sources in OBS are also scenes, provide the user a string
so that they can differentiate.
2020-05-15 16:54:30 -07:00
e3e62c3903 General: Fix a few warnings
Fix a few build warnings on windows because im ocd
2020-05-15 16:52:08 -07:00
631ed022ed Requests: Add SetSourceName (renaming sources)
Adds the `SetSourceName` request which can be used to rename sources.

Also works on scenes since scenes in OBS are also sources.
2020-05-15 16:13:24 -07:00
590943ed95 docs(ci): Update protocol.md - e9c17c9 [skip ci] 2020-05-15 02:20:46 +00:00
e9c17c9a1d Merge pull request #496 from Palakis/request-setsourcemonitortype
Requests: Add audio monitoring requests
2020-05-15 04:20:07 +02:00
f8c8e42ae9 requests: rename Get/SetAudioMonitor to Get/SetAudioMonitorType 2020-05-15 04:10:40 +02:00
0b0560019a requests(Get/SetAudioMonitor): change values to camelCase for consistency 2020-05-15 04:08:40 +02:00
133d3fdda7 docs(ci): Update protocol.md - 53e98db [skip ci] 2020-05-15 00:48:51 +00:00
53e98dbe15 Merge pull request #484 from Palakis/add-screenshot-params
Requests: Add new parameters to TakeSourceScreenshot
2020-05-15 02:48:08 +02:00
0eaa9187ea requests(TakeSourceScreenshot): improved docs 2020-05-15 02:35:53 +02:00
344f5bda69 docs(ci): Update protocol.md - 33b080b [skip ci] 2020-05-14 20:48:16 +00:00
33b080b3b8 Merge pull request #495 from Palakis/volume-decibel-support
Requests: Add useDecibel bool to GetVolume and SetVolume
2020-05-14 22:47:33 +02:00
1c85894472 request(SetVolume): simplified params check 2020-05-14 22:31:10 +02:00
ba4e5959b1 requests(Get/SetVolume): code nitpicks 2020-05-14 22:24:58 +02:00
5534f9a248 docs(ci): Update protocol.md - eb20654 [skip ci] 2020-05-14 20:15:44 +00:00
eb206549ff Merge pull request #503 from Palakis/feature/get-tr-pos
Requests: Add GetTransitionPosition
2020-05-14 22:14:59 +02:00
0852f32b05 Merge branch '4.x-current' into request-setsourcemonitortype 2020-05-14 05:13:29 -07:00
00da210365 Merge pull request #504 from Palakis/bugfix/windows-ci-prepare-obs
ci(windows): simplified OBS Preparation script + shorter build times
2020-05-14 11:11:08 +02:00
bce6c86e60 Merge pull request #481 from Palakis/tt2468-ubuntu-fix
Fix install dir on Ubuntu by using new cmake flag
2020-05-14 11:04:36 +02:00
bed6a1b1e2 ci(windows): simplified OBS Preparation script + shorter build times 2020-05-14 11:02:51 +02:00
497443f012 Update CI scripts to use ubuntu fix 2020-05-14 01:47:55 -07:00
75a06afab0 Merge pull request #493 from Palakis/studio-mode-errors
Add error system to EnableStudioMode and DisableStudioMode
2020-05-14 10:34:05 +02:00
56371a7d80 Add GetTransitionPosition
Gets the current position of the active transition. Works on manual and also auto transition modes
2020-05-14 01:11:47 -07:00
2810787156 docs(ci): Update protocol.md - 6b53cb5 [skip ci] 2020-05-14 08:04:35 +00:00
6b53cb59a5 Merge pull request #501 from Palakis/tt2468-bump-docs
[Docs] Bump version to 4.8
2020-05-14 10:03:51 +02:00
71392613b2 [Docs] Bump version to 4.8 2020-05-12 17:46:49 -07:00
ff21f5b357 docs(ci): Update protocol.md - 98db248 [skip ci] 2020-05-12 09:37:41 +00:00
98db248776 Merge pull request #491 from Palakis/depricate-browserrequests
Deprecate SetBrowserSourceProperties and GetBrowserSourceProperties
2020-05-12 11:37:00 +02:00
ed4872b94c Merge pull request #483 from tt2468/add-http-request-script
Add obs-websocket-http
2020-05-12 11:33:04 +02:00
8c5c6958cf Merge pull request #476 from tt2468/patch-2
[Bug Fix] Fix FreeType2 source type recognition
2020-05-12 11:30:20 +02:00
f9c81f99f2 Install into both dirs on Ubuntu instead of only one 2020-05-12 02:24:27 -07:00
88c72cd80a Add audio monitoring request items. 2020-05-10 20:07:57 -07:00
728ea16701 Introduce useDecibel bool to GetVolume and SetVolume to give a better, built-in option for people to use decibels of attenuation instead of amplitude. 2020-05-10 18:39:24 -07:00
a3bc9f768a Add error system to EnableStudioMode and DisableStudioMode
As talked about in #144 this adds a check to `EnableStudioMode` and `DisableStudioMode` in order to be consistent with `StartRecording`/`StopRecording`
2020-05-08 15:30:40 -07:00
8d88bc19ed Deprecate SetBrowserSourceProperties and GetBrowserSourceProperties
Both of these requests are doing essentially the same thing as `GetSourceSettings` and `SetSourceSettings`, so there is no reason to have the extra bloat.
2020-05-08 13:15:24 -07:00
333ffa0e89 compressionQuality should be for embed and file saving 2020-04-30 20:17:11 -07:00
4ded810ba9 Add optional fileFormat and fileCompressionRatio parameters to TakeSourceScreenshot 2020-04-30 20:09:13 -07:00
b2aa54f3f8 Add obs-websocket-http
obs-websocket-http is a simple python script which allows users to run obs-websocket requests using an http-based api system. It is very simple and is a useful converter.
2020-04-30 16:25:31 -07:00
97836cc5eb Remove extra source type strings 2020-04-30 10:58:10 -07:00
b1df0dca97 Merge pull request #474 from tt2468/patch-1
Suppress websocketpp logging entries
2020-04-30 19:40:22 +02:00
4a6816575a Merge pull request #482 from Palakis/tt2468-start-error
Add websocketpp error message to error dialog box
2020-04-30 19:39:50 +02:00
31e2937b8d Merge pull request #477 from tt2468/patch-3
[Bug Fix] Don't build with captions on MacOS
2020-04-30 19:38:56 +02:00
0f434004a8 Add websocketpp error message to error dialog box 2020-04-30 10:08:56 -07:00
ba75c45cee Update build docs to add Ubuntu fix flag 2020-04-30 09:58:10 -07:00
47492c3fa2 Implement fix for Ubuntu users
#478 Broke building on linux. This implements a new variable to apply the fix.
2020-04-30 09:46:22 -07:00
819917c4bf Merge pull request #478 from Palakis/bugfix/ubuntu-packaging-fixes
Ubuntu packaging fixes
2020-04-28 11:40:20 +02:00
1ce0fd643c Revert "ci(linux): set package summary"
This reverts commit a7d02a79a9.
2020-04-28 11:18:22 +02:00
a7d02a79a9 ci(linux): set package summary 2020-04-28 10:29:48 +02:00
645cbf9888 ci(linux): fix debian package requires 2020-04-28 10:18:54 +02:00
b2d39ab2d7 cmake(linux): include GNUInstallDirs 2020-04-28 09:52:11 +02:00
5843521cf1 cmake(linux): use the standard LIBDIR and DATAROOTDIR install locations 2020-04-28 09:46:45 +02:00
4fbc45b40b ci(linux): make the deb package require OBS 25.0.7 2020-04-28 09:34:41 +02:00
2719da3685 [Bug Fix] Don't build with captions on MacOS
According to [this](https://github.com/obsproject/obs-studio/blob/master/UI/frontend-plugins/frontend-tools/CMakeLists.txt#L73), captions support is not active on MacOS, so we should not build it here.
2020-04-27 10:55:16 -07:00
f61bc809dd [Bug Fix] Fix FreeType2 source type recognition
OBS appears to be using a new format for the FreeType2 source, so we add the new kinds.

One thing I'm unsure on is if anything still uses the old naming stuff `text_ft2`/`text_ft2_v2`, since if not then we should not check
2020-04-27 08:30:39 -07:00
19f9593ac1 Supress websocketpp logging entries 2020-04-21 19:55:43 -07:00
e277cae799 Merge pull request #469 from tt2468/patch-1
Add simpleobsws to list of language APIs
2020-04-21 15:40:51 +02:00
05baf6b8ac Add simpleobsws to list of language APIs
I've created a simple async Python library for people who just want the simple stuff.
2020-04-21 05:30:21 -07:00
758441d65d readme: fix CI badges 2020-04-17 15:00:51 +02:00
631a008fa0 readme: fixes 2020-04-15 10:36:12 +02:00
54e79936e5 ci(macos): add automated codesigning + notarization (#464) 2020-04-10 21:41:45 +02:00
a833822eae docs(ci): Update protocol.md - cfa8d8d [skip ci] 2020-04-10 16:27:48 +00:00
cfa8d8d180 Merge pull request #463 from tt2468/patch-1
[Docs] Fix link to GetCurrentScene
2020-04-10 18:27:18 +02:00
b692756260 The actual fix 2020-04-10 08:14:25 -07:00
dbc62ad63a [Docs] Fix link to GetCurrentScene
For some reason the link to the `GetCurrentScene` section of the docs was put into a codeblock
2020-04-10 08:08:02 -07:00
2ac97dea35 installers: bump version 2020-04-07 17:35:49 +02:00
9451814299 ci(windows): fix installer suffix 2020-04-07 17:34:28 +02:00
5e01283018 Merge pull request #461 from Palakis/bugfix/disable-msvc-fh4
cmake(msvc): disable FH4
2020-04-06 20:00:27 +02:00
f7512c3cc0 cmake(msvc): disable FH4 2020-04-06 19:40:04 +02:00
f4997375e9 cmake: add version + fix /MP msvc flag 2020-04-06 19:09:01 +02:00
fbad54795e Merge pull request #453 from Palakis/bugfix/macos-qt-path
ci(macos): fix install_name_tool packaging step
2020-04-03 10:13:52 +02:00
2ca85d05f0 ci(macos): fix install_name_tool packaging step 2020-04-01 08:51:48 +02:00
67e89e79be docs(ci): Update protocol.md - e467666 [skip ci] 2020-03-29 22:20:01 +00:00
e4676668bb Merge pull request #449 from Palakis/bugfix/get-set-sceneitemproperties-item-id
Target Scene Items per ID, in addition to scene name and source name
2020-03-30 00:19:27 +02:00
3604995c2f requests(DeleteSceneItem + DuplicateSceneItem): backport dual "item" behaviour to existing functions 2020-03-30 00:02:36 +02:00
8b731f3ba4 fixes 2020-03-29 23:55:45 +02:00
83f702fbab requests(GetSceneItemProperties): add itemId response field 2020-03-29 23:28:07 +02:00
fbebf1c7d3 requests(scene item): fix docs 2020-03-29 23:04:20 +02:00
be14947668 oops 2020-03-29 22:12:54 +02:00
0fcf770043 requests(scene items): implement object mode on "item" request field 2020-03-29 22:07:55 +02:00
51ec3ede1f utils: add GetSceneItemFromRequestField 2020-03-29 21:42:10 +02:00
4e2302936f refactor bits 2020-03-29 21:41:38 +02:00
a6c6a42669 requests(scene items): doc fixes again 2020-03-29 19:39:57 +02:00
c486bc64c4 requests(scene items): refactor docs 2020-03-29 19:39:57 +02:00
3709ea1a95 docs(clarity): move scene items events into a proper "Scene Items" category
Namely:
- SourceOrderChanged
- SceneItemAdded
- SceneItemRemoved
- SceneItemVisibilityChanged
- SceneItemLockChanged
- SceneItemTransformChanged
- SceneItemSelected
- SceneItemDeselected
2020-03-29 19:39:57 +02:00
be7fa79327 docs(ci): Update protocol.md - c1660f8 [skip ci] 2020-03-29 17:39:56 +00:00
c1660f8ca2 chore: bump unreleased requests/events to 4.8.0 2020-03-29 19:39:19 +02:00
5e41eaf3dd Merge pull request #448 from Palakis/bugfix/text-gdiplus-v2
Allow Get/Set text gdiplus/ft2 properties functions to operate on v2 sources
2020-03-29 16:23:13 +02:00
898e761988 requests(freetype2 source): allow functions to operate on v2 sources 2020-03-29 16:13:28 +02:00
846d52ebe5 requests(gdiplus source): allow functions to operate on v2 sources 2020-03-29 16:09:16 +02:00
77ac6a7a26 Merge pull request #420 from Palakis/bugfix/crash-get-streaming-time
Fix crash when sending an event on exit
2020-03-29 15:57:07 +02:00
f23ba0c513 Merge branch '4.x-current' into bugfix/crash-get-streaming-time 2020-03-28 19:17:53 +01:00
6a733bbb13 Migrate Windows & Linux builds to Azure Pipelines (#422) 2020-03-28 19:13:55 +01:00
540e1f562f Merge pull request #434 from Palakis/bugfix/enablestudiomode-crash
Fix crash when calling EnableStudioMode/DisableStudioMode/ToggleStudioMode
2020-03-18 01:07:21 +01:00
bfcd16ea28 requests(studio mode): fix crash when calling frontend_set_preview_program_mode 2020-03-18 01:01:33 +01:00
3a8703de87 Merge pull request #438 from Palakis/feature/remove-dynamic-load-functions
Remove dynloaded functions
2020-03-17 23:57:45 +01:00
247ca71bf9 utils: remove dynloaded function for projector open 2020-03-17 23:46:24 +01:00
88d39ab47a utils: remove dynamic loaded functions for recording pause 2020-03-17 23:45:57 +01:00
a5af45fb31 ci(xenial): update frontend api to 25.0.0 2020-03-17 23:41:00 +01:00
dc04cb54ab chore: bump to 4.8.0 2020-03-17 23:37:49 +01:00
c9fa82f073 Merge pull request #437 from Palakis/bugfix/takesourcescreenshot-jpg
Fix JPEG support in TakeSourceScreenshot on Windows
2020-03-17 22:14:35 +01:00
c418cbf4ef cmake(windows): fix qjpeg path 2020-03-17 20:08:46 +01:00
3450c6b9a3 cmake(windows): copy qjpeg on RelWithDebInfo as well 2020-03-17 19:52:49 +01:00
1ada33a4f0 package(deb): fix ambiguous syntax 2020-03-17 19:41:43 +01:00
a669852694 cmake(windows): oops, fix copy command 2020-03-17 19:37:39 +01:00
a5b6ea6c4c cmake(windows): copy qt image format plugins 2020-03-17 19:34:53 +01:00
fe217ef5df package(deb): fix email address 2020-03-17 18:43:01 +01:00
e0a25dbf48 package(deb): add Qt image formats plugins dependency 2020-03-17 18:16:26 +01:00
86191aff7f docs(travis): Update protocol.md - 6bed39e [skip ci] 2020-03-15 22:22:42 +00:00
6bed39e4fa Merge pull request #410 from Lange/feat/transition-end-events
feat(events): add "TransitionEnd" and "TransitionVideoEnd" events
2020-03-15 23:21:53 +01:00
2a7aa432a9 events(TransitionEnd): remove from-scene because it's not available in transition_stop 2020-03-15 23:20:56 +01:00
4e19e41460 Merge branch 'bugfix/openprojector-build-error' into 4.x-current 2020-03-12 17:51:57 +01:00
061f5d5099 requests(OpenProjector): get function pointer from lib dynamically 2020-03-12 17:44:56 +01:00
796bdebd90 requests(OpenProjector): wrap version check with parentheses 2020-03-12 17:27:28 +01:00
010102da25 ci(linux): download frontend API headers from stable 2020-03-12 17:06:09 +01:00
d8f2aeb004 requests(OpenProjector): refactor libobs version check 2020-03-12 17:03:00 +01:00
5173338949 linux: add proper dependencies list in Deb package 2020-03-12 15:40:28 +01:00
a8b00b026e docs(travis): Update protocol.md - d4db0bd [skip ci] 2020-03-12 13:55:33 +00:00
d4db0bdfe5 Merge pull request #431 from Palakis/feature/list-supported-image-formats
request(GetVersion): add supported image export formats list
2020-03-12 14:54:41 +01:00
b26cd901ca request(GetVersion): add supported image export formats list 2020-03-12 10:58:30 +01:00
1e2fc783e5 Merge pull request #429 from Palakis/bugfix/negative-sync-offset
requests(SetSyncOffset): allow negative offsets
2020-03-11 14:18:59 +01:00
8667333416 ci(macos): switch to macOS 10.14 2020-03-11 14:13:22 +01:00
5088d7dd86 requests(SetSyncOffset): allow negative offsets 2020-03-11 14:10:38 +01:00
e78f26e9c0 Merge pull request #428 from Rosuav/add-muted-flag
Add a 'muted' flag to GetSceneItems
2020-03-11 14:06:12 +01:00
be10227829 Utils,GetSceneItemProperties: Document both muted and alignment 2020-03-11 23:02:23 +11:00
ef47c172c5 Utils: Add a 'muted' flag to GetSceneItems 2020-03-11 22:52:33 +11:00
053faa1661 docs(travis): Update protocol.md - 6d63779 [skip ci] 2020-03-11 11:12:19 +00:00
6d63779c70 Merge pull request #338 from Rosuav/open-projector
Create a command for opening a projector.
2020-03-11 12:11:23 +01:00
62ad1a483c docs(OpenProjector): add info on geometry encoding 2020-03-11 12:09:08 +01:00
f4f760a231 Punt on explaining what the geometry string actually means 2020-03-11 22:05:37 +11:00
0eaef1ac3b docs(travis): Update protocol.md - 27245cc [skip ci] 2020-03-05 09:24:10 +00:00
27245cca1a Merge pull request #426 from Palakis/bugfix/setstreamsettings-docs-typo
docs: replace stream service option use-auth with use_auth
2020-03-05 10:23:11 +01:00
afcc11af57 docs: replace stream service option use-auth with use_auth 2020-03-05 10:08:59 +01:00
9fc41e4245 Apply suggestions from code review
Fixes all the easy ones. I'll look into the more substantive changes in a bit.

Co-Authored-By: Stéphane Lepin <Palakis@users.noreply.github.com>
2020-03-05 05:14:46 +11:00
a0e0910117 docs(travis): Update protocol.md - 8a7d7a4 [skip ci] 2020-03-04 13:11:56 +00:00
8a7d7a41a4 Merge pull request #321 from Rosuav/item-locked-event
Fire an event whenever an item is locked/unlocked
2020-03-04 14:10:56 +01:00
45d5f4a760 events: rename SceneItemLockedChanged to SceneItemLockChanged 2020-03-04 14:04:26 +01:00
101b795c0a Merge pull request #324 from Rosuav/show-gravity
Show alignment (gravity) of scene elements when querying/switching scenes
2020-03-04 13:49:58 +01:00
8311bd3fd9 Merge pull request #416 from tt2468/4.x-current
Fix SetStreamSettings when switching type
2020-03-04 11:37:01 +01:00
ec1926f7a6 events: fix @since value for SceneItemLockedChanged 2020-03-04 11:26:48 +01:00
e1c92956f8 docs(travis): Update protocol.md - 9233d4f [skip ci] 2020-03-04 09:57:04 +00:00
9233d4f6dc Merge pull request #411 from Lange/fix/fixed-duration-transitions
fix(events): don't advertise a false duration for fixed transitions
2020-03-04 10:56:14 +01:00
f2f5661625 cmake: fix redundant c++ standard flag 2020-02-25 15:32:47 +01:00
4338cf57e6 OBSRemoteProtocol: rely only on event data when encoding an event 2020-02-25 15:24:26 +01:00
a0dce77a2f RpcEvent: streamTime and recordingTime are optional 2020-02-25 15:23:24 +01:00
ca93882870 Remove unnecessary utf8 string conversion 2020-02-15 14:51:59 -08:00
22fbb4262c Return check type instead of type provided in request 2020-02-15 14:47:46 -08:00
2845fd07a7 Final fixes before testing 2020-02-15 14:22:35 -08:00
c02f40beb6 Update WSRequestHandler_Streaming.cpp 2020-02-15 14:18:25 -08:00
79a7b444a2 Update WSRequestHandler_Streaming.cpp 2020-02-15 11:00:24 -08:00
9c11bbc7a1 Update WSRequestHandler_Streaming.cpp 2020-02-15 10:58:10 -08:00
e1cb57563a fix(events): don't advertise a false duration for fixed transitions
Some transitions, such as Stingers, have a fixed duration. There is currently no way to retrieve this value from the API, and `obs-websocket` will currently return an incorrect value when one of these transitions begins.

For now, I think it is better to explicitly return -1, which is a better indicator that we have no idea what the real duration is, instead of a misleading number which users might think looks correct at first glance.
2020-02-15 11:16:49 -06:00
1057d765f7 feat(events): add "TransitionEnd" and "TransitionVideoEnd" events
Adds events for `TransitionEnd` and `TransitionVideoEnd`.

`TransitionVideoEnd` is specific to Stinger transitions. It is fired when their video has actually completed playback. This is important, because `TransitionEnd` is fired at the cut point of the Stinger, which is nice to have but isn't always what the user might want.

This builds on #409, and should be merged after it.
2020-02-15 11:15:46 -06:00
30db1a8a63 Update WSRequestHandler_Streaming.cpp 2020-02-15 08:50:09 -08:00
c7b49b28c2 docs(travis): Update protocol.md - 40ea7cf [skip ci] 2020-02-15 16:29:59 +00:00
40ea7cfe15 Merge pull request #409 from Lange/feat/transition-type
feat(events): add "type" field to TransitionBegin
2020-02-15 17:29:08 +01:00
b3b2ae267a Update WSRequestHandler_Streaming.cpp 2020-02-15 07:39:01 -08:00
88176f4d2b Merge pull request #413 from Palakis/bugfix/event-recording-time-fix
WSEvents: fix recording time field after refactoring
2020-02-15 14:45:49 +01:00
6917ac48a3 WSEvents: fix recording time field 2020-02-15 13:00:58 +01:00
c326d65ad3 feat(events): add "type" field to TransitionBegin
This field reports the type of the transition (`obs_stinger_transition`, `fade_to_color_transition`, etc), which can be useful for code which needs to react differently depending on what type of transition is being played.

Of note is that OBS calls this an `id`, but I think that's kind of a misleading name. `type` seems like a more semantic name for this information to me, but I'm open to changing it if we want to stay consistent with the nomenclature that OBS itself uses.
2020-02-06 18:04:08 -06:00
e1a2f0c0f4 Merge pull request #405 from Palakis/utils-to-namespace
Utils: turn it into a namespace
2020-01-29 18:20:55 +01:00
6953bc6105 Utils: turn it into a namespace 2020-01-29 17:24:45 +01:00
209555d36d Merge branch '4.x-current' into open-projector 2020-01-29 14:23:47 +01:00
e65958f33e Merge pull request #390 from Palakis/handler-protocol-refactor
Request handler refactor
2020-01-29 14:06:46 +01:00
02fea8938a OBSRemoteProtocol(processMessage): fix field order 2020-01-29 13:16:47 +01:00
f5277e4931 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-29 12:30:24 +01:00
d78ed32df5 Merge pull request #404 from Palakis/bugfix/memory-leaks
Fix memory leaks in GetCurrentProfile, GetCurrentSceneCollection and Heartbeat event
2020-01-29 12:28:48 +01:00
8447395482 requests(GetCurrentSceneCollection): fix memory leak 2020-01-29 12:20:23 +01:00
915a7b6e15 requests(GetCurrentProfile): fix memory leak 2020-01-29 12:18:11 +01:00
b70c39789e events(Heartbeat): fix memory leak 2020-01-29 12:17:15 +01:00
865cb4857b RpcEvent: refactor fields to additionalFields 2020-01-29 11:13:42 +01:00
774abd66fa RpcEvent + RpcRequest: move accessors into declaration 2020-01-28 18:59:35 +01:00
e52d86e6f9 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-28 18:49:49 +01:00
3c0b5b0b43 Utils: fix memory leak in StartReplayBuffer 2020-01-28 18:49:37 +01:00
46068573c5 events: decouple events encoding from event emit 2020-01-28 18:37:25 +01:00
06c29b7810 Merge branch '4.x-current' into handler-protocol-refactor 2020-01-28 17:05:15 +01:00
631452567d OBSRemoteProtocol: correctly handling null message IDs 2020-01-28 17:02:25 +01:00
6c34f00cfe OBSRemoteProtocol(errorResponse): add missing additionalFields 2020-01-28 16:52:11 +01:00
208c7f18b2 OBSRemoteProtocol: refactor class + pass handler when calling processMessage 2020-01-28 16:49:35 +01:00
5e393ed3c4 OBSRemoteProtocol(buildResponse): fix release of obs_data_t 2020-01-28 16:41:42 +01:00
8b16abd370 OBSRemoteProtocol: fix mixed indents 2020-01-28 16:38:57 +01:00
94c232d78e docs(travis): Update protocol.md - 4b41c52 [skip ci] 2020-01-28 15:32:31 +00:00
4b41c52522 Merge pull request #397 from justweb1/patch-1
Fix SetSourceFilterVisibility filterEnabled type
2020-01-28 16:31:38 +01:00
6bfe533a20 chore: fix quality warnings 2020-01-28 16:16:19 +01:00
f0b8aad4b1 docs(SetSourceFilterVisibility): fix filterEnabled type 2020-01-28 16:07:19 +01:00
796cf36a7f Merge pull request #400 from dmerrick/patch-1
Update README to include MacOS
2020-01-28 15:54:26 +01:00
7706d66754 Merge pull request #401 from dmerrick/patch-2
Update README to add Golang package
2020-01-28 15:54:02 +01:00
d992536914 Update README.md 2019-12-29 13:05:16 -05:00
286b76672c Update README.md 2019-12-29 12:50:05 -05:00
f0d1b5a1e4 Fix SetSourceFilterVisibility filterEnabled type
Corrected from String to Boolean
2019-12-19 02:14:45 -06:00
71ad8930d8 Merge pull request #393 from talk2MeGooseman/4.x-current
Switch from using caption_text1 to caption_text2
2019-11-29 12:08:19 +01:00
0f6cbb0c5c Switch from using caption_text1 to caption_text2
- caption_text1 has a built in delay of 2 seconds for all captions that causes every increasing delays for captions
2019-11-28 12:59:36 -08:00
78584b3194 Fix version number in message 2019-11-26 00:59:57 +11:00
c0eb4d652b Mention the OBS version requirement in the docos 2019-11-26 00:58:28 +11:00
edbb28038f Guard open_projector() with a version check.
I can't find a way to probe for an actual feature here.
2019-11-25 22:36:17 +11:00
3b7e9b4eba Create a command for opening a projector.
Depends on obs-studio#1910 for back end functionality.
2019-11-25 16:21:28 +11:00
6d0d07abfe Fire an event whenever an item is locked/unlocked 2019-11-25 16:21:17 +11:00
e9f90b4990 OBSRemoteProtocol: fix params 2019-11-15 23:39:31 +01:00
d25b65c124 RpcResponse: fixes 2019-11-15 22:59:36 +01:00
5864864123 WSServer: reimplement protocol 2019-11-15 21:07:58 +01:00
2f244ae37e WSRequestHandler(SceneItems): fix error message 2019-11-15 20:54:06 +01:00
f83317f7c8 WSRequestHandler(SceneItems): fix DuplicateSceneItem 2019-11-15 20:39:39 +01:00
973b4b9f70 RpcRequest: fix forward declarations 2019-11-15 20:33:26 +01:00
9389ceaf4f WSRequestHandler(SceneItems): fixes 2019-11-15 20:33:13 +01:00
928dc30810 WSRequestHandler(Sources): fixes 2019-11-15 20:30:44 +01:00
c9d6d10995 WSRequesthandler(Scenes): fixes 2019-11-15 20:29:22 +01:00
9323e1cf59 WSRequestHandler(Streaming): fixes 2019-11-15 20:28:42 +01:00
93a456ca82 WSRequestHandler(StudioMode): fixes 2019-11-15 20:27:27 +01:00
f6693d5587 WSRequestHandler(Transitions): fixes 2019-11-15 20:26:44 +01:00
fd09a0ce54 WSRequestHandler(SceneCollections): fixes 2019-11-15 20:26:00 +01:00
50d3a2600e WSRequestHandler(ReplayBuffer): fixes 2019-11-15 20:25:28 +01:00
7e1b05933f WSRequestHandler(Recording): fixes 2019-11-15 20:25:00 +01:00
03c4fdc607 WSRequestHandler(Profiles): fixes 2019-11-15 20:21:40 +01:00
0485cf51ff WSRequestHandler(Outputs): fixes 2019-11-15 20:17:11 +01:00
ba9201831b WSRequestHandler(General): fix error returns 2019-11-15 20:12:38 +01:00
f660036a25 RpcRequest: rename okResponse to success 2019-11-15 20:09:43 +01:00
f09cbfddbc WSRequestHandler(General): fixes 2019-11-15 20:08:09 +01:00
a12f1dcb13 RpcRequest: create ok and error response builders 2019-11-15 20:05:07 +01:00
d2bb1ddc29 RpcRequest: fix accessors 2019-11-15 19:58:57 +01:00
528d81106e WSRequestHandler(processRequest): fix bind call 2019-11-15 19:52:05 +01:00
f0bed24742 WSRequestHandler: fix map 2019-11-15 19:49:06 +01:00
5b54d8135e WSRequestHandler: fixes 2019-11-15 19:41:19 +01:00
31559051f6 WSRequestHandler: request handler as object methods 2019-11-15 19:38:15 +01:00
e6dbc9bfdf WSRequestHandler(General): fixes 2019-11-15 19:30:30 +01:00
d1ee9d83b5 RpcResponse: allow additional fields in responses 2019-11-15 19:30:22 +01:00
ffa6371e3d WSRequestHandler: fix main 2019-11-15 19:29:34 +01:00
ee6e241144 RpcRequest: add has* methods 2019-11-15 19:26:49 +01:00
bdf1023b16 handler: request and response types 2019-11-15 19:26:42 +01:00
9a8f248a75 docs(travis): Update protocol.md - 6845cda [skip ci] 2019-11-12 10:41:34 +00:00
6845cda146 Update WSRequestHandler_Sources.cpp 2019-11-12 11:40:36 +01:00
4488b2b7a5 Merge pull request #385 from Palakis/bugfix/macos-deps-lookup
ci(macos): fix lookup path of Qt dependencies for new OBS bundle format
2019-11-11 15:38:34 +01:00
7dd8cb5d8d ci(macos): use framework rpath in package script 2019-11-11 15:27:27 +01:00
ebf631a15f ci(macos): fix lib paths in package script 2019-11-11 14:54:17 +01:00
3770b75a78 docs(travis): Update protocol.md - 5d81b61 [skip ci] 2019-11-11 13:41:29 +00:00
5d81b61325 Merge pull request #388 from Palakis/feature/enable-disable-filters
Get/set filter visibility (+ get single filter info)
2019-11-11 14:40:32 +01:00
a8e5171b40 utils(GetSourceFilterInfo): fix settings memory leak 2019-11-11 14:27:40 +01:00
a3e0abb6ce events: add SourceFilterVisibilityChanged 2019-11-11 14:12:37 +01:00
c51f20eb99 requests(SetSourceFilterVisibility): fix parameter value 2019-11-11 13:43:33 +01:00
be897c4df2 requests(sources): fix typo 2019-11-11 13:42:16 +01:00
11b948fb69 Revert "ci(macos): use @rpath in package script"
This reverts commit 070660848b.
2019-11-11 13:36:04 +01:00
070660848b ci(macos): use @rpath in package script 2019-11-11 13:27:33 +01:00
61af0ec9c6 requests(sources): add SetSourceFilterVisibility method 2019-11-11 12:58:36 +01:00
897f115363 requests(sources): add GetSourceFilterInfo method 2019-11-11 12:53:22 +01:00
b8fcf0355c requests(GetSourceFilters): add "enabled" status field 2019-11-11 12:44:13 +01:00
5bdff87e3f ci(macos): fix lookup path of Qt dependencies for new OBS bundle format 2019-11-04 13:07:15 +01:00
31991a3567 docs(travis): Update protocol.md - 25c369d [skip ci] 2019-10-17 14:19:25 +00:00
25c369d422 Merge pull request #384 from BarryCarlyon/TakeSourceScreenshotDocs
Take scene screenshot
2019-10-17 16:18:16 +02:00
8d396b1518 Adjust wording based on feedback 2019-10-17 11:53:32 +01:00
ba327b746a Wording Tweak 2019-10-15 20:13:00 +01:00
7d11f912c9 Revise Documentation to note that a Scene Name can be provided 2019-10-15 20:11:40 +01:00
919cdfb5a9 WSServer: remove remains of QWebSocket 2019-09-16 13:01:44 +02:00
da9dd6f775 docs(travis): Update protocol.md - 8b841f0 [skip ci] 2019-09-03 19:29:52 +00:00
8b841f026e Merge pull request #371 from Palakis/pause-recording
Recording Pause support
2019-09-03 21:28:55 +02:00
5342b39640 WSEvents: use frame count for streaming timecode 2019-09-03 21:04:45 +02:00
fc08add504 WSEvents: use stream time getter 2019-09-03 16:05:38 +02:00
20379ac61e WSEvents: recording time based on frame count and frame duration 2019-09-03 16:03:52 +02:00
af40aa59ab events + requests: fix dynamic runtime support for recording pauses 2019-09-03 13:50:44 +02:00
f9afc5597a requests(pause recording): fix memory leak 2019-09-03 13:22:08 +02:00
40178e4661 WSEvents(nsToTimestamp): fix sprintf compiler warning 2019-09-03 09:56:20 +02:00
ad56abd6f5 requests(Recording): refactor bits 2019-09-03 03:34:20 +02:00
9073a09d84 requests(pause recording): runtime feature detection 2019-09-03 03:30:06 +02:00
2a4e6fce3f WSEvents: fix constructor warning 2019-09-03 03:03:06 +02:00
0a3a407217 events: refactor timecode generation 2019-09-03 02:59:02 +02:00
9ca9cf80df Merge branch '4.x-current' into pause-recording 2019-09-03 02:41:42 +02:00
6c881a5da7 Merge pull request #372 from Palakis/frontend-events-ifelse-refactor
events: refactor the massive if else chain in the frontend event callback
2019-09-03 02:40:07 +02:00
5322acd58c events: add RecordingPaused and RecordingResumed 2019-09-03 02:35:54 +02:00
ce489e53c6 requests(GetStreamingStatus): add property "recording-paused" 2019-09-03 02:34:24 +02:00
0c95e509dc events(Heartbeat + StreamStatus): add property "recording-paused" 2019-09-03 02:34:24 +02:00
46a006ed54 requests(pause recording): only compile recording pause function bodies when supported 2019-09-03 02:34:24 +02:00
0cc9378d05 requests: add PauseRecording and ResumeRecording 2019-09-03 02:34:24 +02:00
f3edb2ee68 events: refactor the massive if else chain in the frontend event callback 2019-09-03 02:34:11 +02:00
a539f23194 Update README.md 2019-09-02 17:53:18 +02:00
6b6dec79c5 Merge pull request #369 from Palakis/bugfix/connection-onclose-broadcast-crash
WSServer: handle error codes to avoid throwing exceptions when sending a message
2019-09-02 12:52:58 +02:00
d7f5c042bf WSServer: handle error codes when sending a message 2019-09-01 00:16:09 +02:00
2fe9812b26 gitignore: .idea folder 2019-09-01 00:04:31 +02:00
1c15c3c7a3 Merge pull request #368 from Palakis/bugfix/timecode-sprintf-warning
WSEvents(nsToTimestamp): fix sprintf format warning
2019-08-31 21:50:46 +02:00
1ee5857139 WSEvents(nsToTimestamp): use unsigned long long constants 2019-08-31 21:35:58 +02:00
2f9c2b0d65 WSEvents(nsToTimestamp): fix sprintf format warning 2019-08-31 18:34:49 +02:00
6e5161f43b Merge pull request #367 from Palakis/bugfix/start-server-after-loading-finished
Start server after OBS finishes loading
2019-08-31 17:39:08 +02:00
d492d74a64 module: start server after OBS finishes loading 2019-08-31 16:03:32 +02:00
26506731c5 Merge pull request #366 from Palakis/bugfix/incorrect-recording-free-space
events(GetStats): fix recording path detection
2019-08-31 04:50:40 +02:00
b0c03d4f47 events(GetStats): fix recording path detection 2019-08-31 04:31:40 +02:00
3bc8348c32 Merge pull request #365 from Palakis/bugfix/reordersceneitems-crash
ReorderSceneItems: fix crash + refactor Utils
2019-08-31 03:29:21 +02:00
a034fbba83 utils(GetSceneItemFromItem): fix memory leaks with obs_data_item_t uses 2019-08-31 03:10:49 +02:00
73b5261c49 requests(ReorderSceneItems): atomic reorder + bring back GetSceneItemFromId 2019-08-31 03:03:32 +02:00
cee0cbebc2 Utils: use obs_scene_t* directly for scene-related helpers 2019-08-31 03:03:32 +02:00
9723147429 Utils: refactor GetSceneItemFromItem and get rid of GetSceneItemFromId 2019-08-31 03:03:32 +02:00
dca385ae87 requests(ReorderSceneItems): refactor 2019-08-31 03:03:32 +02:00
71f792944c request handler: fix obs_data_item_t memory leak 2019-08-31 02:52:08 +02:00
6041c4acd2 Merge pull request #364 from Palakis/bugfix/transitionbegin-cut-duration
event(TransitionBegin): set duration to 0 if it's a Cut transition
2019-08-30 14:10:46 +02:00
0c7529705e docs(travis): Update protocol.md - 63f1ea1 [skip ci] 2019-08-30 11:54:21 +00:00
63f1ea1cec docs: bump docs version to 4.7.0 2019-08-30 13:53:11 +02:00
4bbaa75f41 Merge pull request #358 from Palakis/request-helpers
Extend WSRequestHandler::hasField() to support field type checking
2019-08-30 13:50:25 +02:00
5fe9314b74 WSEvents: remove unused transitionIsCut helper 2019-08-30 13:41:50 +02:00
bfd26493ab Utils(GetTransitionDuration): return 0 if transition is a cut transition 2019-08-30 13:41:08 +02:00
5d74d5d03e WSEvents(OnTransitionBegin): refactor event code 2019-08-30 13:36:45 +02:00
3142e097a5 Merge pull request #360 from ZyanKLee/patch-1
allow for "linuxbrowser_source" to be controlled
2019-08-26 21:02:00 +02:00
b25d8d8201 requests(sources): fix linux browser source id 2019-08-26 21:01:05 +02:00
7e68cd016a ci(windows): exclude RCs when fetching the latest version of OBS 2019-08-24 12:39:46 +02:00
9e4fc9eb73 allow for "linuxbrowser_source" to be controlled
based on the request at https://github.com/bazukas/obs-linuxbrowser/issues/46 I did a quick search for the string "browser_source" and these seem to be the only two occurrences. Enhancing the conditional with the identifier of the obs-linuxbrowser plugin, this should solve the problem.

As I'm currently not at home, I can't verify this does what it should, but I will test it later tonight.

@mjc666 if you are still interested please give it a test run to verify if these two plugins work together now.
2019-08-22 19:44:12 +02:00
fb0d13a171 WSRequestHandler: add int and double type check methods 2019-08-02 21:52:50 +02:00
f097b36c2e WSRequestHandler: add type checks for field test methods 2019-08-02 21:40:42 +02:00
57ce3cf80b Merge pull request #320 from Palakis/feature/remove-hacks
utils: remove transition hacks
2019-07-31 17:38:40 +02:00
c1da62391f docs(travis): Update protocol.md - d8882ce [skip ci] 2019-07-31 15:37:41 +00:00
d8882ce979 Merge pull request #357 from julijane/fix-001
Fix spec of HandleBroadcastCustomMessage
2019-07-31 17:36:39 +02:00
9c1871494e Fix spec of HandleBroadcastCustomMessage 2019-07-26 14:40:54 +02:00
ac26c9114a docs(travis): Update protocol.md - fb0a570 [skip ci] 2019-07-26 07:31:42 +00:00
fb0a57003c BroadcastCustomMessage: fix command and event spec 2019-07-26 09:30:30 +02:00
cda22233a6 docs(travis): Update protocol.md - add3075 [skip ci] 2019-07-26 03:52:35 +00:00
add307577d Merge pull request #354 from julijane/4.x-current
Make it possible to broadcast messages (data) to other websocket clients
2019-07-26 05:51:42 +02:00
4af46ac2b9 use obs_data_t* in Event function 2019-07-25 17:20:31 +02:00
8d39752bda Move event emitting code 2019-07-25 17:00:32 +02:00
e3ff9c013e change function name, language 2019-07-25 14:47:47 +02:00
635870ba4b more comment fixes 2019-07-24 05:36:47 +02:00
a827afb05b Comment fix 2019-07-24 01:56:53 +02:00
0195e13bdc Implement proposed changes to Broadcast 2019-07-23 21:47:55 +02:00
76d8b688fd Add Code for BroadcastWebSocketMessage 2019-07-22 01:05:21 +02:00
b4857e2c79 docs(travis): Update protocol.md - ac2ae90 [skip ci] 2019-07-16 16:23:36 +00:00
ac2ae90e8a Merge pull request #343 from Palakis/feature/outputs-api
Outputs API
2019-07-16 18:22:40 +02:00
0b5cb76b5d outputs: remove TODOs 2019-07-16 18:22:07 +02:00
6eed9606ae docs: remove Boost from BUILDING.md 2019-06-23 22:38:36 +02:00
29679ff94c docs(travis): Update protocol.md - 65a1f8b [skip ci] 2019-06-18 16:41:13 +00:00
65a1f8b746 GetSourceTypesList: fix docs
Fixes #348
2019-06-18 18:38:52 +02:00
48182d35bd docs(travis): Update protocol.md - b526ed4 [skip ci] 2019-06-16 14:20:37 +00:00
b526ed47be Merge branch '4.x-current' of github.com:Palakis/obs-websocket into 4.x-current 2019-06-16 16:19:32 +02:00
2225dec2a9 docs(SourceAudioMixersChanged): fix remaining discrepancy 2019-06-16 16:19:22 +02:00
011458d642 docs(travis): Update protocol.md - e32fff3 [skip ci] 2019-06-16 14:18:12 +00:00
e32fff3f61 docs(SourceAudioMixersChanged): fix discrepancy 2019-06-16 16:14:26 +02:00
0b42e3d0f3 outputs: add more info fields 2019-06-11 19:32:52 +02:00
2b746d1353 cmake: add output requests 2019-06-11 19:32:52 +02:00
6fed6f9f7d StartOutput: provide output error message when start fails 2019-06-11 19:32:52 +02:00
480945073a outputs: start or stop only when possible 2019-06-11 19:32:52 +02:00
ef6df94838 outputs: todos 2019-06-11 19:32:52 +02:00
fb05848426 requests: outputs API WIP 2019-06-11 19:32:52 +02:00
e5be6b96fb general: version bump to 4.7 2019-06-11 19:32:32 +02:00
2d706a245a Merge pull request #337 from DieserMerlin/patch-2
Update README.md (+ obs-websocket-java library)
2019-06-04 11:17:43 +02:00
1abc4f491b readme: minor fix 2019-06-04 10:46:19 +02:00
b21a45b43e Update README.md 2019-06-01 00:08:19 +02:00
585f8450e1 Merge pull request #330 from Palakis/transitionbegin-hotfix
Fix TransitionBegin event
2019-05-21 00:05:24 +02:00
d3401db5fe general: version bump to 4.6.1 2019-05-21 00:02:48 +02:00
45dc6447bf events: fix how and when TransitionBegin is hooked 2019-05-20 23:54:13 +02:00
409c270ba2 events: fix TransitionBegin event not triggering 2019-05-20 23:48:49 +02:00
47b485819f Update property -> return as this isn't a typedef 2019-05-20 01:47:25 +10:00
fd32f7f435 Explain how OBS interprets alignment numbers 2019-05-20 00:44:11 +10:00
c4b07b1a7b Merge remote-tracking branch 'origin/4.x-current' into show-gravity 2019-05-20 00:37:22 +10:00
2622eb4663 Add alignment to the documented definition of a Source 2019-05-14 04:09:18 +10:00
74b3dc2831 Show alignment (gravity) of scene elements when querying/switching scenes 2019-05-14 04:05:22 +10:00
84629f6e63 utils: remove transition hacks 2019-05-09 13:28:50 +02:00
59 changed files with 5614 additions and 1901 deletions

3
.gitignore vendored
View File

@ -4,6 +4,7 @@
/build32/
/build64/
/release/
/package/
/installer/Output/
.idea
.vscode

View File

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

View File

@ -3,7 +3,7 @@
## Prerequisites
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
[CMake](https://cmake.org/download/), [Boost](https://www.boost.org/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
[CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
computer.
## Windows
@ -24,7 +24,7 @@ sudo apt-get install libboost-all-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 ..
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
make -j4
sudo make install
```

6
CI/build-ubuntu.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
set -ex
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
make -j4

View File

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

6
CI/download-obs-deps.cmd Normal file
View File

@ -0,0 +1,6 @@
if not exist %DepsBasePath% (
curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
7z x %DepsBasePath%.zip -o%DepsBasePath%
) else (
echo "OBS dependencies are already there. Download skipped."
)

View File

@ -4,6 +4,9 @@ echo "-- Generating documentation."
echo "-- Node version: $(node -v)"
echo "-- NPM version: $(npm -v)"
git fetch origin
git checkout ${CHECKOUT_REF/refs\/heads\//}
cd docs
npm install
npm run build
@ -15,19 +18,14 @@ if git diff --quiet; then
exit 0
fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; 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.name "Azure 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
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO

View File

@ -34,7 +34,6 @@ git checkout $OBSLatestTag
mkdir build && cd build
echo "[obs-websocket] Building obs-studio.."
cmake .. \
-DBUILD_CAPTIONS=true \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \

View File

@ -1,129 +0,0 @@
@echo off
SETLOCAL EnableDelayedExpansion
REM Check if obs-studio build exists.
REM If the obs-studio directory does exist, check if the last OBS tag built
REM matches the latest OBS tag.
REM If the tags match, do not build obs-studio.
REM If the tags do not match, build obs-studio.
REM If the obs-studio directory doesn't exist, build obs-studio.
echo Checking for obs-studio build...
set OBSLatestTagPrePull=0
set OBSLatestTagPostPull=0
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
REM Set up the build flag as undefined.
set "BuildOBS="
REM Check the last tag successfully built by CI.
if exist C:\projects\obs-studio-last-tag-built.txt (
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else (
set OBSLastTagBuilt=0
)
REM If obs-studio directory exists, run git pull and get the latest tag number.
if exist C:\projects\obs-studio\ (
echo obs-studio directory exists
echo Updating tag info
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
git checkout master
git pull
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
)
REM Check the obs-studio tags for mismatches.
REM If a new tag was pulled, set the build flag.
if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% (
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true
)
REM If the latest git tag doesn't match the last built tag, set the build flag.
if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
echo Last built OBS tag: %OBSLastTagBuilt%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true
)
REM If obs-studio directory does not exist, clone the git repo, get the latest
REM tag number, and set the build flag.
if not exist C:\projects\obs-studio (
echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
set BuildOBS=true
)
REM If the needed obs-studio libs for this build_config do not exist,
REM set the build flag.
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
set BuildOBS=true
)
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
set BuildOBS=true
)
REM Some debug info
echo:
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Latest tag: %OBSLatestTag%
echo Last built OBS tag: %OBSLastTagBuilt%
if defined BuildOBS (
echo BuildOBS: true
) else (
echo BuildOBS: false
)
echo:
REM If the build flag is set, build obs-studio.
if defined BuildOBS (
echo Building obs-studio...
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
echo Removing previous build dirs...
if exist build rmdir /s /q C:\projects\obs-studio\build
if exist build32 rmdir /s /q C:\projects\obs-studio\build32
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
echo Making new build dirs...
mkdir build
mkdir build32
mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd ./build32
cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ../build64
cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
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 ..
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else (
echo Last OBS tag built is: %OBSLastTagBuilt%
echo No need to rebuild OBS.
)

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -ex
sudo add-apt-repository -y ppa:obsproject/obs-studio
sudo apt-get -qq update
sudo apt-get install -y \
libc-dev-bin \
libc6-dev git \
build-essential \
checkinstall \
cmake \
obs-studio \
qtbase5-dev
# Dirty hack
sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h
sudo ldconfig

View File

@ -1,19 +0,0 @@
#!/bin/sh
set -ex
add-apt-repository -y ppa:obsproject/obs-studio
apt-get -qq update
apt-get install -y \
libc-dev-bin \
libc6-dev git \
build-essential \
checkinstall \
cmake \
obs-studio \
qtbase5-dev
# Dirty hack
wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h
ldconfig

8
CI/install-qt-win.cmd Normal file
View File

@ -0,0 +1,8 @@
if not exist %QtBaseDir% (
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z
7z x Qt_5.10.1.7z -o%QtBaseDir%
) else (
echo "Qt is already installed. Download skipped."
)
dir %QtBaseDir%

View File

@ -1,6 +0,0 @@
@echo off
REM Set default values to use AppVeyor's built-in Qt.
set QTDIR32=C:\Qt\5.10.1\msvc2015
set QTDIR64=C:\Qt\5.10.1\msvc2015_64
set QTCompileVersion=5.10.1

View File

@ -514,11 +514,11 @@
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>IDENTIFIER</key>
<string>fr.palakis.obswebsocket</string>
<string>fr.palakis.obs-websocket</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>VERSION</key>
<string>4.6.0</string>
<string>4.8.0</string>
</dict>
<key>PROJECT_COMMENTS</key>
<dict>

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
set -e
@ -12,29 +12,79 @@ fi
echo "[obs-websocket] Preparing package build"
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
export GIT_HASH=$(git rev-parse --short HEAD)
export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
GIT_HASH=$(git rev-parse --short HEAD)
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
export FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
FILENAME="obs-websocket-$VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so"
install_name_tool \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
-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 \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
./build/obs-websocket.so
# Check if replacement worked
echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
else
echo "[obs-websocket] Skipped plugin codesigning"
fi
echo "[obs-websocket] Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing installer: $FILENAME"
productsign \
--sign "$INSTALLER_SIGNING_IDENTITY" \
./release/$FILENAME_UNSIGNED \
./release/$FILENAME
rm ./release/$FILENAME_UNSIGNED
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
zip -r ./release/$FILENAME.zip ./release/$FILENAME
UPLOAD_RESULT=$(xcrun altool \
--notarize-app \
--primary-bundle-id "fr.palakis.obs-websocket" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME" \
--file "./release/$FILENAME.zip")
rm ./release/$FILENAME.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
echo "Request UUID: $REQUEST_UUID"
echo "[obs-websocket] Wait for notarization result"
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME")
echo $CHECK_RESULT
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
xcrun stapler staple ./release/$FILENAME
break
fi
done
else
echo "[obs-websocket] Skipped installer codesigning and notarization"
fi

23
CI/package-ubuntu.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
set -e
export GIT_HASH=$(git rev-parse --short HEAD)
export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git"
if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then
export PKG_VERSION="$BRANCH_SHORT_NAME"
fi
cd ./build
PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \
--pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \
--pakdir="../package"
sudo chmod ao+r ../package/*

12
CI/package-windows.cmd Normal file
View File

@ -0,0 +1,12 @@
mkdir package
cd package
git rev-parse --short HEAD > package-version.txt
set /p PackageVersion=<package-version.txt
del package-version.txt
REM Package ZIP archive
7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*"
REM Build installer
iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer"

View File

@ -1,24 +0,0 @@
#!/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" \
--pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--pakdir="/package"
chmod ao+r /package/*

View File

@ -0,0 +1,37 @@
@echo off
SETLOCAL EnableDelayedExpansion
REM If obs-studio directory does not exist, clone the git repo
if not exist %OBSPath% (
echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio %OBSPath%
cd /D %OBSPath%\
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
)
REM Prepare OBS Studio builds
echo Running CMake...
cd /D %OBSPath%
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
if not exist build32 mkdir build32
if not exist build64 mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
dir "%OBSPath%\libobs"

7
CI/prepare-windows.cmd Normal file
View File

@ -0,0 +1,7 @@
mkdir build32
mkdir build64
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..

View File

@ -1,30 +1,22 @@
cmake_minimum_required(VERSION 3.2)
project(obs-websocket)
cmake_minimum_required(VERSION 3.5)
project(obs-websocket VERSION 4.8.0)
set(CMAKE_PREFIX_PATH "${QTDIR}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake)
endif()
add_definitions(-DASIO_STANDALONE)
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake)
endif()
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
set(obs-websocket_SOURCES
src/obs-websocket.cpp
@ -42,9 +34,14 @@ set(obs-websocket_SOURCES
src/WSRequestHandler_Streaming.cpp
src/WSRequestHandler_StudioMode.cpp
src/WSRequestHandler_Transitions.cpp
src/WSRequestHandler_Outputs.cpp
src/WSEvents.cpp
src/Config.cpp
src/Utils.cpp
src/rpc/RpcRequest.cpp
src/rpc/RpcResponse.cpp
src/rpc/RpcEvent.cpp
src/protocol/OBSRemoteProtocol.cpp
src/forms/settings-dialog.cpp)
set(obs-websocket_HEADERS
@ -55,6 +52,10 @@ set(obs-websocket_HEADERS
src/WSEvents.h
src/Config.h
src/Utils.h
src/rpc/RpcRequest.h
src/rpc/RpcResponse.h
src/rpc/RpcEvent.h
src/protocol/OBSRemoteProtocol.h
src/forms/settings-dialog.h)
# --- Platform-independent build settings ---
@ -83,6 +84,11 @@ if(WIN32)
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
endif()
if(MSVC)
# Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL)
add_definitions(/MP /d2FH4-)
endif()
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
@ -119,6 +125,16 @@ if(WIN32)
"$<TARGET_FILE:obs-websocket>"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# In Release mode, copy Qt image format plugins
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
# If config is RelWithDebInfo, package release files
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E make_directory
@ -164,20 +180,22 @@ endif()
# --- Linux-specific build settings and tasks ---
if(UNIX AND NOT APPLE)
include(GNUInstallDirs)
set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket obs-frontend-api)
file(GLOB locale_files data/locale/*.ini)
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE)
if(${USE_UBUNTU_FIX})
install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
# Dirty fix for Ubuntu
LIBRARY DESTINATION "/usr/lib/obs-plugins")
endif()
install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins")
install(FILES ${locale_files}
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale")
endif()
# --- End of section ---

View File

@ -1,22 +1,26 @@
obs-websocket
==============
Remote control of OBS Studio made easy.
WebSockets API for OBS Studio.
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
[![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)
[![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
## Downloads
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
## Using obs-websocket
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/
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)
- Change your stream overlay/graphics based on the current scene
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
### For developers
@ -29,6 +33,10 @@ Here's a list of available language APIs for obs-websocket :
- 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
- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468
- Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET
- Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf
- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468
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 `stephane /dot/ lepin /at/ gmail /dot/ com` !
@ -40,26 +48,21 @@ See the [build instructions](BUILDING.md).
### Branches
The two main development branches are:
- `4.x-current`: actively-maintained codebase for 4.x releases. Backwards-compatible (unless stated otherwise) with existing clients until 5.0.
- `5.x`: upcoming 5.0 version
**New features and fixes must be based off and contributed to `4.x-current`**, as obs-websocket 5.0 is not in active development yet.
Development happens on `4.x-current`
### Pull Requests
Pull Requests must never be based off your fork's main branch (in our case, `4.x-current` or `5.x`). Start your work in a new branch
Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`). Start your work in a new branch
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
If your Pull Request is not ready to merge yet, tag it with the `work in progress` label. You can also use the `help needed` label if you have questions, need a hand or want to ask for input.
**If your Pull Request is not ready to merge yet, create it as a Draft Pull Request** (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request").
### Code style & formatting
Source code is indented with tabs, with spaces allowed for alignment.
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
These are using to automatically generate the [protocol specification](docs/generated/protocol.md).
These are required to automatically generate the [protocol specification document](docs/generated/protocol.md).
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:

View File

@ -1,40 +0,0 @@
environment:
CURL_VERSION: 7.39.0
install:
- git submodule update --init --recursive
- cd C:\projects\
- if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
- 7z x dependencies2015.zip -odependencies2015
- set DepsPath32=%CD%\dependencies2015\win32
- set DepsPath64=%CD%\dependencies2015\win64
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
- set build_config=RelWithDebInfo
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
- cd C:\projects\obs-websocket\
- mkdir build32
- mkdir build64
- cd ./build32
- cmake -G "Visual Studio 14 2015" -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 14 2015 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\*
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
- iscc "C:\projects\obs-websocket\installer\installer.iss"
deploy_script:
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
test: off
cache:
- C:\projects\dependencies2015.zip
- C:\projects\obs-studio-last-tag-built.txt
- C:\projects\obs-studio\

View File

@ -1,23 +1,183 @@
pool:
vmImage: 'macOS-10.13'
variables:
isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}
steps:
- checkout: self
trigger:
branches:
include:
- master
tags:
include:
- '*'
jobs:
- job: 'GenerateDocs'
condition: |
or(
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
eq(variables['Build.SourceBranch'], 'refs/heads/master')
)
pool:
vmImage: 'ubuntu-18.04'
steps:
- checkout: self
submodules: false
- script: ./CI/generate-docs.sh
displayName: 'Generate docs'
env:
CHECKOUT_REF: $(Build.SourceBranch)
GH_TOKEN: $(GithubToken)
- job: 'Build_Windows'
pool:
vmImage: 'windows-2019'
variables:
build_config: RelWithDebInfo
DepsBasePath: 'D:\obsdependencies'
DepsPath32: '$(DepsBasePath)\win32'
DepsPath64: '$(DepsBasePath)\win64'
QtBaseDir: 'D:\QtDep'
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
OBSPath: 'D:\obs-studio'
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install Dependencies'
- script: ./CI/install-qt-win.cmd
displayName: 'Install Qt'
env:
QtBaseDir: $(QtBaseDir)
- script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS'
- task: Cache@2
displayName: Restore cached OBS Studio dependencies
inputs:
key: 'obsdeps | "$(Agent.OS)"'
restoreKeys: |
obsdeps | "$(Agent.OS)"
path: $(DepsBasePath)
- script: ./CI/build-macos.sh
- script: ./CI/download-obs-deps.cmd
displayName: 'Download OBS Studio dependencies'
- task: Cache@2
displayName: Restore cached OBS Studio builds
inputs:
key: 'obs | "$(Agent.OS)"'
restoreKeys: |
obs | "$(Agent.OS)"
path: $(OBSPath)
- script: ./CI/prepare-obs-windows.cmd
displayName: 'Checkout & CMake OBS Studio'
env:
build_config: $(build_config)
DepsPath32: $(DepsPath32)
DepsPath64: $(DepsPath64)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build OBS Studio 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build32\obs-studio.sln'
- task: MSBuild@1
displayName: 'Build OBS Studio 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build64\obs-studio.sln'
- script: ./CI/prepare-windows.cmd
displayName: 'CMake obs-websocket'
env:
build_config: $(build_config)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build obs-websocket 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build32\obs-websocket.sln'
- task: MSBuild@1
displayName: 'Build obs-websocket 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build64\obs-websocket.sln'
- script: ./CI/package-windows.cmd
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
displayName: 'Upload package artifacts'
inputs:
pathtoPublish: './package'
artifactName: 'windows_build'
- job: 'Build_Linux'
pool:
vmImage: 'ubuntu-18.04'
variables:
BUILD_REASON: $(Build.Reason)
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
BRANCH_FULL_NAME: $(Build.SourceBranch)
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-ubuntu.sh
displayName: 'Install dependencies'
- script: ./CI/build-ubuntu.sh
displayName: 'Build obs-websocket'
- script: ./CI/package-macos.sh
displayName: 'Package'
- script: ./CI/package-ubuntu.sh
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './package'
artifactName: 'deb_build'
- job: 'Build_macOS'
pool:
vmImage: 'macos-10.14'
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install dependencies'
- script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS'
- script: ./CI/build-macos.sh
displayName: 'Build obs-websocket'
- task: InstallAppleCertificate@1
displayName: 'Install release signing certificates'
condition: eq(variables['isReleaseMode'], true)
inputs:
certSecureFile: 'Certificates.p12'
certPwd: $(secrets.macOS.certificatesImportPassword)
- script: ./CI/package-macos.sh
displayName: 'Package obs-websocket'
env:
RELEASE_MODE: $(isReleaseMode)
CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity)
INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity)
AC_USERNAME: $(secrets.macOS.notarization.username)
AC_PASSWORD: $(secrets.macOS.notarization.password)
AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'build'
artifactName: 'macos_build'

View File

@ -10,7 +10,7 @@ OBSWebsocket.NotifyConnect.Message="Client %1 connected"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system."
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# obs-websocket 4.6.0 protocol reference
# obs-websocket 4.8.0 protocol reference
# General Introduction
Messages are exchanged between the client and the server as JSON objects.

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket"
#define MyAppVersion "4.6.0"
#define MyAppVersion "4.8.0"
#define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"

View File

@ -16,12 +16,15 @@ 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 <inttypes.h>
#include <QtWidgets/QMainWindow>
#include <QtCore/QDir>
#include <QtCore/QUrl>
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <util/platform.h>
#include "obs-websocket.h"
#include "Utils.h"
@ -101,9 +104,11 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
* @typedef {Object} `SceneItem` An OBS Scene Item.
* @property {Number} `cy`
* @property {Number} `cx`
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {String} `name` The name of this Scene Item.
* @property {int} `id` Scene item ID
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
* @property {Number} `source_cx`
* @property {Number} `source_cy`
@ -143,6 +148,8 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
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));
@ -173,23 +180,11 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
return data;
}
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* item) {
OBSSceneItem sceneItem;
if (obs_data_has_user_value(item, "id")) {
sceneItem = GetSceneItemFromId(source, obs_data_get_int(item, "id"));
if (obs_data_has_user_value(item, "name") &&
(QString)obs_source_get_name(obs_sceneitem_get_source(sceneItem)) !=
(QString)obs_data_get_string(item, "name")) {
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
if (!scene) {
return nullptr;
}
}
else if (obs_data_has_user_value(item, "name")) {
sceneItem = GetSceneItemFromName(source, obs_data_get_string(item, "name"));
}
return sceneItem;
}
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name) {
struct current_search {
QString query;
obs_sceneitem_t* result;
@ -199,11 +194,6 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
current_search search;
search.query = name;
search.result = nullptr;
search.enumCallback = nullptr;
OBSScene scene = obs_scene_from_source(source);
if (!scene)
return nullptr;
search.enumCallback = [](
obs_scene_t* scene,
@ -236,10 +226,13 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
return search.result;
}
// TODO refactor this to unify it with GetSceneItemFromName
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
if (!scene) {
return nullptr;
}
struct current_search {
size_t query;
int query;
obs_sceneitem_t* result;
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
};
@ -247,11 +240,6 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
current_search search;
search.query = id;
search.result = nullptr;
search.enumCallback = nullptr;
OBSScene scene = obs_scene_from_source(source);
if (!scene)
return nullptr;
search.enumCallback = [](
obs_scene_t* scene,
@ -261,7 +249,7 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
current_search* search = reinterpret_cast<current_search*>(param);
if (obs_sceneitem_is_group(currentItem)) {
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, param);
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
if (search->result) {
return false;
}
@ -281,6 +269,49 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
return search.result;
}
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) {
if (!scene) {
return nullptr;
}
OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id");
int id = obs_data_item_get_int(idInfoItem);
OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name");
const char* name = obs_data_item_get_string(nameInfoItem);
if (idInfoItem) {
obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id);
obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem);
QString sceneItemName = obs_source_get_name(sceneItemSource);
if (nameInfoItem && (QString(name) != sceneItemName)) {
return nullptr;
}
return sceneItem;
} else if (nameInfoItem) {
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem)
{
enum obs_data_type dataType = obs_data_item_gettype(dataItem);
if (dataType == OBS_DATA_OBJECT) {
OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem);
return GetSceneItemFromItem(scene, itemData);
} else if (dataType == OBS_DATA_STRING) {
QString name = obs_data_item_get_string(dataItem);
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
bool Utils::IsValidAlignment(const uint32_t alignment) {
switch (alignment) {
case OBS_ALIGN_CENTER:
@ -319,17 +350,19 @@ obs_source_t* Utils::GetTransitionFromName(QString searchName) {
return foundTransition;
}
obs_source_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
// do addref on the return source, so no need to use an OBSSource helper
obs_source_t* scene = nullptr;
// increase the returned source's refcount
OBSSourceAutoRelease sceneSource = nullptr;
if (sceneName.isEmpty() || sceneName.isNull())
scene = obs_frontend_get_current_scene();
else
scene = obs_get_source_by_name(sceneName.toUtf8());
if (sceneName.isEmpty() || sceneName.isNull()) {
sceneSource = obs_frontend_get_current_scene();
}
else {
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
}
return scene;
return obs_scene_from_source(sceneSource);
}
obs_data_array_t* Utils::GetScenes() {
@ -362,18 +395,37 @@ QSpinBox* Utils::GetTransitionDurationControl() {
return window->findChild<QSpinBox*>("transitionDuration");
}
int Utils::GetTransitionDuration() {
QSpinBox* control = GetTransitionDurationControl();
if (control)
return control->value();
else
int Utils::GetTransitionDuration(obs_source_t* transition) {
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) {
return -1;
}
}
void Utils::SetTransitionDuration(int ms) {
QSpinBox* control = GetTransitionDurationControl();
if (control && ms >= 0)
control->setValue(ms);
QString transitionKind = obs_source_get_id(transition);
if (transitionKind == "cut_transition") {
// If this is a Cut transition, return 0
return 0;
}
if (obs_transition_fixed(transition)) {
// If this transition has a fixed duration (such as a Stinger),
// we don't currently have a way of retrieving that number.
// For now, return -1 to indicate that we don't know the actual duration.
return -1;
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
// Detect if transition is the global transition or a transition override.
// Fetching the duration is different depending on the case.
obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration");
int duration = (
transitionDurationItem
? obs_data_item_get_int(transitionDurationItem)
: obs_frontend_get_transition_duration()
);
return duration;
}
bool Utils::SetTransitionByName(QString transitionName) {
@ -387,38 +439,35 @@ bool Utils::SetTransitionByName(QString transitionName) {
}
}
QPushButton* Utils::GetPreviewModeButtonControl() {
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QPushButton*>("modeSwitch");
}
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
int duration = Utils::GetTransitionDuration(transition);
if (duration < 0) {
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
}
QLayout* Utils::GetPreviewLayout() {
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QLayout*>("previewLayout");
}
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
void Utils::TransitionToProgram() {
if (!obs_frontend_preview_program_mode_active())
return;
obs_data_t* transitionData = obs_data_create();
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
obs_data_set_string(transitionData, "type", obs_source_get_id(transition));
obs_data_set_int(transitionData, "duration", duration);
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
// then this won't work as expected
// When a transition starts and while it is running, SOURCE_A is the source scene
// and SOURCE_B is the destination scene.
// Before the transition_end event is triggered on a transition, the destination scene
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
// we don't know what was the source scene
// TODO fix this in libobs
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
bool isTransitionEndEvent = (sourceScene == destinationScene);
if (!isTransitionEndEvent) {
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene));
}
// The program options widget is the second item in the left-to-right layout
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
// 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();
return transitionData;
}
QString Utils::OBSVersionString() {
@ -588,7 +637,7 @@ void Utils::StartReplayBuffer() {
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
OBSData dummyBinding = obs_data_create();
OBSDataAutoRelease dummyBinding = obs_data_create();
obs_data_set_bool(dummyBinding, "control", true);
obs_data_set_bool(dummyBinding, "alt", true);
obs_data_set_bool(dummyBinding, "shift", true);
@ -744,6 +793,19 @@ obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
return data;
}
obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings)
{
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "enabled", obs_source_enabled(filter));
obs_data_set_string(data, "type", obs_source_get_id(filter));
obs_data_set_string(data, "name", obs_source_get_name(filter));
if (includeSettings) {
OBSDataAutoRelease settings = obs_source_get_settings(filter);
obs_data_set_obj(data, "settings", settings);
}
return data;
}
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
{
struct enum_params {
@ -764,14 +826,36 @@ obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool include
{
auto enumParams = reinterpret_cast<struct enum_params*>(param);
OBSDataAutoRelease filter = obs_data_create();
obs_data_set_string(filter, "type", obs_source_get_id(child));
obs_data_set_string(filter, "name", obs_source_get_name(child));
if (enumParams->includeSettings) {
obs_data_set_obj(filter, "settings", obs_source_get_settings(child));
}
obs_data_array_push_back(enumParams->filters, filter);
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
obs_data_array_push_back(enumParams->filters, filterData);
}, &enumParams);
return enumParams.filters;
}
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
{
void* frontendApi = os_dlopen("obs-frontend-api");
if (recPausedFuncPtr) {
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
}
if (pauseRecFuncPtr) {
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
}
}
QString Utils::nsToTimestamp(uint64_t ns)
{
uint64_t ms = ns / 1000000ULL;
uint64_t secs = ms / 1000ULL;
uint64_t minutes = secs / 60ULL;
uint64_t hoursPart = minutes / 60ULL;
uint64_t minutesPart = minutes % 60ULL;
uint64_t secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL;
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
}

View File

@ -31,55 +31,58 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h>
#include <util/config-file.h>
class Utils {
public:
static obs_data_array_t* StringListToArray(char** strings, const char* key);
static obs_data_array_t* GetSceneItems(obs_source_t* source);
static obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
static obs_sceneitem_t* GetSceneItemFromName(
obs_source_t* source, QString name);
static obs_sceneitem_t* GetSceneItemFromId(obs_source_t* source, size_t id);
static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item);
static obs_source_t* GetTransitionFromName(QString transitionName);
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
static obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
typedef void(*PauseRecordingFunction)(bool);
typedef bool(*RecordingPausedFunction)();
static obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
namespace Utils {
obs_data_array_t* StringListToArray(char** strings, const char* key);
obs_data_array_t* GetSceneItems(obs_source_t* source);
obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
static bool IsValidAlignment(const uint32_t alignment);
// These functions support nested lookup into groups
obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name);
obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id);
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem);
static obs_data_array_t* GetScenes();
static obs_data_t* GetSceneData(obs_source_t* source);
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName);
obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
obs_data_t* GetSourceFilterInfo(obs_source_t* filter, bool includeSettings);
obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
bool IsValidAlignment(const uint32_t alignment);
obs_data_array_t* GetScenes();
obs_data_t* GetSceneData(obs_source_t* source);
// TODO contribute a proper frontend API method for this to OBS and remove this hack
static QSpinBox* GetTransitionDurationControl();
static int GetTransitionDuration();
static void SetTransitionDuration(int ms);
QSpinBox* GetTransitionDurationControl();
int GetTransitionDuration(obs_source_t* transition);
obs_source_t* GetTransitionFromName(QString transitionName);
bool SetTransitionByName(QString transitionName);
obs_data_t* GetTransitionData(obs_source_t* transition);
static bool SetTransitionByName(QString transitionName);
QString OBSVersionString();
static QPushButton* GetPreviewModeButtonControl();
static QLayout* GetPreviewLayout();
// TODO contribute a proper frontend API method for this to OBS and remove this hack
static void TransitionToProgram();
static QString OBSVersionString();
static QSystemTrayIcon* GetTrayIcon();
static void SysTrayNotify(
QSystemTrayIcon* GetTrayIcon();
void SysTrayNotify(
QString text,
QSystemTrayIcon::MessageIcon n,
QString title = QString("obs-websocket"));
static const char* GetRecordingFolder();
static bool SetRecordingFolder(const char* path);
const char* GetRecordingFolder();
bool SetRecordingFolder(const char* path);
static QString ParseDataToQueryString(obs_data_t* data);
static obs_hotkey_t* FindHotkeyByName(QString name);
static bool ReplayBufferEnabled();
static void StartReplayBuffer();
static bool IsRPHotkeySet();
static const char* GetFilenameFormatting();
static bool SetFilenameFormatting(const char* filenameFormatting);
QString ParseDataToQueryString(obs_data_t* data);
obs_hotkey_t* FindHotkeyByName(QString name);
bool ReplayBufferEnabled();
void StartReplayBuffer();
bool IsRPHotkeySet();
const char* GetFilenameFormatting();
bool SetFilenameFormatting(const char* filenameFormatting);
QString nsToTimestamp(uint64_t ns);
};

View File

@ -17,45 +17,21 @@
* with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <inttypes.h>
#include <util/platform.h>
#include <media-io/video-io.h>
#include <QtWidgets/QPushButton>
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include "rpc/RpcEvent.h"
#define STATUS_INTERVAL 2000
bool transitionIsCut(obs_source_t* transition) {
if (!transition)
return false;
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
&& QString(obs_source_get_id(transition)) == "cut_transition") {
return true;
}
return false;
}
const char* nsToTimestamp(uint64_t ns) {
uint64_t ms = ns / (1000 * 1000);
uint64_t secs = ms / 1000;
uint64_t minutes = secs / 60;
uint64_t hoursPart = minutes / 60;
uint64_t minutesPart = minutes % 60;
uint64_t secsPart = secs % 60;
uint64_t msPart = ms % 1000;
char* ts = (char*)bmalloc(64);
sprintf(ts, "%02lu:%02lu:%02lu.%03lu", hoursPart, minutesPart, secsPart, msPart);
return ts;
}
const char* sourceTypeToString(obs_source_type type) {
switch (type) {
case OBS_SOURCE_TYPE_INPUT:
@ -86,7 +62,8 @@ const char* calldata_get_string(const calldata_t* data, const char* name) {
WSEvents::WSEvents(WSServerPtr srv) :
_srv(srv),
_streamStarttime(0),
_recStarttime(0),
_lastBytesSent(0),
_lastBytesSentTime(0),
HeartbeatIsActive(false),
pulse(false)
{
@ -143,112 +120,138 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
return;
}
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
switch (event) {
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
owner->hookTransitionPlaybackEvents();
break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
owner->OnSceneChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
break;
case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED:
owner->OnSceneListChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
owner->hookTransitionPlaybackEvents();
owner->OnSceneCollectionChange();
}
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) {
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
owner->OnSceneCollectionListChange();
}
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) {
break;
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
owner->OnTransitionChange();
}
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) {
break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
owner->hookTransitionPlaybackEvents();
owner->OnTransitionListChange();
}
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
owner->OnProfileChange();
}
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) {
break;
case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED:
owner->OnProfileListChange();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
break;
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
owner->OnStreamStarting();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
break;
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
owner->streamStatusTimer.start(STATUS_INTERVAL);
owner->StreamStatus();
owner->OnStreamStarted();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
owner->streamStatusTimer.stop();
owner->OnStreamStopping();
}
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
owner->OnStreamStopped();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTING:
owner->OnRecordingStarting();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
owner->OnRecordingStarted();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
owner->OnRecordingStopping();
}
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
owner->OnRecordingStopped();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
owner->OnRecordingPaused();
break;
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
owner->OnRecordingResumed();
break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
owner->OnReplayStarting();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED:
owner->OnReplayStarted();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING:
owner->OnReplayStopping();
}
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED:
owner->OnReplayStopped();
}
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED) {
break;
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
owner->OnStudioModeSwitched(true);
}
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED) {
break;
case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED:
owner->OnStudioModeSwitched(false);
}
else if (event == OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED) {
break;
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
owner->OnPreviewSceneChanged();
}
else if (event == OBS_FRONTEND_EVENT_EXIT) {
break;
case OBS_FRONTEND_EVENT_EXIT:
owner->unhookTransitionPlaybackEvents();
owner->OnExit();
break;
}
}
void WSEvents::broadcastUpdate(const char* updateType,
obs_data_t* additionalFields = nullptr)
{
OBSDataAutoRelease update = obs_data_create();
obs_data_set_string(update, "update-type", updateType);
const char* ts = nullptr;
std::optional<uint64_t> streamTime;
if (obs_frontend_streaming_active()) {
ts = nsToTimestamp(os_gettime_ns() - _streamStarttime);
obs_data_set_string(update, "stream-timecode", ts);
bfree((void*)ts);
streamTime = std::make_optional(getStreamingTime());
}
std::optional<uint64_t> recordingTime;
if (obs_frontend_recording_active()) {
ts = nsToTimestamp(os_gettime_ns() - _recStarttime);
obs_data_set_string(update, "rec-timecode", ts);
bfree((void*)ts);
recordingTime = std::make_optional(getRecordingTime());
}
if (additionalFields)
obs_data_apply(update, additionalFields);
QString json = obs_data_get_json(update);
_srv->broadcast(json.toStdString());
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Update << '%s'", json.toUtf8().constData());
}
RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields);
_srv->broadcast(event);
}
void WSEvents::connectSourceSignals(obs_source_t* source) {
@ -279,13 +282,12 @@ void WSEvents::connectSourceSignals(obs_source_t* source) {
signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this);
signal_handler_connect(sh,
"item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_connect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_connect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
}
if (sourceType == OBS_SOURCE_TYPE_TRANSITION) {
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
}
}
void WSEvents::disconnectSourceSignals(obs_source_t* source) {
@ -311,33 +313,98 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) {
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
signal_handler_disconnect(sh,
"item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_disconnect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
uint64_t WSEvents::GetStreamingTime() {
if (obs_frontend_streaming_active())
return (os_gettime_ns() - _streamStarttime);
else
void WSEvents::connectFilterSignals(obs_source_t* filter) {
if (!filter) {
return;
}
signal_handler_t* sh = obs_source_get_signal_handler(filter);
signal_handler_connect(sh, "enable", OnSourceFilterVisibilityChanged, this);
}
void WSEvents::disconnectFilterSignals(obs_source_t* filter) {
if (!filter) {
return;
}
signal_handler_t* sh = obs_source_get_signal_handler(filter);
signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this);
}
void WSEvents::hookTransitionPlaybackEvents() {
obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions);
for (uint i = 0; i < transitions.sources.num; i++) {
obs_source_t* transition = transitions.sources.array[i];
signal_handler_t* sh = obs_source_get_signal_handler(transition);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
obs_frontend_source_list_free(&transitions);
}
void WSEvents::unhookTransitionPlaybackEvents() {
obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions);
for (uint i = 0; i < transitions.sources.num; i++) {
obs_source_t* transition = transitions.sources.array[i];
signal_handler_t* sh = obs_source_get_signal_handler(transition);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
obs_frontend_source_list_free(&transitions);
}
uint64_t getOutputRunningTime(obs_output_t* output) {
if (!output || !obs_output_active(output)) {
return 0;
}
video_t* video = obs_output_video(output);
uint64_t frameTimeNs = video_output_get_frame_time(video);
int totalFrames = obs_output_get_total_frames(output);
return (((uint64_t)totalFrames) * frameTimeNs);
}
const char* WSEvents::GetStreamingTimecode() {
return nsToTimestamp(GetStreamingTime());
uint64_t WSEvents::getStreamingTime() {
OBSOutputAutoRelease streamingOutput = obs_frontend_get_streaming_output();
return getOutputRunningTime(streamingOutput);
}
uint64_t WSEvents::GetRecordingTime() {
if (obs_frontend_recording_active())
return (os_gettime_ns() - _recStarttime);
else
return 0;
uint64_t WSEvents::getRecordingTime() {
OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output();
return getOutputRunningTime(recordingOutput);
}
const char* WSEvents::GetRecordingTimecode() {
return nsToTimestamp(GetRecordingTime());
QString WSEvents::getStreamingTimecode() {
return Utils::nsToTimestamp(getStreamingTime());
}
QString WSEvents::getRecordingTimecode() {
return Utils::nsToTimestamp(getRecordingTime());
}
/**
@ -490,6 +557,7 @@ void WSEvents::OnStreamStarting() {
void WSEvents::OnStreamStarted() {
_streamStarttime = os_gettime_ns();
_lastBytesSent = 0;
broadcastUpdate("StreamStarted");
}
@ -506,7 +574,6 @@ void WSEvents::OnStreamStarted() {
void WSEvents::OnStreamStopping() {
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStopping", data);
}
@ -520,6 +587,7 @@ void WSEvents::OnStreamStopping() {
*/
void WSEvents::OnStreamStopped() {
_streamStarttime = 0;
broadcastUpdate("StreamStopped");
}
@ -544,7 +612,6 @@ void WSEvents::OnRecordingStarting() {
* @since 0.3
*/
void WSEvents::OnRecordingStarted() {
_recStarttime = os_gettime_ns();
broadcastUpdate("RecordingStarted");
}
@ -569,10 +636,33 @@ void WSEvents::OnRecordingStopping() {
* @since 0.3
*/
void WSEvents::OnRecordingStopped() {
_recStarttime = 0;
broadcastUpdate("RecordingStopped");
}
/**
* Current recording paused
*
* @api events
* @name RecordingPaused
* @category recording
* @since 4.7.0
*/
void WSEvents::OnRecordingPaused() {
broadcastUpdate("RecordingPaused");
}
/**
* Current recording resumed
*
* @api events
* @name RecordingResumed
* @category recording
* @since 4.7.0
*/
void WSEvents::OnRecordingResumed() {
broadcastUpdate("RecordingResumed");
}
/**
* A request to start the replay buffer has been issued.
*
@ -664,6 +754,7 @@ void WSEvents::OnExit() {
void WSEvents::StreamStatus() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = obs_frontend_recording_paused();
bool replayBufferActive = obs_frontend_replay_buffer_active();
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -690,9 +781,7 @@ void WSEvents::StreamStatus() {
_lastBytesSent = bytesSent;
_lastBytesSentTime = bytesSentTime;
uint64_t totalStreamTime =
(os_gettime_ns() - _streamStarttime) / 1000000000;
uint64_t totalStreamTime = (getStreamingTime() / 1000000000ULL);
int totalFrames = obs_output_get_total_frames(streamOutput);
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
@ -702,6 +791,7 @@ void WSEvents::StreamStatus() {
obs_data_set_bool(data, "streaming", streamingActive);
obs_data_set_bool(data, "recording", recordingActive);
obs_data_set_bool(data, "recording-paused", recordingPaused);
obs_data_set_bool(data, "replay-buffer-active", replayBufferActive);
obs_data_set_int(data, "bytes-per-sec", bytesPerSec);
@ -748,6 +838,7 @@ void WSEvents::Heartbeat() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = obs_frontend_recording_paused();
OBSDataAutoRelease data = obs_data_create();
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
@ -756,23 +847,24 @@ void WSEvents::Heartbeat() {
pulse = !pulse;
obs_data_set_bool(data, "pulse", pulse);
obs_data_set_string(data, "current-profile", obs_frontend_get_current_profile());
char* currentProfile = obs_frontend_get_current_profile();
obs_data_set_string(data, "current-profile", currentProfile);
bfree(currentProfile);
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene));
obs_data_set_bool(data, "streaming", streamingActive);
if (streamingActive) {
uint64_t totalStreamTime = (os_gettime_ns() - _streamStarttime) / 1000000000;
obs_data_set_int(data, "total-stream-time", totalStreamTime);
obs_data_set_int(data, "total-stream-time", (getStreamingTime() / 1000000000ULL));
obs_data_set_int(data, "total-stream-bytes", (uint64_t)obs_output_get_total_bytes(streamOutput));
obs_data_set_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput));
}
obs_data_set_bool(data, "recording", recordingActive);
obs_data_set_bool(data, "recording-paused", recordingPaused);
if (recordingActive) {
uint64_t totalRecordTime = (os_gettime_ns() - _recStarttime) / 1000000000;
obs_data_set_int(data, "total-record-time", totalRecordTime);
obs_data_set_int(data, "total-record-time", (getRecordingTime() / 1000000000ULL));
obs_data_set_int(data, "total-record-bytes", (uint64_t)obs_output_get_total_bytes(recordOutput));
obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput));
}
@ -804,7 +896,10 @@ void WSEvents::TransitionDurationChanged(int ms) {
* A transition (other than "cut") has begun.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* Will be -1 for any transition with a fixed duration,
* such as a Stinger, due to limitations of the OBS API.
* @return {String} `from-scene` Source scene of the transition
* @return {String} `to-scene` Destination scene of the transition
*
@ -817,40 +912,66 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) return;
// Detect if transition is the global transition or a transition override.
// Fetching the duration is different depending on the case.
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
int duration = -1;
if (obs_data_has_default_value(destinationSettings, "transition_duration") ||
obs_data_has_user_value(destinationSettings, "transition_duration"))
{
duration = obs_data_get_int(destinationSettings, "transition_duration");
} else {
duration = Utils::GetTransitionDuration();
}
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "name", obs_source_get_name(transition));
if (duration >= 0) {
obs_data_set_int(fields, "duration", duration);
} else {
blog(LOG_WARNING, "OnTransitionBegin: duration is negative !");
}
if (sourceScene) {
obs_data_set_string(fields, "from-scene", obs_source_get_name(sourceScene));
}
if (destinationScene) {
obs_data_set_string(fields, "to-scene", obs_source_get_name(destinationScene));
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionBegin", fields);
}
/**
* A transition (other than "cut") has ended.
* Please note that the `from-scene` field is not available in TransitionEnd.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionEnd", fields);
}
/**
* A stinger transition has finished playing its video.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `from-scene` Source scene of the transition
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionVideoEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionVideoEnd", fields);
}
/**
* A source has been created. A source can be an input, a scene or a transition.
*
@ -1011,9 +1132,9 @@ void WSEvents::OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data) {
* Audio mixer routing changed on a source.
*
* @return {String} `sourceName` Source name
* @return {Array<Object>} `routingStatus` Routing status of the source for each audio mixer (array of 6 values)
* @return {int} `routingStatus.*.id` Mixer number
* @return {boolean} `routingStatus.*.enabled` Routing status
* @return {Array<Object>} `mixers` Routing status of the source for each audio mixer (array of 6 values)
* @return {int} `mixers.*.id` Mixer number
* @return {boolean} `mixers.*.enabled` Routing status
* @return {String} `hexMixersValue` Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value
*
* @api events
@ -1056,6 +1177,7 @@ void WSEvents::OnSourceAudioMixersChanged(void* param, calldata_t* data) {
*
* @return {String} `previousName` Previous source name
* @return {String} `newName` New source name
* @return {String} `sourceType` Type of source (input, scene, filter, transition)
*
* @api events
* @name SourceRenamed
@ -1080,6 +1202,8 @@ void WSEvents::OnSourceRename(void* param, calldata_t* data) {
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "previousName", previousName);
obs_data_set_string(fields, "newName", newName);
obs_data_set_string(fields, "sourceType",
sourceTypeToString(obs_source_get_type(source))); // TODO: Split into dedicated events for source/scene. Only doing it this way for backwards compatability until 5.0
self->broadcastUpdate("SourceRenamed", fields);
}
@ -1109,6 +1233,8 @@ void WSEvents::OnSourceFilterAdded(void* param, calldata_t* data) {
return;
}
self->connectFilterSignals(filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
OBSDataAutoRelease fields = obs_data_create();
@ -1140,6 +1266,11 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
}
obs_source_t* filter = calldata_get_pointer<obs_source_t>(data, "filter");
if (!filter) {
return;
}
self->disconnectFilterSignals(filter);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "sourceName", obs_source_get_name(source));
@ -1148,6 +1279,36 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
self->broadcastUpdate("SourceFilterRemoved", fields);
}
/**
* The visibility/enabled state of a filter changed
*
* @return {String} `sourceName` Source name
* @return {String} `filterName` Filter name
* @return {Boolean} `filterEnabled` New filter state
*
* @api events
* @name SourceFilterVisibilityChanged
* @category sources
* @since 4.7.0
*/
void WSEvents::OnSourceFilterVisibilityChanged(void* param, calldata_t* data) {
auto self = reinterpret_cast<WSEvents*>(param);
OBSSource source = calldata_get_pointer<obs_source_t>(data, "source");
if (!source) {
return;
}
OBSSource parent = obs_filter_get_parent(source);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "sourceName", obs_source_get_name(parent));
obs_data_set_string(fields, "filterName", obs_source_get_name(source));
obs_data_set_bool(fields, "filterEnabled", obs_source_enabled(source));
self->broadcastUpdate("SourceFilterVisibilityChanged", fields);
}
/**
* Filters in a source have been reordered.
*
@ -1187,7 +1348,7 @@ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) {
*
* @api events
* @name SourceOrderChanged
* @category sources
* @category scene items
* @since 4.0.0
*/
void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
@ -1229,7 +1390,7 @@ void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemAdded
* @category sources
* @category scene items
* @since 4.0.0
*/
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
@ -1262,7 +1423,7 @@ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemRemoved
* @category sources
* @category scene items
* @since 4.0.0
*/
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
@ -1296,7 +1457,7 @@ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemVisibilityChanged
* @category sources
* @category scene items
* @since 4.0.0
*/
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
@ -1324,6 +1485,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
}
/**
* An item's locked status has been toggled.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item in the scene.
* @return {int} `item-id` Scene item ID
* @return {boolean} `item-locked` New locked state of the item.
*
* @api events
* @name SceneItemLockChanged
* @category scene items
* @since 4.8.0
*/
void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
obs_sceneitem_t* sceneItem = nullptr;
calldata_get_ptr(data, "item", &sceneItem);
bool locked = false;
calldata_get_bool(data, "locked", &locked);
const char* sceneName =
obs_source_get_name(obs_scene_get_source(scene));
const char* sceneItemName =
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "scene-name", sceneName);
obs_data_set_string(fields, "item-name", sceneItemName);
obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem));
obs_data_set_bool(fields, "item-locked", locked);
instance->broadcastUpdate("SceneItemLockChanged", fields);
}
/**
* An item's transform has been changed.
*
@ -1334,7 +1533,7 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemTransformChanged
* @category sources
* @category scene items
* @since 4.6.0
*/
void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) {
@ -1370,7 +1569,7 @@ void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemSelected
* @category sources
* @category scene items
* @since 4.6.0
*/
void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) {
@ -1405,7 +1604,7 @@ void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) {
*
* @api events
* @name SceneItemDeselected
* @category sources
* @category scene items
* @since 4.6.0
*/
void WSEvents::OnSceneItemDeselected(void* param, calldata_t* data) {
@ -1475,6 +1674,25 @@ void WSEvents::OnStudioModeSwitched(bool checked) {
broadcastUpdate("StudioModeSwitched", data);
}
/**
* A custom broadcast message was received
*
* @return {String} `realm` Identifier provided by the sender
* @return {Object} `data` User-defined data
*
* @api events
* @name BroadcastCustomMessage
* @category general
* @since 4.7.0
*/
void WSEvents::OnBroadcastCustomMessage(QString realm, obs_data_t* data) {
OBSDataAutoRelease broadcastData = obs_data_create();
obs_data_set_string(broadcastData, "realm", realm.toUtf8().constData());
obs_data_set_obj(broadcastData, "data", data);
broadcastUpdate("BroadcastCustomMessage", broadcastData);
}
/**
* @typedef {Object} `OBSStats`
* @property {double} `fps` Current framerate.
@ -1500,12 +1718,12 @@ obs_data_t* WSEvents::GetStats() {
double averageFrameTime = (double)obs_get_average_frame_time_ns() / 1000000.0;
config_t* currentProfile = obs_frontend_get_profile_config();
QString outputMode = config_get_string(currentProfile, "Output", "Mode");
QString path = (outputMode == "Advanced") ?
const char* outputMode = config_get_string(currentProfile, "Output", "Mode");
const char* path = strcmp(outputMode, "Advanced") ?
config_get_string(currentProfile, "SimpleOutput", "FilePath") :
config_get_string(currentProfile, "AdvOut", "RecFilePath");
double freeDiskSpace = (double)os_get_free_disk_space(path.toUtf8()) / (1024.0 * 1024.0);
double freeDiskSpace = (double)os_get_free_disk_space(path) / (1024.0 * 1024.0);
obs_data_set_double(stats, "fps", obs_get_active_fps());
obs_data_set_int(stats, "render-total-frames", obs_get_total_frames());

View File

@ -40,12 +40,22 @@ public:
void connectSourceSignals(obs_source_t* source);
void disconnectSourceSignals(obs_source_t* source);
uint64_t GetStreamingTime();
const char* GetStreamingTimecode();
uint64_t GetRecordingTime();
const char* GetRecordingTimecode();
void connectFilterSignals(obs_source_t* filter);
void disconnectFilterSignals(obs_source_t* filter);
void hookTransitionPlaybackEvents();
void unhookTransitionPlaybackEvents();
uint64_t getStreamingTime();
uint64_t getRecordingTime();
QString getStreamingTimecode();
QString getRecordingTimecode();
obs_data_t* GetStats();
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
bool HeartbeatIsActive;
private slots:
@ -62,7 +72,6 @@ private:
bool pulse;
uint64_t _streamStarttime;
uint64_t _recStarttime;
uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime;
@ -90,6 +99,8 @@ private:
void OnRecordingStarted();
void OnRecordingStopping();
void OnRecordingStopped();
void OnRecordingPaused();
void OnRecordingResumed();
void OnReplayStarting();
void OnReplayStarted();
@ -105,6 +116,8 @@ private:
enum obs_frontend_event event, void* privateData);
static void OnTransitionBegin(void* param, calldata_t* data);
static void OnTransitionEnd(void* param, calldata_t* data);
static void OnTransitionVideoEnd(void* param, calldata_t* data);
static void OnSourceCreate(void* param, calldata_t* data);
static void OnSourceDestroy(void* param, calldata_t* data);
@ -118,12 +131,14 @@ private:
static void OnSourceFilterAdded(void* param, calldata_t* data);
static void OnSourceFilterRemoved(void* param, calldata_t* data);
static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data);
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
static void OnSceneReordered(void* param, calldata_t* data);
static void OnSceneItemAdd(void* param, calldata_t* data);
static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
static void OnSceneItemLockChanged(void* param, calldata_t* data);
static void OnSceneItemTransform(void* param, calldata_t* data);
static void OnSceneItemSelected(void* param, calldata_t* data);
static void OnSceneItemDeselected(void* param, calldata_t* data);

View File

@ -17,6 +17,8 @@
* with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <functional>
#include <obs-data.h>
#include "Config.h"
@ -24,204 +26,156 @@
#include "WSRequestHandler.h"
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
{ "GetVersion", WSRequestHandler::HandleGetVersion },
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
using namespace std::placeholders;
{ "GetStats", WSRequestHandler::HandleGetStats },
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
{ "GetVideoInfo", WSRequestHandler::HandleGetVideoInfo },
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap {
{ "GetVersion", &WSRequestHandler::GetVersion },
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
{ "Authenticate", &WSRequestHandler::Authenticate },
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
{ "GetStats", &WSRequestHandler::GetStats },
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
{ "OpenProjector", &WSRequestHandler::OpenProjector },
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage },
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
{ "StartRecording", WSRequestHandler::HandleStartRecording },
{ "StopRecording", WSRequestHandler::HandleStopRecording },
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
{ "GetSceneList", &WSRequestHandler::GetSceneList },
{ "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride },
{ "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride },
{ "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride },
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender },
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition },
{ "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform },
{ "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop },
{ "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties },
{ "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties },
{ "ResetSceneItem", &WSRequestHandler::ResetSceneItem },
{ "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem },
{ "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem },
{ "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems },
{ "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus },
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming },
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
{ "StartStreaming", &WSRequestHandler::StartStreaming },
{ "StopStreaming", &WSRequestHandler::StopStreaming },
{ "SetVolume", WSRequestHandler::HandleSetVolume },
{ "GetVolume", WSRequestHandler::HandleGetVolume },
{ "ToggleMute", WSRequestHandler::HandleToggleMute },
{ "SetMute", WSRequestHandler::HandleSetMute },
{ "GetMute", WSRequestHandler::HandleGetMute },
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
{ "TakeSourceScreenshot", WSRequestHandler::HandleTakeSourceScreenshot },
{ "StartRecording", &WSRequestHandler::StartRecording },
{ "StopRecording", &WSRequestHandler::StopRecording },
{ "PauseRecording", &WSRequestHandler::PauseRecording },
{ "ResumeRecording", &WSRequestHandler::ResumeRecording },
{ "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
{ "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
{ "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
{ "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer },
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer },
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer },
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer },
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder },
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder },
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
{ "ListProfiles", WSRequestHandler::HandleListProfiles },
{ "GetTransitionList", &WSRequestHandler::GetTransitionList },
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition },
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition },
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
{ "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition },
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
{ "SetVolume", &WSRequestHandler::SetVolume },
{ "GetVolume", &WSRequestHandler::GetVolume },
{ "ToggleMute", &WSRequestHandler::ToggleMute },
{ "SetMute", &WSRequestHandler::SetMute },
{ "GetMute", &WSRequestHandler::GetMute },
{ "SetSourceName", &WSRequestHandler::SetSourceName },
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
{ "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType },
{ "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType },
{ "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot },
{ "GetSourceFilters", &WSRequestHandler::GetSourceFilters },
{ "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo },
{ "AddFilterToSource", &WSRequestHandler::AddFilterToSource },
{ "RemoveFilterFromSource", &WSRequestHandler::RemoveFilterFromSource },
{ "ReorderSourceFilter", &WSRequestHandler::ReorderSourceFilter },
{ "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter },
{ "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings },
{ "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility },
{ "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection },
{ "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection },
{ "ListSceneCollections", &WSRequestHandler::ListSceneCollections },
{ "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile },
{ "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile },
{ "ListProfiles", &WSRequestHandler::ListProfiles },
{ "SetStreamSettings", &WSRequestHandler::SetStreamSettings },
{ "GetStreamSettings", &WSRequestHandler::GetStreamSettings },
{ "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings },
#if BUILD_CAPTIONS
{ "SendCaptions", WSRequestHandler::HandleSendCaptions },
{ "SendCaptions", &WSRequestHandler::SendCaptions },
#endif
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
{ "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
{ "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties },
{ "ListOutputs", &WSRequestHandler::ListOutputs },
{ "GetOutputInfo", &WSRequestHandler::GetOutputInfo },
{ "StartOutput", &WSRequestHandler::StartOutput },
{ "StopOutput", &WSRequestHandler::StopOutput }
};
QSet<QString> WSRequestHandler::authNotRequired {
const QSet<QString> WSRequestHandler::authNotRequired {
"GetVersion",
"GetAuthRequired",
"Authenticate"
};
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
_messageId(0),
_requestType(""),
data(nullptr),
_connProperties(connProperties)
{
}
std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) {
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Request >> '%s'", textMessage.c_str());
}
OBSDataAutoRelease responseData = processRequest(textMessage);
std::string response = obs_data_get_json(responseData);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Response << '%s'", response.c_str());
}
return response;
}
HandlerResponse WSRequestHandler::processRequest(std::string& textMessage){
std::string msgContainer(textMessage);
const char* msg = msgContainer.c_str();
data = obs_data_create_from_json(msg);
if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return SendErrorResponse("invalid JSON payload");
}
if (!hasField("request-type") || !hasField("message-id")) {
return SendErrorResponse("missing request parameters");
}
_requestType = obs_data_get_string(data, "request-type");
_messageId = obs_data_get_string(data, "message-id");
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){
if (GetConfig()->AuthRequired
&& (!authNotRequired.contains(_requestType))
&& (!authNotRequired.contains(request.methodName()))
&& (!_connProperties.isAuthenticated()))
{
return SendErrorResponse("Not Authenticated");
return RpcResponse::fail(request, "Not Authenticated");
}
HandlerResponse (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
RpcMethodHandler handlerFunc = messageMap[request.methodName()];
if (!handlerFunc) {
return SendErrorResponse("invalid request type");
return RpcResponse::fail(request, "invalid request type");
}
return handlerFunc(this);
}
WSRequestHandler::~WSRequestHandler() {
}
HandlerResponse WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
return SendResponse("ok", additionalFields);
}
HandlerResponse WSRequestHandler::SendErrorResponse(const char* errorMessage) {
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "error", errorMessage);
return SendResponse("error", fields);
}
HandlerResponse WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
return SendResponse("error", additionalFields);
}
HandlerResponse WSRequestHandler::SendResponse(const char* status, obs_data_t* fields) {
obs_data_t* response = obs_data_create();
obs_data_set_string(response, "message-id", _messageId);
obs_data_set_string(response, "status", status);
if (fields) {
obs_data_apply(response, fields);
}
return response;
}
bool WSRequestHandler::hasField(QString name) {
if (!data || name.isEmpty() || name.isNull())
return false;
return obs_data_has_user_value(data, name.toUtf8());
return std::bind(handlerFunc, this, _1)(request);
}

View File

@ -19,145 +19,153 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once
#include <QtCore/QString>
#include <QtCore/QHash>
#include <QtCore/QSet>
#include <QtCore/QVariantHash>
#include <QtCore/QString>
#include <QtCore/QSharedPointer>
#include <obs.hpp>
#include <obs-frontend-api.h>
#include "ConnectionProperties.h"
#include "rpc/RpcRequest.h"
#include "rpc/RpcResponse.h"
#include "obs-websocket.h"
typedef obs_data_t* HandlerResponse;
class WSRequestHandler : public QObject {
Q_OBJECT
class WSRequestHandler;
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&);
class WSRequestHandler {
public:
explicit WSRequestHandler(ConnectionProperties& connProperties);
~WSRequestHandler();
std::string processIncomingMessage(std::string& textMessage);
bool hasField(QString name);
RpcResponse processRequest(const RpcRequest& textMessage);
private:
const char* _messageId;
const char* _requestType;
ConnectionProperties& _connProperties;
OBSDataAutoRelease data;
HandlerResponse processRequest(std::string& textMessage);
static const QHash<QString, RpcMethodHandler> messageMap;
static const QSet<QString> authNotRequired;
HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendErrorResponse(const char* errorMessage);
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);
RpcResponse GetVersion(const RpcRequest&);
RpcResponse GetAuthRequired(const RpcRequest&);
RpcResponse Authenticate(const RpcRequest&);
static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
static QSet<QString> authNotRequired;
RpcResponse GetStats(const RpcRequest&);
RpcResponse SetHeartbeat(const RpcRequest&);
RpcResponse GetVideoInfo(const RpcRequest&);
RpcResponse OpenProjector(const RpcRequest&);
static HandlerResponse HandleGetVersion(WSRequestHandler* req);
static HandlerResponse HandleGetAuthRequired(WSRequestHandler* req);
static HandlerResponse HandleAuthenticate(WSRequestHandler* req);
RpcResponse SetFilenameFormatting(const RpcRequest&);
RpcResponse GetFilenameFormatting(const RpcRequest&);
static HandlerResponse HandleGetStats(WSRequestHandler* req);
static HandlerResponse HandleSetHeartbeat(WSRequestHandler* req);
static HandlerResponse HandleGetVideoInfo(WSRequestHandler* req);
RpcResponse BroadcastCustomMessage(const RpcRequest&);
static HandlerResponse HandleSetFilenameFormatting(WSRequestHandler* req);
static HandlerResponse HandleGetFilenameFormatting(WSRequestHandler* req);
RpcResponse SetCurrentScene(const RpcRequest&);
RpcResponse GetCurrentScene(const RpcRequest&);
RpcResponse GetSceneList(const RpcRequest&);
RpcResponse SetSceneTransitionOverride(const RpcRequest&);
RpcResponse RemoveSceneTransitionOverride(const RpcRequest&);
RpcResponse GetSceneTransitionOverride(const RpcRequest&);
static HandlerResponse HandleSetCurrentScene(WSRequestHandler* req);
static HandlerResponse HandleGetCurrentScene(WSRequestHandler* req);
static HandlerResponse HandleGetSceneList(WSRequestHandler* req);
RpcResponse SetSceneItemRender(const RpcRequest&);
RpcResponse SetSceneItemPosition(const RpcRequest&);
RpcResponse SetSceneItemTransform(const RpcRequest&);
RpcResponse SetSceneItemCrop(const RpcRequest&);
RpcResponse GetSceneItemProperties(const RpcRequest&);
RpcResponse SetSceneItemProperties(const RpcRequest&);
RpcResponse ResetSceneItem(const RpcRequest&);
RpcResponse DuplicateSceneItem(const RpcRequest&);
RpcResponse DeleteSceneItem(const RpcRequest&);
RpcResponse ReorderSceneItems(const RpcRequest&);
static HandlerResponse HandleSetSceneItemRender(WSRequestHandler* req);
static HandlerResponse HandleSetSceneItemPosition(WSRequestHandler* req);
static HandlerResponse HandleSetSceneItemTransform(WSRequestHandler* req);
static HandlerResponse HandleSetSceneItemCrop(WSRequestHandler* req);
static HandlerResponse HandleGetSceneItemProperties(WSRequestHandler* req);
static HandlerResponse HandleSetSceneItemProperties(WSRequestHandler* req);
static HandlerResponse HandleResetSceneItem(WSRequestHandler* req);
static HandlerResponse HandleDuplicateSceneItem(WSRequestHandler* req);
static HandlerResponse HandleDeleteSceneItem(WSRequestHandler* req);
static HandlerResponse HandleReorderSceneItems(WSRequestHandler* req);
RpcResponse GetStreamingStatus(const RpcRequest&);
RpcResponse StartStopStreaming(const RpcRequest&);
RpcResponse StartStopRecording(const RpcRequest&);
static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
static HandlerResponse HandleStartStopRecording(WSRequestHandler* req);
static HandlerResponse HandleStartStreaming(WSRequestHandler* req);
static HandlerResponse HandleStopStreaming(WSRequestHandler* req);
static HandlerResponse HandleStartRecording(WSRequestHandler* req);
static HandlerResponse HandleStopRecording(WSRequestHandler* req);
RpcResponse StartStreaming(const RpcRequest&);
RpcResponse StopStreaming(const RpcRequest&);
static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleStopReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleSaveReplayBuffer(WSRequestHandler* req);
RpcResponse StartRecording(const RpcRequest&);
RpcResponse StopRecording(const RpcRequest&);
RpcResponse PauseRecording(const RpcRequest&);
RpcResponse ResumeRecording(const RpcRequest&);
static HandlerResponse HandleSetRecordingFolder(WSRequestHandler* req);
static HandlerResponse HandleGetRecordingFolder(WSRequestHandler* req);
RpcResponse StartStopReplayBuffer(const RpcRequest&);
RpcResponse StartReplayBuffer(const RpcRequest&);
RpcResponse StopReplayBuffer(const RpcRequest&);
RpcResponse SaveReplayBuffer(const RpcRequest&);
static HandlerResponse HandleGetTransitionList(WSRequestHandler* req);
static HandlerResponse HandleGetCurrentTransition(WSRequestHandler* req);
static HandlerResponse HandleSetCurrentTransition(WSRequestHandler* req);
RpcResponse SetRecordingFolder(const RpcRequest&);
RpcResponse GetRecordingFolder(const RpcRequest&);
static HandlerResponse HandleSetVolume(WSRequestHandler* req);
static HandlerResponse HandleGetVolume(WSRequestHandler* req);
static HandlerResponse HandleToggleMute(WSRequestHandler* req);
static HandlerResponse HandleSetMute(WSRequestHandler* req);
static HandlerResponse HandleGetMute(WSRequestHandler* req);
static HandlerResponse HandleSetSyncOffset(WSRequestHandler* req);
static HandlerResponse HandleGetSyncOffset(WSRequestHandler* req);
static HandlerResponse HandleGetSpecialSources(WSRequestHandler* req);
static HandlerResponse HandleGetSourcesList(WSRequestHandler* req);
static HandlerResponse HandleGetSourceTypesList(WSRequestHandler* req);
static HandlerResponse HandleGetSourceSettings(WSRequestHandler* req);
static HandlerResponse HandleSetSourceSettings(WSRequestHandler* req);
static HandlerResponse HandleTakeSourceScreenshot(WSRequestHandler* req);
RpcResponse GetTransitionList(const RpcRequest&);
RpcResponse GetCurrentTransition(const RpcRequest&);
RpcResponse SetCurrentTransition(const RpcRequest&);
RpcResponse SetTransitionDuration(const RpcRequest&);
RpcResponse GetTransitionDuration(const RpcRequest&);
RpcResponse GetTransitionPosition(const RpcRequest&);
static HandlerResponse HandleGetSourceFilters(WSRequestHandler* req);
static HandlerResponse HandleAddFilterToSource(WSRequestHandler* req);
static HandlerResponse HandleRemoveFilterFromSource(WSRequestHandler* req);
static HandlerResponse HandleReorderSourceFilter(WSRequestHandler* req);
static HandlerResponse HandleMoveSourceFilter(WSRequestHandler* req);
static HandlerResponse HandleSetSourceFilterSettings(WSRequestHandler* req);
RpcResponse SetVolume(const RpcRequest&);
RpcResponse GetVolume(const RpcRequest&);
RpcResponse ToggleMute(const RpcRequest&);
RpcResponse SetMute(const RpcRequest&);
RpcResponse GetMute(const RpcRequest&);
RpcResponse SetSourceName(const RpcRequest&);
RpcResponse SetSyncOffset(const RpcRequest&);
RpcResponse GetSyncOffset(const RpcRequest&);
RpcResponse GetSpecialSources(const RpcRequest&);
RpcResponse GetSourcesList(const RpcRequest&);
RpcResponse GetSourceTypesList(const RpcRequest&);
RpcResponse GetSourceSettings(const RpcRequest&);
RpcResponse SetSourceSettings(const RpcRequest&);
RpcResponse GetAudioMonitorType(const RpcRequest&);
RpcResponse SetAudioMonitorType(const RpcRequest&);
RpcResponse TakeSourceScreenshot(const RpcRequest&);
static HandlerResponse HandleSetCurrentSceneCollection(WSRequestHandler* req);
static HandlerResponse HandleGetCurrentSceneCollection(WSRequestHandler* req);
static HandlerResponse HandleListSceneCollections(WSRequestHandler* req);
RpcResponse GetSourceFilters(const RpcRequest&);
RpcResponse GetSourceFilterInfo(const RpcRequest&);
RpcResponse AddFilterToSource(const RpcRequest&);
RpcResponse RemoveFilterFromSource(const RpcRequest&);
RpcResponse ReorderSourceFilter(const RpcRequest&);
RpcResponse MoveSourceFilter(const RpcRequest&);
RpcResponse SetSourceFilterSettings(const RpcRequest&);
RpcResponse SetSourceFilterVisibility(const RpcRequest&);
static HandlerResponse HandleSetCurrentProfile(WSRequestHandler* req);
static HandlerResponse HandleGetCurrentProfile(WSRequestHandler* req);
static HandlerResponse HandleListProfiles(WSRequestHandler* req);
RpcResponse SetCurrentSceneCollection(const RpcRequest&);
RpcResponse GetCurrentSceneCollection(const RpcRequest&);
RpcResponse ListSceneCollections(const RpcRequest&);
static HandlerResponse HandleSetStreamSettings(WSRequestHandler* req);
static HandlerResponse HandleGetStreamSettings(WSRequestHandler* req);
static HandlerResponse HandleSaveStreamSettings(WSRequestHandler* req);
RpcResponse SetCurrentProfile(const RpcRequest&);
RpcResponse GetCurrentProfile(const RpcRequest&);
RpcResponse ListProfiles(const RpcRequest&);
RpcResponse SetStreamSettings(const RpcRequest&);
RpcResponse GetStreamSettings(const RpcRequest&);
RpcResponse SaveStreamSettings(const RpcRequest&);
#if BUILD_CAPTIONS
static HandlerResponse HandleSendCaptions(WSRequestHandler * req);
RpcResponse SendCaptions(const RpcRequest&);
#endif
static HandlerResponse HandleSetTransitionDuration(WSRequestHandler* req);
static HandlerResponse HandleGetTransitionDuration(WSRequestHandler* req);
RpcResponse GetStudioModeStatus(const RpcRequest&);
RpcResponse GetPreviewScene(const RpcRequest&);
RpcResponse SetPreviewScene(const RpcRequest&);
RpcResponse TransitionToProgram(const RpcRequest&);
RpcResponse EnableStudioMode(const RpcRequest&);
RpcResponse DisableStudioMode(const RpcRequest&);
RpcResponse ToggleStudioMode(const RpcRequest&);
static HandlerResponse HandleGetStudioModeStatus(WSRequestHandler* req);
static HandlerResponse HandleGetPreviewScene(WSRequestHandler* req);
static HandlerResponse HandleSetPreviewScene(WSRequestHandler* req);
static HandlerResponse HandleTransitionToProgram(WSRequestHandler* req);
static HandlerResponse HandleEnableStudioMode(WSRequestHandler* req);
static HandlerResponse HandleDisableStudioMode(WSRequestHandler* req);
static HandlerResponse HandleToggleStudioMode(WSRequestHandler* req);
RpcResponse SetTextGDIPlusProperties(const RpcRequest&);
RpcResponse GetTextGDIPlusProperties(const RpcRequest&);
static HandlerResponse HandleSetTextGDIPlusProperties(WSRequestHandler* req);
static HandlerResponse HandleGetTextGDIPlusProperties(WSRequestHandler* req);
RpcResponse SetTextFreetype2Properties(const RpcRequest&);
RpcResponse GetTextFreetype2Properties(const RpcRequest&);
static HandlerResponse HandleSetTextFreetype2Properties(WSRequestHandler* req);
static HandlerResponse HandleGetTextFreetype2Properties(WSRequestHandler* req);
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);
RpcResponse ListOutputs(const RpcRequest&);
RpcResponse GetOutputInfo(const RpcRequest&);
RpcResponse StartOutput(const RpcRequest&);
RpcResponse StopOutput(const RpcRequest&);
};

View File

@ -1,10 +1,13 @@
#include "WSRequestHandler.h"
#include <QtCore/QByteArray>
#include <QtGui/QImageWriter>
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#include "WSRequestHandler.h"
#define CASE(x) case x: return #x;
const char *describe_output_format(int format) {
switch (format) {
@ -60,32 +63,41 @@ const char *describe_scale_type(int scale) {
* @return {String} `obs-websocket-version` obs-websocket plugin version.
* @return {String} `obs-studio-version` OBS Studio program version.
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
*
* @api requests
* @name GetVersion
* @category general
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
QString obsVersion = Utils::OBSVersionString();
QList<QString> names = req->messageMap.keys();
names.sort(Qt::CaseInsensitive);
QList<QString> names = messageMap.keys();
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
QString requests;
names.sort(Qt::CaseInsensitive);
requests += names.takeFirst();
for (QString reqName : names) {
for (const QString& reqName : names) {
requests += ("," + reqName);
}
QString supportedImageExportFormats;
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
for (const QByteArray& format : imageWriterFormats) {
supportedImageExportFormats += ("," + QString::fromUtf8(format));
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8());
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
return req->SendOKResponse(data);
return request.success(data);
}
/**
@ -101,7 +113,7 @@ HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
* @category general
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
bool authRequired = GetConfig()->AuthRequired;
OBSDataAutoRelease data = obs_data_create();
@ -115,7 +127,7 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
config->Salt.toUtf8());
}
return req->SendOKResponse(data);
return request.success(data);
}
/**
@ -128,26 +140,26 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
* @category general
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
if (!req->hasField("auth")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
if (!request.hasField("auth")) {
return request.failed("missing request parameters");
}
if (req->_connProperties.isAuthenticated()) {
return req->SendErrorResponse("already authenticated");
if (_connProperties.isAuthenticated()) {
return request.failed("already authenticated");
}
QString auth = obs_data_get_string(req->data, "auth");
QString auth = obs_data_get_string(request.parameters(), "auth");
if (auth.isEmpty()) {
return req->SendErrorResponse("auth not specified!");
return request.failed("auth not specified!");
}
if (GetConfig()->CheckAuth(auth) == false) {
return req->SendErrorResponse("Authentication Failed.");
return request.failed("Authentication Failed.");
}
req->_connProperties.setAuthenticated(true);
return req->SendOKResponse();
_connProperties.setAuthenticated(true);
return request.success();
}
/**
@ -160,17 +172,18 @@ HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
* @category general
* @since 4.3.0
*/
HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
if (!req->hasField("enable")) {
return req->SendErrorResponse("Heartbeat <enable> parameter missing");
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
if (!request.hasField("enable")) {
return request.failed("Heartbeat <enable> parameter missing");
}
auto events = GetEventsSystem();
events->HeartbeatIsActive = obs_data_get_bool(req->data, "enable");
events->HeartbeatIsActive = obs_data_get_bool(request.parameters(), "enable");
OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -183,18 +196,19 @@ HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
* @category general
* @since 4.3.0
*/
HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
if (!req->hasField("filename-formatting")) {
return req->SendErrorResponse("<filename-formatting> parameter missing");
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
if (!request.hasField("filename-formatting")) {
return request.failed("<filename-formatting> parameter missing");
}
QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
QString filenameFormatting = obs_data_get_string(request.parameters(), "filename-formatting");
if (filenameFormatting.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
return req->SendOKResponse();
return request.success();
}
/**
@ -207,10 +221,11 @@ HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler*
* @category general
* @since 4.3.0
*/
HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -223,14 +238,49 @@ HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler*
* @category general
* @since 4.6.0
*/
HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_obj(response, "stats", stats);
return req->SendOKResponse(response);
return request.success(response);
}
/**
* Broadcast custom message to all connected WebSocket clients
*
* @param {String} `realm` Identifier to be choosen by the client
* @param {Object} `data` User-defined data
*
* @api requests
* @name BroadcastCustomMessage
* @category general
* @since 4.7.0
*/
RpcResponse WSRequestHandler::BroadcastCustomMessage(const RpcRequest& request) {
if (!request.hasField("realm") || !request.hasField("data")) {
return request.failed("missing request parameters");
}
QString realm = obs_data_get_string(request.parameters(), "realm");
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "data");
if (realm.isEmpty()) {
return request.failed("realm not specified!");
}
if (!data) {
return request.failed("data not specified!");
}
auto events = GetEventsSystem();
events->OnBroadcastCustomMessage(realm, data);
return request.success();
}
/**
* Get basic OBS video information
*
@ -249,9 +299,10 @@ HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
* @category general
* @since 4.6.0
*/
HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
obs_video_info ovi;
obs_get_video_info(&ovi);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "baseWidth", ovi.base_width);
obs_data_set_int(response, "baseHeight", ovi.base_height);
@ -262,5 +313,34 @@ HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
return req->SendOKResponse(response);
return request.success(response);
}
/**
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
*
* @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
*
* @api requests
* @name OpenProjector
* @category general
* @since 4.8.0
*/
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
const char* type = obs_data_get_string(request.parameters(), "type");
int monitor = -1;
if (request.hasField("monitor")) {
monitor = obs_data_get_int(request.parameters(), "monitor");
}
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
const char* name = obs_data_get_string(request.parameters(), "name");
obs_frontend_open_projector(type, monitor, geometry, name);
return request.success();
}

View File

@ -0,0 +1,184 @@
#include <functional>
#include "WSRequestHandler.h"
/**
* @typedef {Object} `Output`
* @property {String} `name` Output name
* @property {String} `type` Output type/kind
* @property {int} `width` Video output width
* @property {int} `height` Video output height
* @property {Object} `flags` Output flags
* @property {int} `flags.rawValue` Raw flags value
* @property {boolean} `flags.audio` Output uses audio
* @property {boolean} `flags.video` Output uses video
* @property {boolean} `flags.encoded` Output is encoded
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
* @property {boolean} `flags.service` Output uses a service
* @property {Object} `settings` Output name
* @property {boolean} `active` Output status (active or not)
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
* @property {double} `congestion` Output congestion
* @property {int} `totalFrames` Number of frames sent
* @property {int} `droppedFrames` Number of frames dropped
* @property {int} `totalBytes` Total bytes sent
*/
obs_data_t* getOutputInfo(obs_output_t* output)
{
if (!output) {
return nullptr;
}
OBSDataAutoRelease settings = obs_output_get_settings(output);
uint32_t rawFlags = obs_output_get_flags(output);
OBSDataAutoRelease flags = obs_data_create();
obs_data_set_int(flags, "rawValue", rawFlags);
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name", obs_output_get_name(output));
obs_data_set_string(data, "type", obs_output_get_id(output));
obs_data_set_int(data, "width", obs_output_get_width(output));
obs_data_set_int(data, "height", obs_output_get_height(output));
obs_data_set_obj(data, "flags", flags);
obs_data_set_obj(data, "settings", settings);
obs_data_set_bool(data, "active", obs_output_active(output));
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));
return data;
}
RpcResponse findOutputOrFail(const RpcRequest& request, std::function<RpcResponse (obs_output_t*)> callback)
{
if (!request.hasField("outputName")) {
return request.failed("missing request parameters");
}
const char* outputName = obs_data_get_string(request.parameters(), "outputName");
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
if (!output) {
return request.failed("specified output doesn't exist");
}
return callback(output);
}
/**
* List existing outputs
*
* @return {Array<Output>} `outputs` Outputs list
*
* @api requests
* @name ListOutputs
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ListOutputs(const RpcRequest& request)
{
OBSDataArrayAutoRelease outputs = obs_data_array_create();
obs_enum_outputs([](void* param, obs_output_t* output) {
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease outputInfo = getOutputInfo(output);
obs_data_array_push_back(outputs, outputInfo);
return true;
}, outputs);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_array(fields, "outputs", outputs);
return request.success(fields);
}
/**
* Get information about a single output
*
* @param {String} `outputName` Output name
*
* @return {Output} `outputInfo` Output info
*
* @api requests
* @name GetOutputInfo
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::GetOutputInfo(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
OBSDataAutoRelease outputInfo = getOutputInfo(output);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_obj(fields, "outputInfo", outputInfo);
return request.success(fields);
});
}
/**
* Start an output
*
* @param {String} `outputName` Output name
*
* @api requests
* @name StartOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StartOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (obs_output_active(output)) {
return request.failed("output already active");
}
bool success = obs_output_start(output);
if (!success) {
QString lastError = obs_output_get_last_error(output);
QString errorMessage = QString("output start failed: %1").arg(lastError);
return request.failed(errorMessage);
}
return request.success();
});
}
/**
* Stop an output
*
* @param {String} `outputName` Output name
* @param {boolean (optional)} `force` Force stop (default: false)
*
* @api requests
* @name StopOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StopOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (!obs_output_active(output)) {
return request.failed("output not active");
}
bool forceStop = obs_data_get_bool(request.parameters(), "force");
if (forceStop) {
obs_output_force_stop(output);
} else {
obs_output_stop(output);
}
return request.success();
});
}

View File

@ -12,19 +12,19 @@
* @category profiles
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
if (!req->hasField("profile-name")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
if (!request.hasField("profile-name")) {
return request.failed("missing request parameters");
}
QString profileName = obs_data_get_string(req->data, "profile-name");
QString profileName = obs_data_get_string(request.parameters(), "profile-name");
if (profileName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
// TODO : check if profile exists
obs_frontend_set_current_profile(profileName.toUtf8());
return req->SendOKResponse();
return request.success();
}
/**
@ -37,10 +37,12 @@ HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req)
* @category profiles
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
return req->SendOKResponse(response);
char* currentProfile = obs_frontend_get_current_profile();
obs_data_set_string(response, "profile-name", currentProfile);
bfree(currentProfile);
return request.success(response);
}
/**
@ -53,7 +55,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req)
* @category profiles
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) {
char** profiles = obs_frontend_get_profiles();
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
bfree(profiles);
@ -61,5 +63,5 @@ HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "profiles", list);
return req->SendOKResponse(response);
return request.success(response);
}

View File

@ -1,6 +1,17 @@
#include "WSRequestHandler.h"
#include <functional>
#include <util/platform.h>
#include "Utils.h"
#include "WSRequestHandler.h"
RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> callback)
{
if (!obs_frontend_recording_active()) {
return request.failed("recording is not active");
}
return callback();
}
/**
* Toggle recording on or off.
@ -10,13 +21,9 @@
* @category recording
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active())
obs_frontend_recording_stop();
else
obs_frontend_recording_start();
return req->SendOKResponse();
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
return request.success();
}
/**
@ -28,13 +35,13 @@ HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req
* @category recording
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == false) {
obs_frontend_recording_start();
return req->SendOKResponse();
} else {
return req->SendErrorResponse("recording already active");
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
if (obs_frontend_recording_active()) {
return request.failed("recording already active");
}
obs_frontend_recording_start();
return request.success();
}
/**
@ -46,13 +53,53 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
* @category recording
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == true) {
obs_frontend_recording_stop();
return req->SendOKResponse();
} else {
return req->SendErrorResponse("recording not active");
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) {
if (!obs_frontend_recording_active()) {
return request.failed("recording not active");
}
obs_frontend_recording_stop();
return request.success();
}
/**
* Pause the current recording.
* Returns an error if recording is not active or already paused.
*
* @api requests
* @name PauseRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (obs_frontend_recording_paused()) {
return request.failed("recording already paused");
}
obs_frontend_recording_pause(true);
return request.success();
});
}
/**
* Resume/unpause the current recording (if paused).
* Returns an error if recording is not active or not paused.
*
* @api requests
* @name ResumeRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (!obs_frontend_recording_paused()) {
return request.failed("recording is not paused");
}
obs_frontend_recording_pause(false);
return request.success();
});
}
/**
@ -63,7 +110,6 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
* in progress, the change won't be applied immediately and will be
* effective on the next recording.
*
*
* @param {String} `rec-folder` Path of the recording folder.
*
* @api requests
@ -71,18 +117,18 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
* @category recording
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
if (!req->hasField("rec-folder")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
if (!request.hasField("rec-folder")) {
return request.failed("missing request parameters");
}
const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
const char* newRecFolder = obs_data_get_string(request.parameters(), "rec-folder");
bool success = Utils::SetRecordingFolder(newRecFolder);
if (!success) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
return req->SendOKResponse();
return request.success();
}
/**
@ -95,11 +141,11 @@ HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req
* @category recording
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) {
const char* recFolder = Utils::GetRecordingFolder();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "rec-folder", recFolder);
return req->SendOKResponse(response);
return request.success(response);
}

View File

@ -10,13 +10,13 @@
* @category replay buffer
* @since 4.2.0
*/
HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop();
} else {
Utils::StartReplayBuffer();
}
return req->SendOKResponse();
return request.success();
}
/**
@ -31,17 +31,17 @@ HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler*
* @category replay buffer
* @since 4.2.0
*/
HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
if (!Utils::ReplayBufferEnabled()) {
return req->SendErrorResponse("replay buffer disabled in settings");
return request.failed("replay buffer disabled in settings");
}
if (obs_frontend_replay_buffer_active() == true) {
return req->SendErrorResponse("replay buffer already active");
return request.failed("replay buffer already active");
}
Utils::StartReplayBuffer();
return req->SendOKResponse();
return request.success();
}
/**
@ -53,12 +53,12 @@ HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req)
* @category replay buffer
* @since 4.2.0
*/
HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
if (obs_frontend_replay_buffer_active() == true) {
obs_frontend_replay_buffer_stop();
return req->SendOKResponse();
return request.success();
} else {
return req->SendErrorResponse("replay buffer not active");
return request.failed("replay buffer not active");
}
}
@ -72,9 +72,9 @@ HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req)
* @category replay buffer
* @since 4.2.0
*/
HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
if (!obs_frontend_replay_buffer_active()) {
return req->SendErrorResponse("replay buffer not active");
return request.failed("replay buffer not active");
}
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
@ -84,5 +84,5 @@ HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req)
proc_handler_call(ph, "save", &cd);
calldata_free(&cd);
return req->SendOKResponse();
return request.success();
}

View File

@ -12,19 +12,19 @@
* @category scene collections
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
if (!req->hasField("sc-name")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) {
if (!request.hasField("sc-name")) {
return request.failed("missing request parameters");
}
QString sceneCollection = obs_data_get_string(req->data, "sc-name");
QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name");
if (sceneCollection.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
// TODO : Check if specified profile exists and if changing is allowed
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
return req->SendOKResponse();
return request.success();
}
/**
@ -37,12 +37,14 @@ HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandl
* @category scene collections
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "sc-name",
obs_frontend_get_current_scene_collection());
return req->SendOKResponse(response);
char* sceneCollection = obs_frontend_get_current_scene_collection();
obs_data_set_string(response, "sc-name", sceneCollection);
bfree(sceneCollection);
return request.success(response);
}
/**
@ -55,7 +57,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandl
* @category scene collections
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
char** sceneCollections = obs_frontend_get_scene_collections();
OBSDataArrayAutoRelease list =
Utils::StringListToArray(sceneCollections, "sc-name");
@ -64,5 +66,5 @@ HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* r
OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "scene-collections", list);
return req->SendOKResponse(response);
return request.success(response);
}

View File

@ -6,10 +6,13 @@
* Gets the scene specific properties of the specified source item.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
*
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
*
* @return {String} `name` The name of the source.
* @return {String} `name` Scene Item name.
* @return {int} `itemId` Scene Item ID.
* @return {int} `position.x` The x position of the source from the left.
* @return {int} `position.y` The y position of the source from the top.
* @return {int} `position.alignment` The point on the source that the item is manipulated from.
@ -21,6 +24,7 @@
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
* @return {bool} `visible` If the source is visible.
* @return {bool} `muted` If the source is muted.
* @return {bool} `locked` If the source's transform is locked.
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @return {int} `bounds.alignment` Alignment of the bounding box.
@ -30,48 +34,51 @@
* @return {int} `sourceHeight` Base source (without scaling) of the source
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*
* @api requests
* @name GetSceneItemProperties
* @category scene items
* @since 4.3.0
*/
HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
}
OBSData params = request.parameters();
QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
QString sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName);
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
obs_data_set_string(data, "name", itemName.toUtf8());
return req->SendOKResponse(data);
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource));
obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem));
return request.success(data);
}
/**
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
*
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source.
* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
* @param {int (optional)} `position.x` The new x position of the source.
* @param {int (optional)} `position.y` The new y position of the source.
* @param {int (optional)} `position.alignment` The new alignment of the source.
@ -94,82 +101,87 @@ HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler*
* @category scene items
* @since 4.3.0
*/
HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
}
OBSData params = request.parameters();
QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
QString sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName);
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
bool badRequest = false;
OBSDataAutoRelease errorMessage = obs_data_create();
OBSDataAutoRelease errorData = obs_data_create();
obs_sceneitem_defer_update_begin(sceneItem);
if (req->hasField("position")) {
if (request.hasField("position")) {
vec2 oldPosition;
OBSDataAutoRelease positionError = obs_data_create();
obs_sceneitem_get_pos(sceneItem, &oldPosition);
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position");
vec2 newPosition = oldPosition;
if (obs_data_has_user_value(reqPosition, "x")) {
newPosition.x = obs_data_get_int(reqPosition, "x");
}
if (obs_data_has_user_value(reqPosition, "y")) {
newPosition.y = obs_data_get_int(reqPosition, "y");
}
if (obs_data_has_user_value(reqPosition, "alignment")) {
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
if (Utils::IsValidAlignment(alignment)) {
obs_sceneitem_set_alignment(sceneItem, alignment);
}
else {
} else {
badRequest = true;
obs_data_set_string(positionError, "alignment", "invalid");
obs_data_set_obj(errorMessage, "position", positionError);
obs_data_set_obj(errorData, "position", positionError);
}
}
obs_sceneitem_set_pos(sceneItem, &newPosition);
}
if (req->hasField("rotation")) {
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
if (request.hasField("rotation")) {
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation"));
}
if (req->hasField("scale")) {
if (request.hasField("scale")) {
vec2 oldScale;
obs_sceneitem_get_scale(sceneItem, &oldScale);
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
vec2 newScale = oldScale;
OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale");
if (obs_data_has_user_value(reqScale, "x")) {
newScale.x = obs_data_get_double(reqScale, "x");
}
if (obs_data_has_user_value(reqScale, "y")) {
newScale.y = obs_data_get_double(reqScale, "y");
}
obs_sceneitem_set_scale(sceneItem, &newScale);
}
if (req->hasField("crop")) {
if (request.hasField("crop")) {
obs_sceneitem_crop oldCrop;
obs_sceneitem_get_crop(sceneItem, &oldCrop);
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop");
obs_sceneitem_crop newCrop = oldCrop;
if (obs_data_has_user_value(reqCrop, "top")) {
newCrop.top = obs_data_get_int(reqCrop, "top");
}
@ -182,21 +194,23 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
if (obs_data_has_user_value(reqCrop, "left")) {
newCrop.left = obs_data_get_int(reqCrop, "left");
}
obs_sceneitem_set_crop(sceneItem, &newCrop);
}
if (req->hasField("visible")) {
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
if (request.hasField("visible")) {
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible"));
}
if (req->hasField("locked")) {
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(req->data, "locked"));
if (request.hasField("locked")) {
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked"));
}
if (req->hasField("bounds")) {
if (request.hasField("bounds")) {
bool badBounds = false;
OBSDataAutoRelease boundsError = obs_data_create();
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds");
if (obs_data_has_user_value(reqBounds, "type")) {
QString newBoundsType = obs_data_get_string(reqBounds, "type");
if (newBoundsType == "OBS_BOUNDS_NONE") {
@ -225,16 +239,20 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
obs_data_set_string(boundsError, "type", "invalid");
}
}
vec2 oldBounds;
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
vec2 newBounds = oldBounds;
if (obs_data_has_user_value(reqBounds, "x")) {
newBounds.x = obs_data_get_double(reqBounds, "x");
}
if (obs_data_has_user_value(reqBounds, "y")) {
newBounds.y = obs_data_get_double(reqBounds, "y");
}
obs_sceneitem_set_bounds(sceneItem, &newBounds);
if (obs_data_has_user_value(reqBounds, "alignment")) {
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
if (Utils::IsValidAlignment(bounds_alignment)) {
@ -245,52 +263,51 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
obs_data_set_string(boundsError, "alignment", "invalid");
}
}
if (badBounds) {
obs_data_set_obj(errorMessage, "bounds", boundsError);
obs_data_set_obj(errorData, "bounds", boundsError);
}
}
obs_sceneitem_defer_update_end(sceneItem);
if (badRequest) {
return req->SendErrorResponse(errorMessage);
return request.failed("error", errorData);
}
return req->SendOKResponse();
return request.success();
}
/**
* Reset a scene item.
*
* @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.
* @param {String} `item` Name of the source item.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
*
* @api requests
* @name ResetSceneItem
* @category scene items
* @since 4.2.0
*/
HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
// TODO: remove this request, or refactor it to ResetSource
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
const char* itemName = obs_data_get_string(req->data, "item");
if (!itemName) {
return req->SendErrorResponse("invalid request parameters");
}
OBSData params = request.parameters();
const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
const char* sceneName = obs_data_get_string(params, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
@ -298,15 +315,15 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
obs_source_update(sceneItemSource, settings);
return req->SendOKResponse();
return request.success();
}
/**
* Show or hide a specified source item in a specified scene.
*
* @param {String} `source` Scene item name in the specified scene.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene.
* @param {String} `source` Scene Item name.
* @param {boolean} `render` true = shown ; false = hidden
* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene.
*
* @api requests
* @name SetSceneItemRender
@ -314,41 +331,41 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
* @since 0.3
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
if (!req->hasField("source") ||
!req->hasField("render"))
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
if (!request.hasField("source") ||
!request.hasField("render"))
{
return req->SendErrorResponse("missing request parameters");
return request.failed("missing request parameters");
}
const char* itemName = obs_data_get_string(req->data, "source");
bool isVisible = obs_data_get_bool(req->data, "render");
const char* itemName = obs_data_get_string(request.parameters(), "source");
bool isVisible = obs_data_get_bool(request.parameters(), "render");
if (!itemName) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
obs_sceneitem_set_visible(sceneItem, isVisible);
return req->SendOKResponse();
return request.success();
}
/**
* Sets the coordinates of a specified source item.
*
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source item.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {double} `x` X coordinate.
* @param {double} `y` Y coordinate.
@ -359,41 +376,41 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req
* @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
if (!req->hasField("item") ||
!req->hasField("x") || !req->hasField("y")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
if (!request.hasField("item") ||
!request.hasField("x") || !request.hasField("y")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(req->data, "item");
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene could not be found");
return request.failed("requested scene could not be found");
}
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
vec2 item_position = { 0 };
item_position.x = obs_data_get_double(req->data, "x");
item_position.y = obs_data_get_double(req->data, "y");
item_position.x = obs_data_get_double(request.parameters(), "x");
item_position.y = obs_data_get_double(request.parameters(), "y");
obs_sceneitem_set_pos(sceneItem, &item_position);
return req->SendOKResponse();
return request.success();
}
/**
* Set the transform of the specified source item.
*
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source item.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {double} `x-scale` Width scale factor.
* @param {double} `y-scale` Height scale factor.
* @param {double} `rotation` Source item rotation (in degrees).
@ -404,34 +421,34 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* r
* @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
if (!req->hasField("item") ||
!req->hasField("x-scale") ||
!req->hasField("y-scale") ||
!req->hasField("rotation"))
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
if (!request.hasField("item") ||
!request.hasField("x-scale") ||
!request.hasField("y-scale") ||
!request.hasField("rotation"))
{
return req->SendErrorResponse("missing request parameters");
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(req->data, "item");
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
vec2 scale;
scale.x = obs_data_get_double(req->data, "x-scale");
scale.y = obs_data_get_double(req->data, "y-scale");
float rotation = obs_data_get_double(req->data, "rotation");
scale.x = obs_data_get_double(request.parameters(), "x-scale");
scale.y = obs_data_get_double(request.parameters(), "y-scale");
float rotation = obs_data_get_double(request.parameters(), "rotation");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
obs_sceneitem_defer_update_begin(sceneItem);
@ -441,14 +458,14 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
obs_sceneitem_defer_update_end(sceneItem);
return req->SendOKResponse();
return request.success();
}
/**
* Sets the crop coordinates of the specified source item.
*
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` The name of the source.
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name.
* @param {int} `top` Pixel position of the top of the source item.
* @param {int} `bottom` Pixel position of the bottom of the source item.
* @param {int} `left` Pixel position of the left of the source item.
@ -460,83 +477,71 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
* @since 4.1.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/
HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
QString itemName = obs_data_get_string(req->data, "item");
QString itemName = obs_data_get_string(request.parameters(), "item");
if (itemName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) {
return req->SendErrorResponse("specified scene item doesn't exist");
return request.failed("specified scene item doesn't exist");
}
struct obs_sceneitem_crop crop = { 0 };
crop.top = obs_data_get_int(req->data, "top");
crop.bottom = obs_data_get_int(req->data, "bottom");
crop.left = obs_data_get_int(req->data, "left");
crop.right = obs_data_get_int(req->data, "right");
crop.top = obs_data_get_int(request.parameters(), "top");
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
crop.left = obs_data_get_int(request.parameters(), "left");
crop.right = obs_data_get_int(request.parameters(), "right");
obs_sceneitem_set_crop(sceneItem, &crop);
return req->SendOKResponse();
return request.success();
}
/**
* Deletes a scene item.
*
* @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.
* @param {Object} `item` item to delete (required)
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` id of the scene item.
* @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.
* @param {Object} `item` Scene item to delete (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID.
*
* @api requests
* @name DeleteSceneItem
* @category scene items
* @since 4.5.0
*/
HandlerResponse WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
const char* sceneName = obs_data_get_string(req->data, "scene");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
const char* sceneName = obs_data_get_string(request.parameters(), "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
if (!sceneItem) {
return req->SendErrorResponse("item with id/name combination not found in specified scene");
return request.failed("item with id/name combination not found in specified scene");
}
obs_sceneitem_remove(sceneItem);
return req->SendOKResponse();
}
struct DuplicateSceneItemData {
obs_sceneitem_t *referenceItem;
obs_source_t *fromSource;
obs_sceneitem_t *newItem;
};
static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
DuplicateSceneItemData *data = (DuplicateSceneItemData *)_data;
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
return request.success();
}
/**
@ -544,9 +549,9 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
*
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
* @param {Object} `item` item to duplicate (required)
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` id of the scene item.
* @param {Object} `item` Scene Item to duplicate from the source scene (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID.
*
* @return {String} `scene` Name of the scene where the new item was created
* @return {Object} `item` New item info
@ -558,27 +563,33 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
* @category scene items
* @since 4.5.0
*/
HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
if (!req->hasField("item")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
struct DuplicateSceneItemData {
obs_sceneitem_t *referenceItem;
obs_source_t *fromSource;
obs_sceneitem_t *newItem;
};
if (!request.hasField("item")) {
return request.failed("missing request parameters");
}
const char* fromSceneName = obs_data_get_string(req->data, "fromScene");
OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene");
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
if (!fromScene) {
return req->SendErrorResponse("requested fromScene doesn't exist");
return request.failed("requested fromScene doesn't exist");
}
const char* toSceneName = obs_data_get_string(req->data, "toScene");
OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene");
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
if (!toScene) {
return req->SendErrorResponse("requested toScene doesn't exist");
return request.failed("requested toScene doesn't exist");
}
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField);
if (!referenceItem) {
return req->SendErrorResponse("item with id/name combination not found in specified scene");
return request.failed("item with id/name combination not found in specified scene");
}
DuplicateSceneItemData data;
@ -586,12 +597,16 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
data.referenceItem = referenceItem;
obs_enter_graphics();
obs_scene_atomic_update(obs_scene_from_source(toScene), DuplicateSceneItem, &data);
obs_scene_atomic_update(toScene, [](void *_data, obs_scene_t *scene) {
auto data = reinterpret_cast<DuplicateSceneItemData*>(_data);
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
}, &data);
obs_leave_graphics();
obs_sceneitem_t *newItem = data.newItem;
if (!newItem) {
return req->SendErrorResponse("Error duplicating scene item");
return request.failed("Error duplicating scene item");
}
OBSDataAutoRelease itemData = obs_data_create();
@ -600,7 +615,7 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
OBSDataAutoRelease responseData = obs_data_create();
obs_data_set_obj(responseData, "item", itemData);
obs_data_set_string(responseData, "scene", obs_source_get_name(toScene));
obs_data_set_string(responseData, "scene", obs_source_get_name(obs_scene_get_source(toScene)));
return req->SendOKResponse(responseData);
return request.success(responseData);
}

View File

@ -18,19 +18,19 @@
* @category scenes
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
if (!req->hasField("scene-name")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) {
if (!request.hasField("scene-name")) {
return request.failed("missing request parameters");
}
const char* sceneName = obs_data_get_string(req->data, "scene-name");
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
if (source) {
obs_frontend_set_current_scene(source);
return req->SendOKResponse();
return request.success();
} else {
return req->SendErrorResponse("requested scene does not exist");
return request.failed("requested scene does not exist");
}
}
@ -45,7 +45,7 @@ HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
* @category scenes
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
@ -53,21 +53,21 @@ HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
obs_data_set_array(data, "sources", sceneItems);
return req->SendOKResponse(data);
return request.success(data);
}
/**
* Get a list of scenes in the currently active profile.
*
* @return {String} `current-scene` Name of the currently active scene.
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information).
*
* @api requests
* @name GetSceneList
* @category scenes
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
@ -76,7 +76,7 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
obs_source_get_name(currentScene));
obs_data_set_array(data, "scenes", scenes);
return req->SendOKResponse(data);
return request.success(data);
}
/**
@ -92,50 +92,184 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
* @category scenes
* @since 4.5.0
*/
HandlerResponse WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
QString sceneName = obs_data_get_string(req->data, "scene");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) {
QString sceneName = obs_data_get_string(request.parameters(), "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) {
return req->SendErrorResponse("requested scene doesn't exist");
return request.failed("requested scene doesn't exist");
}
OBSDataArrayAutoRelease items = obs_data_get_array(req->data, "items");
OBSDataArrayAutoRelease items = obs_data_get_array(request.parameters(), "items");
if (!items) {
return req->SendErrorResponse("sceneItem order not specified");
return request.failed("sceneItem order not specified");
}
size_t count = obs_data_array_count(items);
struct reorder_context {
obs_data_array_t* items;
bool success;
QString errorMessage;
};
std::vector<obs_sceneitem_t*> newOrder;
newOrder.reserve(count);
struct reorder_context ctx;
ctx.success = false;
ctx.items = items;
for (size_t i = 0; i < count; ++i) {
OBSDataAutoRelease item = obs_data_array_item(items, i);
obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) {
auto ctx = reinterpret_cast<struct reorder_context*>(param);
QVector<struct obs_sceneitem_order_info> orderList;
struct obs_sceneitem_order_info info;
size_t itemCount = obs_data_array_count(ctx->items);
for (uint i = 0; i < itemCount; i++) {
OBSDataAutoRelease item = obs_data_array_item(ctx->items, i);
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
obs_sceneitem_release(sceneItem); // ref dec
if (!sceneItem) {
return req->SendErrorResponse("Invalid sceneItem id or name specified");
ctx->success = false;
ctx->errorMessage = "Invalid sceneItem id or name specified";
return;
}
for (size_t j = 0; j <= i; ++j) {
if (sceneItem == newOrder[j]) {
return req->SendErrorResponse("Duplicate sceneItem in specified order");
}
info.group = nullptr;
info.item = sceneItem;
orderList.insert(0, info);
}
newOrder.push_back(sceneItem);
ctx->success = obs_scene_reorder_items2(scene, orderList.data(), orderList.size());
if (!ctx->success) {
ctx->errorMessage = "Invalid sceneItem order";
}
}, &ctx);
if (!ctx.success) {
return request.failed(ctx.errorMessage);
}
bool success = obs_scene_reorder_items(obs_scene_from_source(scene), newOrder.data(), count);
if (!success) {
return req->SendErrorResponse("Invalid sceneItem order");
}
for (auto const& item: newOrder) {
obs_sceneitem_release(item);
}
return req->SendOKResponse();
return request.success();
}
/**
* Set a scene to use a specific transition override.
*
* @param {String} `sceneName` Name of the scene to switch to.
* @param {String} `transitionName` Name of the transition to use.
* @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given.
*
* @api requests
* @name SetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName") || !request.hasField("transitionName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
QString transitionName = obs_data_get_string(request.parameters(), "transitionName");
if (!Utils::GetTransitionFromName(transitionName)) {
return request.failed("requested transition does not exist");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData());
if (request.hasField("transitionDuration")) {
int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration");
obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration);
} else if(!obs_data_has_user_value(sourceData, "transition_duration")) {
obs_data_set_int(sourceData, "transition_duration",
obs_frontend_get_transition_duration()
);
}
return request.success();
}
/**
* Remove any transition override on a scene.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @api requests
* @name RemoveSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_erase(sourceData, "transition");
obs_data_erase(sourceData, "transition_duration");
return request.success();
}
/**
* Get the current scene transition override.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set.
* @return {int} `transitionDuration` Transition duration. `-1` if no override is set.
*
* @api requests
* @name GetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
const char* transitionOverrideName = obs_data_get_string(sourceData, "transition");
bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration");
int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration");
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "transitionName", transitionOverrideName);
obs_data_set_int(fields, "transitionDuration",
(hasDurationOverride ? transitionOverrideDuration : -1)
);
return request.success(fields);
}

File diff suppressed because it is too large Load Diff

View File

@ -20,28 +20,26 @@
* @category streaming
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
auto events = GetEventsSystem();
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
obs_data_set_bool(data, "preview-only", false);
const char* tc = nullptr;
if (obs_frontend_streaming_active()) {
tc = events->GetStreamingTimecode();
obs_data_set_string(data, "stream-timecode", tc);
bfree((void*)tc);
QString streamingTimecode = events->getStreamingTimecode();
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData());
}
if (obs_frontend_recording_active()) {
tc = events->GetRecordingTimecode();
obs_data_set_string(data, "rec-timecode", tc);
bfree((void*)tc);
QString recordingTimecode = events->getRecordingTimecode();
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData());
}
return req->SendOKResponse(data);
return request.success(data);
}
/**
@ -52,11 +50,11 @@ HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req
* @category streaming
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active())
return HandleStopStreaming(req);
return StopStreaming(request);
else
return HandleStartStreaming(req);
return StartStreaming(request);
}
/**
@ -69,24 +67,24 @@ HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req
* @param {Object (optional)} `stream.settings` Settings for the stream.
* @param {String (optional)} `stream.settings.server` The publish URL.
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
* @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`.
* @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`.
*
* @api requests
* @name StartStreaming
* @category streaming
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active() == false) {
OBSService configuredService = obs_frontend_get_streaming_service();
OBSService newService = nullptr;
// TODO: fix service memory leak
if (req->hasField("stream")) {
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
if (request.hasField("stream")) {
OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream");
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
@ -105,10 +103,10 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
&& obs_data_has_user_value(newSettings, "key"))
{
const char* key = obs_data_get_string(newSettings, "key");
int keylen = strlen(key);
size_t keylen = strlen(key);
bool hasQuestionMark = false;
for (int i = 0; i < keylen; i++) {
for (size_t i = 0; i < keylen; i++) {
if (key[i] == '?') {
hasQuestionMark = true;
break;
@ -159,9 +157,9 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
obs_frontend_set_streaming_service(configuredService);
}
return req->SendOKResponse();
return request.success();
} else {
return req->SendErrorResponse("streaming already active");
return request.failed("streaming already active");
}
}
@ -174,12 +172,12 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
* @category streaming
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
if (obs_frontend_streaming_active() == true) {
obs_frontend_streaming_stop();
return req->SendOKResponse();
return request.success();
} else {
return req->SendErrorResponse("streaming not active");
return request.failed("streaming not active");
}
}
@ -190,7 +188,7 @@ HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
* @param {Object} `settings` The actual settings of the stream.
* @param {String (optional)} `settings.server` The publish URL.
* @param {String (optional)} `settings.key` The publish key.
* @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `settings.username` The username for the streaming service.
* @param {String (optional)} `settings.password` The password for the streaming service.
* @param {boolean} `save` Persist the settings to disk.
@ -200,21 +198,22 @@ HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
* @category streaming
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
OBSService service = obs_frontend_get_streaming_service();
OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
OBSDataAutoRelease requestSettings = obs_data_get_obj(request.parameters(), "settings");
if (!requestSettings) {
return req->SendErrorResponse("'settings' are required'");
return request.failed("'settings' are required'");
}
QString serviceType = obs_service_get_type(service);
QString requestedType = obs_data_get_string(req->data, "type");
QString requestedType = obs_data_get_string(request.parameters(), "type");
if (requestedType != nullptr && requestedType != serviceType) {
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
service = obs_service_create(
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
obs_frontend_set_streaming_service(service);
} else {
// If type isn't changing, we should overlay the settings we got
// to the existing settings. By doing so, you can send a request that
@ -233,17 +232,19 @@ HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
}
//if save is specified we should immediately save the streaming service
if (obs_data_get_bool(req->data, "save")) {
if (obs_data_get_bool(request.parameters(), "save")) {
obs_frontend_save_streaming_service();
}
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
OBSService responseService = obs_frontend_get_streaming_service();
OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService);
const char* responseType = obs_service_get_type(responseService);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", requestedType.toUtf8());
obs_data_set_string(response, "type", responseType);
obs_data_set_obj(response, "settings", serviceSettings);
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -253,16 +254,16 @@ HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
* @return {Object} `settings` Stream settings object.
* @return {String} `settings.server` The publish URL.
* @return {String} `settings.key` The publish key of the stream.
* @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`.
* @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`.
*
* @api requests
* @name GetStreamSettings
* @category streaming
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
OBSService service = obs_frontend_get_streaming_service();
const char* serviceType = obs_service_get_type(service);
@ -272,7 +273,7 @@ HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
obs_data_set_string(response, "type", serviceType);
obs_data_set_obj(response, "settings", settings);
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -283,9 +284,9 @@ HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
* @category streaming
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) {
obs_frontend_save_streaming_service();
return req->SendOKResponse();
return request.success();
}
@ -301,18 +302,19 @@ HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req
* @since 4.6.0
*/
#if BUILD_CAPTIONS
HandlerResponse WSRequestHandler::HandleSendCaptions(WSRequestHandler* req) {
if (!req->hasField("text")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) {
if (!request.hasField("text")) {
return request.failed("missing request parameters");
}
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
if (output) {
const char* caption = obs_data_get_string(req->data, "text");
obs_output_output_caption_text1(output, caption);
const char* caption = obs_data_get_string(request.parameters(), "text");
// Send caption text with immediately (0 second delay)
obs_output_output_caption_text2(output, caption, 0.0);
}
return req->SendOKResponse();
return request.success();
}
#endif

View File

@ -12,13 +12,13 @@
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) {
bool previewActive = obs_frontend_preview_program_mode_active();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "studio-mode", previewActive);
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -33,9 +33,9 @@ HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* re
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return req->SendErrorResponse("studio mode not enabled");
return request.failed("studio mode not enabled");
}
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
@ -45,7 +45,7 @@ HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
obs_data_set_string(data, "name", obs_source_get_name(scene));
obs_data_set_array(data, "sources", sceneItems);
return req->SendOKResponse(data);
return request.success(data);
}
/**
@ -59,23 +59,23 @@ HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return req->SendErrorResponse("studio mode not enabled");
return request.failed("studio mode not enabled");
}
if (!req->hasField("scene-name")) {
return req->SendErrorResponse("missing request parameters");
if (!request.hasField("scene-name")) {
return request.failed("missing request parameters");
}
const char* scene_name = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
const char* scene_name = obs_data_get_string(request.parameters(), "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name);
if (!scene) {
return req->SendErrorResponse("specified scene doesn't exist");
return request.failed("specified scene doesn't exist");
}
obs_frontend_set_current_preview_scene(scene);
return req->SendOKResponse();
obs_frontend_set_current_preview_scene(obs_scene_get_source(scene));
return request.success();
}
/**
@ -91,37 +91,37 @@ HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return req->SendErrorResponse("studio mode not enabled");
return request.failed("studio mode not enabled");
}
if (req->hasField("with-transition")) {
if (request.hasField("with-transition")) {
OBSDataAutoRelease transitionInfo =
obs_data_get_obj(req->data, "with-transition");
obs_data_get_obj(request.parameters(), "with-transition");
if (obs_data_has_user_value(transitionInfo, "name")) {
QString transitionName =
obs_data_get_string(transitionInfo, "name");
if (transitionName.isEmpty()) {
return req->SendErrorResponse("invalid request parameters");
return request.failed("invalid request parameters");
}
bool success = Utils::SetTransitionByName(transitionName);
if (!success) {
return req->SendErrorResponse("specified transition doesn't exist");
return request.failed("specified transition doesn't exist");
}
}
if (obs_data_has_user_value(transitionInfo, "duration")) {
int transitionDuration =
obs_data_get_int(transitionInfo, "duration");
Utils::SetTransitionDuration(transitionDuration);
obs_frontend_set_transition_duration(transitionDuration);
}
}
Utils::TransitionToProgram();
return req->SendOKResponse();
obs_frontend_preview_program_trigger_transition();
return request.success();
}
/**
@ -132,9 +132,16 @@ HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* re
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
if (obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode already active");
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(true);
return req->SendOKResponse();
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}
/**
@ -145,9 +152,17 @@ HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req)
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not active");
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(false);
return req->SendOKResponse();
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}
/**
@ -158,8 +173,13 @@ HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req)
* @category studio mode
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
bool previewProgramMode = obs_frontend_preview_program_mode_active();
obs_frontend_set_preview_program_mode(!previewProgramMode);
return req->SendOKResponse();
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
}

View File

@ -14,7 +14,7 @@
* @category transitions
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
@ -34,7 +34,7 @@ HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req)
obs_source_get_name(currentTransition));
obs_data_set_array(response, "transitions", transitions);
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -48,7 +48,7 @@ HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req)
* @category transitions
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create();
@ -56,9 +56,9 @@ HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* r
obs_source_get_name(currentTransition));
if (!obs_transition_fixed(currentTransition))
obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
obs_data_set_int(response, "duration", obs_frontend_get_transition_duration());
return req->SendOKResponse(response);
return request.success(response);
}
/**
@ -71,18 +71,18 @@ HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* r
* @category transitions
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
if (!req->hasField("transition-name")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) {
if (!request.hasField("transition-name")) {
return request.failed("missing request parameters");
}
QString name = obs_data_get_string(req->data, "transition-name");
QString name = obs_data_get_string(request.parameters(), "transition-name");
bool success = Utils::SetTransitionByName(name);
if (!success) {
return req->SendErrorResponse("requested transition does not exist");
return request.failed("requested transition does not exist");
}
return req->SendOKResponse();
return request.success();
}
/**
@ -95,14 +95,14 @@ HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* r
* @category transitions
* @since 4.0.0
*/
HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
if (!req->hasField("duration")) {
return req->SendErrorResponse("missing request parameters");
RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) {
if (!request.hasField("duration")) {
return request.failed("missing request parameters");
}
int ms = obs_data_get_int(req->data, "duration");
Utils::SetTransitionDuration(ms);
return req->SendOKResponse();
int ms = obs_data_get_int(request.parameters(), "duration");
obs_frontend_set_transition_duration(ms);
return request.success();
}
/**
@ -115,8 +115,27 @@ HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler*
* @category transitions
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "transition-duration", Utils::GetTransitionDuration());
return req->SendOKResponse(response);
obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration());
return request.success(response);
}
/**
* Get the position of the current transition.
*
* @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.
*
* @api requests
* @name GetTransitionPosition
* @category transitions
* @since 4.8.0
*/
RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_double(response, "position", obs_transition_get_time(currentTransition));
return request.success(response);
}

View File

@ -31,6 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include "protocol/OBSRemoteProtocol.h"
QT_USE_NAMESPACE
@ -43,6 +44,7 @@ WSServer::WSServer()
_connections(),
_clMutex(QMutex::Recursive)
{
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control);
_server.init_asio();
#ifndef _WIN32
_server.set_reuse_addr(true);
@ -82,7 +84,7 @@ void WSServer::start(quint16 port)
obs_frontend_push_ui_translation(obs_module_get_string);
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title");
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort);
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str());
obs_frontend_pop_ui_translation();
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
@ -124,8 +126,15 @@ void WSServer::stop()
blog(LOG_INFO, "server stopped successfully");
}
void WSServer::broadcast(std::string message)
void WSServer::broadcast(const RpcEvent& event)
{
OBSRemoteProtocol protocol;
std::string message = protocol.encodeEvent(event);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Update << '%s'", message.c_str());
}
QMutexLocker locker(&_clMutex);
for (connection_hdl hdl : _connections) {
if (GetConfig()->AuthRequired) {
@ -134,7 +143,15 @@ void WSServer::broadcast(std::string message)
continue;
}
}
_server.send(hdl, message, websocketpp::frame::opcode::text);
websocketpp::lib::error_code errorCode;
_server.send(hdl, message, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(broadcast): send failed: %s",
errorCodeMessage.c_str());
}
}
}
@ -163,10 +180,26 @@ void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
ConnectionProperties& connProperties = _connectionProperties[hdl];
locker.unlock();
WSRequestHandler handler(connProperties);
std::string response = handler.processIncomingMessage(payload);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Request >> '%s'", payload.c_str());
}
_server.send(hdl, response, websocketpp::frame::opcode::text);
WSRequestHandler requestHandler(connProperties);
OBSRemoteProtocol protocol;
std::string response = protocol.processMessage(requestHandler, payload);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Response << '%s'", response.c_str());
}
websocketpp::lib::error_code errorCode;
_server.send(hdl, response, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(response): send failed: %s",
errorCodeMessage.c_str());
}
});
}

View File

@ -30,11 +30,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <websocketpp/server.hpp>
#include "ConnectionProperties.h"
#include "WSRequestHandler.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
#include "rpc/RpcEvent.h"
using websocketpp::connection_hdl;
@ -49,7 +46,7 @@ public:
virtual ~WSServer();
void start(quint16 port);
void stop();
void broadcast(std::string message);
void broadcast(const RpcEvent& event);
QThreadPool* threadPool() {
return &_threadPool;
}

View File

@ -18,6 +18,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h>
#include <obs-frontend-api.h>
#include <obs-data.h>
#include <QtCore/QTimer>
#include <QtWidgets/QAction>
@ -35,6 +36,11 @@ void ___data_dummy_addref(obs_data_t*) {}
void ___data_array_dummy_addref(obs_data_array_t*) {}
void ___output_dummy_addref(obs_output_t*) {}
void ___data_item_dummy_addref(obs_data_item_t*) {}
void ___data_item_release(obs_data_item_t* dataItem) {
obs_data_item_release(&dataItem);
}
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
@ -55,10 +61,6 @@ bool obs_module_load(void) {
_server = WSServerPtr(new WSServer());
_eventsSystem = WSEventsPtr(new WSEvents(_server));
if (_config->ServerEnabled) {
_server->start(_config->ServerPort);
}
// UI setup
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
@ -75,6 +77,17 @@ bool obs_module_load(void) {
settingsDialog->ToggleShowHide();
});
// Setup event handler to start the server once OBS is ready
auto eventCallback = [](enum obs_frontend_event event, void *param) {
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
if (_config->ServerEnabled) {
_server->start(_config->ServerPort);
}
obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr);
}
};
obs_frontend_add_event_callback(eventCallback, (void*)(obs_frontend_event_cb)eventCallback);
// Loading finished
blog(LOG_INFO, "module loaded!");

View File

@ -38,6 +38,11 @@ using OBSDataArrayAutoRelease =
using OBSOutputAutoRelease =
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
void ___data_item_dummy_addref(obs_data_item_t*);
void ___data_item_release(obs_data_item_t*);
using OBSDataItemAutoRelease =
OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
class Config;
typedef std::shared_ptr<Config> ConfigPtr;
@ -51,6 +56,6 @@ ConfigPtr GetConfig();
WSServerPtr GetServer();
WSEventsPtr GetEventsSystem();
#define OBS_WEBSOCKET_VERSION "4.6.0"
#define OBS_WEBSOCKET_VERSION "4.8.0"
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)

View File

@ -0,0 +1,119 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#include <inttypes.h>
#include "OBSRemoteProtocol.h"
#include "../WSRequestHandler.h"
#include "../rpc/RpcEvent.h"
#include "../Utils.h"
std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, std::string message)
{
std::string msgContainer(message);
const char* msg = msgContainer.c_str();
OBSDataAutoRelease data = obs_data_create_from_json(msg);
if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return errorResponse(QString::Null(), "invalid JSON payload");
}
if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) {
return errorResponse(QString::Null(), "missing request parameters");
}
QString methodName = obs_data_get_string(data, "request-type");
QString messageId = obs_data_get_string(data, "message-id");
OBSDataAutoRelease params = obs_data_create();
obs_data_apply(params, data);
obs_data_unset_user_value(params, "request-type");
obs_data_unset_user_value(params, "message-id");
RpcRequest request(messageId, methodName, params);
RpcResponse response = requestHandler.processRequest(request);
OBSData additionalFields = response.additionalFields();
switch (response.status()) {
case RpcResponse::Status::Ok:
return successResponse(messageId, additionalFields);
case RpcResponse::Status::Error:
return errorResponse(messageId, response.errorMessage(), additionalFields);
}
return std::string();
}
std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
{
OBSDataAutoRelease eventData = obs_data_create();
QString updateType = event.updateType();
obs_data_set_string(eventData, "update-type", updateType.toUtf8().constData());
std::optional<uint64_t> streamTime = event.streamTime();
if (streamTime.has_value()) {
QString streamingTimecode = Utils::nsToTimestamp(streamTime.value());
obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData());
}
std::optional<uint64_t> recordingTime = event.recordingTime();
if (recordingTime.has_value()) {
QString recordingTimecode = Utils::nsToTimestamp(recordingTime.value());
obs_data_set_string(eventData, "rec-timecode", recordingTimecode.toUtf8().constData());
}
OBSData additionalFields = event.additionalFields();
if (additionalFields) {
obs_data_apply(eventData, additionalFields);
}
return std::string(obs_data_get_json(eventData));
}
std::string OBSRemoteProtocol::buildResponse(QString messageId, QString status, obs_data_t* fields)
{
OBSDataAutoRelease response = obs_data_create();
if (!messageId.isNull()) {
obs_data_set_string(response, "message-id", messageId.toUtf8().constData());
}
obs_data_set_string(response, "status", status.toUtf8().constData());
if (fields) {
obs_data_apply(response, fields);
}
std::string responseString = obs_data_get_json(response);
return responseString;
}
std::string OBSRemoteProtocol::successResponse(QString messageId, obs_data_t* fields)
{
return buildResponse(messageId, "ok", fields);
}
std::string OBSRemoteProtocol::errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields)
{
OBSDataAutoRelease fields = obs_data_create();
if (additionalFields) {
obs_data_apply(fields, additionalFields);
}
obs_data_set_string(fields, "error", errorMessage.toUtf8().constData());
return buildResponse(messageId, "error", fields);
}

View File

@ -0,0 +1,38 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#pragma once
#include <string>
#include <obs-data.h>
#include <QtCore/QString>
class WSRequestHandler;
class RpcEvent;
class OBSRemoteProtocol
{
public:
std::string processMessage(WSRequestHandler& requestHandler, std::string message);
std::string encodeEvent(const RpcEvent& event);
private:
std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr);
std::string successResponse(QString messageId, obs_data_t* fields = nullptr);
std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr);
};

35
src/rpc/RpcEvent.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
obs-websocket
Copyright (C) 2016-2020 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/>
*/
#include "RpcEvent.h"
RpcEvent::RpcEvent(
const QString& updateType,
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
obs_data_t* additionalFields
) :
_updateType(updateType),
_streamTime(streamTime),
_recordingTime(recordingTime),
_additionalFields(nullptr)
{
if (additionalFields) {
_additionalFields = obs_data_create();
obs_data_apply(_additionalFields, additionalFields);
}
}

61
src/rpc/RpcEvent.h Normal file
View File

@ -0,0 +1,61 @@
/*
obs-websocket
Copyright (C) 2016-2020 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/>
*/
#pragma once
#include <optional>
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
class RpcEvent
{
public:
explicit RpcEvent(
const QString& updateType,
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
obs_data_t* additionalFields = nullptr
);
const QString& updateType() const
{
return _updateType;
}
const std::optional<uint64_t> streamTime() const
{
return _streamTime;
}
const std::optional<uint64_t> recordingTime() const
{
return _recordingTime;
}
const OBSData additionalFields() const
{
return OBSData(_additionalFields);
}
private:
QString _updateType;
std::optional<uint64_t> _streamTime;
std::optional<uint64_t> _recordingTime;
OBSDataAutoRelease _additionalFields;
};

104
src/rpc/RpcRequest.cpp Normal file
View File

@ -0,0 +1,104 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#include "RpcRequest.h"
#include "RpcResponse.h"
RpcRequest::RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params) :
_messageId(messageId),
_methodName(methodName),
_parameters(nullptr)
{
if (params) {
_parameters = obs_data_create();
obs_data_apply(_parameters, params);
}
}
const RpcResponse RpcRequest::success(obs_data_t* additionalFields) const
{
return RpcResponse::ok(*this, additionalFields);
}
const RpcResponse RpcRequest::failed(const QString& errorMessage, obs_data_t* additionalFields) const
{
return RpcResponse::fail(*this, errorMessage, additionalFields);
}
const bool RpcRequest::hasField(QString name, obs_data_type expectedFieldType, obs_data_number_type expectedNumberType) const
{
if (!_parameters || name.isEmpty() || name.isNull()) {
return false;
}
OBSDataItemAutoRelease dataItem = obs_data_item_byname(_parameters, name.toUtf8());
if (!dataItem) {
return false;
}
if (expectedFieldType != OBS_DATA_NULL) {
obs_data_type fieldType = obs_data_item_gettype(dataItem);
if (fieldType != expectedFieldType) {
return false;
}
if (fieldType == OBS_DATA_NUMBER && expectedNumberType != OBS_DATA_NUM_INVALID) {
obs_data_number_type numberType = obs_data_item_numtype(dataItem);
if (numberType != expectedNumberType) {
return false;
}
}
}
return true;
}
const bool RpcRequest::hasBool(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_BOOLEAN);
}
const bool RpcRequest::hasString(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_STRING);
}
const bool RpcRequest::hasNumber(QString fieldName, obs_data_number_type expectedNumberType) const
{
return this->hasField(fieldName, OBS_DATA_NUMBER, expectedNumberType);
}
const bool RpcRequest::hasInteger(QString fieldName) const
{
return this->hasNumber(fieldName, OBS_DATA_NUM_INT);
}
const bool RpcRequest::hasDouble(QString fieldName) const
{
return this->hasNumber(fieldName, OBS_DATA_NUM_DOUBLE);
}
const bool RpcRequest::hasArray(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_ARRAY);
}
const bool RpcRequest::hasObject(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_OBJECT);
}

65
src/rpc/RpcRequest.h Normal file
View File

@ -0,0 +1,65 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#pragma once
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
// forward declarations
class RpcResponse;
class RpcRequest
{
public:
explicit RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params);
const QString& messageId() const
{
return _messageId;
}
const QString& methodName() const
{
return _methodName;
}
const OBSData parameters() const
{
return OBSData(_parameters);
}
const RpcResponse success(obs_data_t* additionalFields = nullptr) const;
const RpcResponse failed(const QString& errorMessage, obs_data_t* additionalFields = nullptr) const;
const bool hasField(QString fieldName, obs_data_type expectedFieldType = OBS_DATA_NULL,
obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
const bool hasBool(QString fieldName) const;
const bool hasString(QString fieldName) const;
const bool hasNumber(QString fieldName, obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
const bool hasInteger(QString fieldName) const;
const bool hasDouble(QString fieldName) const;
const bool hasArray(QString fieldName) const;
const bool hasObject(QString fieldName) const;
private:
const QString _messageId;
const QString _methodName;
OBSDataAutoRelease _parameters;
};

48
src/rpc/RpcResponse.cpp Normal file
View File

@ -0,0 +1,48 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#include "RpcResponse.h"
#include "RpcRequest.h"
RpcResponse::RpcResponse(
Status status, const QString& messageId,
const QString& methodName, obs_data_t* additionalFields
) :
_status(status),
_messageId(messageId),
_methodName(methodName),
_additionalFields(nullptr)
{
if (additionalFields) {
_additionalFields = obs_data_create();
obs_data_apply(_additionalFields, additionalFields);
}
}
const RpcResponse RpcResponse::ok(const RpcRequest& request, obs_data_t* additionalFields)
{
RpcResponse response(Status::Ok, request.messageId(), request.methodName(), additionalFields);
return response;
}
const RpcResponse RpcResponse::fail(const RpcRequest& request, const QString& errorMessage, obs_data_t* additionalFields)
{
RpcResponse response(Status::Error, request.messageId(), request.methodName(), additionalFields);
response._errorMessage = errorMessage;
return response;
}

70
src/rpc/RpcResponse.h Normal file
View File

@ -0,0 +1,70 @@
/*
obs-websocket
Copyright (C) 2016-2019 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/>
*/
#pragma once
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
class RpcRequest;
class RpcResponse
{
public:
enum Status { Unknown, Ok, Error };
static RpcResponse ofRequest(const RpcRequest& request);
static const RpcResponse ok(const RpcRequest& request, obs_data_t* additionalFields = nullptr);
static const RpcResponse fail(
const RpcRequest& request, const QString& errorMessage,
obs_data_t* additionalFields = nullptr
);
Status status() {
return _status;
}
const QString& messageId() const {
return _messageId;
}
const QString& methodName() const {
return _methodName;
}
const QString& errorMessage() const {
return _errorMessage;
}
const OBSData additionalFields() const {
return OBSData(_additionalFields);
}
private:
explicit RpcResponse(
Status status,
const QString& messageId, const QString& methodName,
obs_data_t* additionalFields = nullptr
);
const Status _status;
const QString _messageId;
const QString _methodName;
QString _errorMessage;
OBSDataAutoRelease _additionalFields;
};