Compare commits

..

43 Commits

Author SHA1 Message Date
PatTheMav
faf0866f1b cmake: Downgrade deprecation error for MSVC compilations
Error has already been downgraded for Clang, AppleClang, and GCC.
2024-08-01 14:24:00 -07:00
PatTheMav
228afd3405 cmake: Downgrade deprecation error for GCC compilations
Error has already been downgraded for Clang and AppleClang, not doing
so for GCC was an oversight.
2024-08-01 10:26:50 -07:00
tt2468
0548c7798a base: Update version to 5.5.2
Bug Fixes:
- Fix an issue where the virtualcam requests would report that the
virtualcam is not available.
- Fix an issue with the config migration where the migrated settings
were not being persisted to disk.
2024-07-18 12:45:53 -07:00
tt2468
6c9fd55c63 config: Always write config when migrating
Fixes an issue where OBS 30.1.2 migrations would work on the first
30.2.0 load, but the settings would not persist to disk for further
loads.
2024-07-17 22:58:54 -07:00
Translation Updater
7e3f2a82f0 Update translations from Crowdin 2024-07-17 09:34:11 +00:00
tt2468
65396e1db7 requesthandler: Use existence of virtualcam output to test availability
An upstream commit removed the `vcamEnabled` private data field from
being set, so we need to use a new method now.
2024-07-16 11:44:02 -07:00
tt2468
f8bc7c4f59 base: Update version to 5.5.1
Enhancements:
- Updated translation strings

Bug Fixes:
- Fixed a potential crash with the migration on systems set to
non-english languages
2024-06-11 15:41:13 -07:00
derrod
9e48274617 Config: Ensure conversion to filesystem::path uses utf-8 2024-06-11 13:43:10 -07:00
Translation Updater
3b7c1c5381 Update translations from Crowdin 2024-06-07 09:47:56 +00:00
tt2468
20551043f9 base: Update version to 5.5.0
New Features:
- Added `CreateRecordChapter` request for the new native MP4 muxer
[tt2468]
- Added `SplitRecordFile` request to create a file split [tt2468]
- Added `RecordFileChanged` request for when the current recording is
split [tt2468]
- Added obs-websocket-api as a Cmake target, allowing plugins to build
against it without directly including the header file in their source
tree. [tytan652]
- Added the ability to subscribe to obs-websocket events via the
obs-websocket-api header file. [tt2468]

Enhancements:
- Added `cropToBounds` boolean value to Get/SetSceneItemTransform
[exeldro]

Bug Fixes:
- Fixed screenshot behavior of sources with a crop filter not
respecting the cropped size (#1132) [tt2468]
- Fixed an issue with `TriggerHotkeyByName` not releasing keys
correctly when multiple keys are specified. [exeldro]

Other Notes:
- Fixed a few enums showing as deprecated in the documentation
- The location of the obs-websocket global settings data has changed!
Settings located in `global.ini` have moved to the
`plugin_config/obs-websocket` directory. This includes the `global`
realm for the `*PersistentData` requests. Upon loading with an
un-migrated configuration, obs-websocket will perform a migration and
delete the old configurations.
As such, **migration is not reversible**
2024-06-07 01:30:46 -07:00
Github Actions
086bf06008 docs(ci): Update generated docs - 6483dca [skip ci] 2024-06-07 08:29:25 +00:00
tt2468
6483dcaef0 requesthandler: Add CreateRecordChapter
The new `Hybrid MP4 [BETA]` output added in OBS adds support for
writing chapter markers to the file.
2024-06-07 01:29:10 -07:00
tt2468
71920c484b eventhandler: Add RecordFileChanged event
When a file split happens, this will fire with the new file name
2024-06-07 01:29:10 -07:00
tt2468
0eda8f9406 requesthandler: Add SplitRecordFile request 2024-06-07 01:29:10 -07:00
Exeldro
3b873ceb30 requesthandler: Fix releasing hotkeys triggered by name 2024-06-06 00:04:39 -07:00
Exeldro
36f50adf8a requesthandler: Add cropToBounds to scene item 2024-06-06 00:03:26 -07:00
Github Actions
acd1af12a1 docs(ci): Update generated docs - eb28825 [skip ci] 2024-06-06 06:53:33 +00:00
tt2468
eb2882515f docs: Fix some enums showing up as deprecated
Closes #1141
2024-06-05 23:50:48 -07:00
tt2468
5c3c4c76c8 requesthandler: Fix resolution of screenshots of cropped sources
This applies the same fix found in obsproject/obs-studio#10077 to get
the target source's real width and height, not the width and height
values from the pre-filter stage.

Closes #1213
2024-06-05 23:34:04 -07:00
tt2468
8c80e0745a Config: Fix plugin startup for fresh installs
The commit to migrate data from global.ini to the plugin_config folder
accidentally broke plugin startup for fresh configurations. Instead of
returning early if no configuration is found, simply generate a new one
from defaults.

Closes #1225
2024-06-05 23:26:36 -07:00
tt2468
5b4aa9dabd WebSocketApi: Implement backend for obs-websocket event listening 2024-04-23 01:50:51 -07:00
tt2468
ee283c7141 lib: Implement obs-websocket event callback access
This allows plugins to listen directly for obs-websocket events.
2024-04-23 01:50:11 -07:00
tt2468
179e197bd5 base: Many random fixups preparing for WebSocketApi event callbacks 2024-04-23 00:28:00 -07:00
tt2468
5fc39ef054 base: Apply latest clang-format changes from upstream
Minus, some customizations, of course
2024-04-22 23:44:04 -07:00
tt2468
74719ce502 base: Move some direct crosstalk to callback system 2024-04-22 23:35:16 -07:00
tt2468
9123879c76 Config: Use std::string for ServerPassword instead of QString
Less Qt leeching into things is better.
2024-04-22 22:50:10 -07:00
tt2468
9db7464faa base: Use std::make_shared when allocating classes
Follows c++ recommendations.
2024-04-22 22:42:55 -07:00
tt2468
e2b8a06d94 requesthandler: Use new global realm path in persistent data requests
The `MigratePersistentData()` function handles migrating persistent
data on module load, and will fail if the data cannot be migrated.
2024-04-22 22:36:12 -07:00
tt2468
af31f1adca Config: Migrate config/persistent data to plugin_config directory
This commit moves the Config value storage from `global.ini` to a new
`config.json` file in the `plugin_config/obs-websocket` directory. This
comes after some internal discussion about plugins not using the
`plugin_config` directory, and that obs-websocket was offending.

Settings are currently stored as a JSON object, and field names have
been changed from using PascalCase to snake_case, to better align
with how JSON is stored elsewhere in OBS.
2024-04-22 22:30:44 -07:00
tt2468
4410e30684 utils: Pass fileName by value in GetModuleConfigPath()
Lots of `const char *` values, preventing usage of passing by reference
2024-04-22 22:29:21 -07:00
tt2468
2c884ca690 utils: Make SetJsonFileContent() create directories by default 2024-04-22 22:28:45 -07:00
tt2468
f72f23a9d7 utils: Minor code fixups 2024-04-22 18:40:50 -07:00
tt2468
a589e80bdb utils: Implement helper to get current module config path 2024-04-22 18:38:19 -07:00
tt2468
305afd763d utils: Remove old *AutoRelease definitions
Now that OBS has been out with the upstream AutoRelease definitions,
and obs-websocket is also in-tree-only, these are no longer necessary.
2024-04-22 18:32:51 -07:00
tt2468
42e7eb6c34 utils: Remove text file Get/Set methods from Platform
No longer needed, and using Qt isn't good anyway
2024-04-22 18:23:39 -07:00
tt2468
bdf812dc09 utils: Reimplement Get/SetJsonFileContent helpers
Uses <fstream> instead of the text helpers
2024-04-22 18:22:27 -07:00
tytan652
c8cf2d94ac cmake,lib,base: Export obs-websocket-api as a target
This enables the installation of the header in the include directory
2024-03-30 17:04:37 -07:00
Translation Updater
d2d4bfb3e7 Update translations from Crowdin 2024-03-12 18:11:05 +00:00
Lain
d5077fca03 base: Update to version 5.4.2
Bug Fixes:
- Fixes version update to use both legacy and main CMake files
2024-02-21 11:21:17 -06:00
Lain
4a647c5262 base: Update to version 5.4.1
Bug Fixes:
- Updated scene item transform API to latest version to prevent
  deprecation warnings (obs_sceneitem_set_info2 and
  obs_sceneitem_get_info2)
2024-02-20 21:44:33 -06:00
Exeldro
3ea3d3228b requesthandler: Update scene item transform API
Updates:
obs_sceneitem_get_info to obs_sceneitem_get_info2
obs_sceneitem_set_info to obs_sceneitem_set_info2

Ensures that we're using the latest versions of these functions in order
to prevent future deprecation
2024-02-20 21:44:33 -06:00
Ryan Foster
e94f9194a2 CI: Update first-party GitHub Actions to v4
GitHub Actions has deprecated actions based on node16. The v4 actions
are based on node20. Replace first-party v2/v3 actions with their v4
counterparts.

GitHub Actions has deprecated actions based on node12 and forces them to
run on node16, which is also deprecated. Update to v4 actions to avoid
warnings on CI.

See:
https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/
https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2024-01-30 16:25:38 -08:00
Translation Updater
9ee6e2ff2a Update translations from Crowdin 2024-01-29 20:47:37 +00:00
59 changed files with 1106 additions and 474 deletions

View File

@ -1,6 +1,6 @@
# please use clang-format version 8 or later
# please use clang-format version 16 or later
Standard: Cpp11
Standard: c++17
AccessModifierOffset: -8
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
@ -8,14 +8,14 @@ AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
#AllowAllArgumentsOnNextLine: false # requires clang-format 9
#AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
#AllowShortLambdasOnASingleLine: Inline # requires clang-format 9
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
@ -53,10 +53,11 @@ Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: false
ForEachMacros:
ForEachMacros:
- 'json_object_foreach'
- 'json_object_foreach_safe'
- 'json_array_foreach'
- 'HASH_ITER'
IncludeBlocks: Preserve
IndentCaseLabels: false
IndentPPDirectives: None
@ -65,7 +66,7 @@ IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
#ObjCBinPackProtocolList: Auto # requires clang-format 7
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
@ -83,13 +84,13 @@ ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
#SpaceAfterLogicalNot: false # requires clang-format 9
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # requires clang-format 7
#SpaceBeforeInheritanceColon: true # requires clang-format 7
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
@ -97,11 +98,111 @@ SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
#StatementMacros: # requires clang-format 8
# - 'Q_OBJECT'
StatementMacros:
- 'Q_OBJECT'
TabWidth: 8
#TypenameMacros: # requires clang-format 9
# - 'DARRAY'
TypenameMacros:
- 'DARRAY'
UseTab: ForContinuationAndIndentation
---
Language: ObjC
AccessModifierOffset: 2
AlignArrayOfStructures: Right
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AllowShortBlocksOnASingleLine: Never
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AttributeMacros: ['__unused', '__autoreleasing', '_Nonnull', '__bridge']
BitFieldColonSpacing: Both
#BreakBeforeBraces: Webkit
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: true
BreakAfterAttributes: Never
BreakArrays: false
BreakBeforeConceptDeclarations: Allowed
BreakBeforeInlineASMColon: OnlyMultiline
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterComma
ColumnLimit: 120
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: Indent
IndentGotoLabels: false
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertBraces: false
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: false
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PPIndentWidth: -1
PackConstructorInitializers: NextLine
QualifierAlignment: Leave
ReferenceAlignment: Right
RemoveSemicolon: false
RequiresClausePosition: WithPreceding
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 1
SortIncludes: false
#SortUsingDeclarations: LexicographicNumeric
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInConditionalStatement: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
Standard: c++17
TabWidth: 4
UseTab: Never

View File

@ -11,7 +11,7 @@ jobs:
if: github.repository_owner == 'obsproject'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 100
- name: Upload US English Language Files 🇺🇸

View File

@ -18,7 +18,7 @@ jobs:
IS_CI: "true"
steps:
- name: 'Checkout'
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
path: ${{ github.workspace }}/obs-websocket
- name: 'Generate docs'

View File

@ -15,7 +15,7 @@ jobs:
if: contains(github.event.head_commit.message, '[skip ci]') != true
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Generate docs
run: cd docs && ./build_docs.sh
- name: Run markdownlint-cli

View File

@ -2,9 +2,11 @@ cmake_minimum_required(VERSION 3.16...3.25)
legacy_check()
set(obs-websocket_VERSION 5.4.0)
set(obs-websocket_VERSION 5.5.2)
set(OBS_WEBSOCKET_RPC_VERSION 1)
include(cmake/obs-websocket-api.cmake)
option(ENABLE_WEBSOCKET "Enable building OBS with websocket plugin" ON)
if(NOT ENABLE_WEBSOCKET)
target_disable(obs-websocket)
@ -34,7 +36,6 @@ add_library(OBS::websocket ALIAS obs-websocket)
target_sources(
obs-websocket
PRIVATE # cmake-format: sortable
lib/obs-websocket-api.h
src/Config.cpp
src/Config.h
src/forms/ConnectInfo.cpp
@ -137,12 +138,14 @@ target_compile_definitions(
target_compile_options(
obs-websocket
PRIVATE $<$<PLATFORM_ID:Windows>:/wd4267>
$<$<PLATFORM_ID:Windows>:/wd4996>
$<$<COMPILE_LANG_AND_ID:CXX,GNU,AppleClang,Clang>:-Wall>
$<$<COMPILE_LANG_AND_ID:CXX,GNU,AppleClang,Clang>:-Wno-error=float-conversion>
$<$<COMPILE_LANG_AND_ID:CXX,GNU,AppleClang,Clang>:-Wno-error=shadow>
$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wno-error=format-overflow>
$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wno-error=int-conversion>
$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wno-error=comment>
$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wno-error=deprecated-declarations>
$<$<COMPILE_LANG_AND_ID:CXX,AppleClang,Clang>:-Wno-error=null-pointer-subtraction>
$<$<COMPILE_LANG_AND_ID:CXX,AppleClang,Clang>:-Wno-error=deprecated-declarations>
$<$<COMPILE_LANG_AND_ID:CXX,AppleClang,Clang>:-Wno-error=implicit-int-conversion>
@ -154,6 +157,7 @@ target_link_libraries(
obs-websocket
PRIVATE OBS::libobs
OBS::frontend-api
OBS::websocket-api
Qt::Core
Qt::Widgets
Qt::Svg

View File

@ -1,8 +1,23 @@
project(obs-websocket VERSION 5.4.0)
project(obs-websocket VERSION 5.5.2)
set(OBS_WEBSOCKET_RPC_VERSION 1)
option(ENABLE_WEBSOCKET "Enable building OBS with websocket plugin" ON)
add_library(obs-websocket-api INTERFACE)
add_library(OBS::websocket-api ALIAS obs-websocket-api)
target_sources(obs-websocket-api INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/obs-websocket-api.h>
$<INSTALL_INTERFACE:${OBS_INCLUDE_DESTINATION}/obs-websocket-api.h>)
target_link_libraries(obs-websocket-api INTERFACE OBS::libobs)
target_include_directories(obs-websocket-api INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib>
$<INSTALL_INTERFACE:${OBS_INCLUDE_DESTINATION}>)
set_target_properties(obs-websocket-api PROPERTIES PUBLIC_HEADER lib/obs-websocket-api.h)
export_target(obs-websocket-api)
if(NOT ENABLE_WEBSOCKET OR NOT ENABLE_UI)
message(STATUS "OBS: DISABLED obs-websocket")
return()
@ -56,7 +71,6 @@ target_sources(
src/obs-websocket.h
src/Config.cpp
src/Config.h
lib/obs-websocket-api.h
src/forms/SettingsDialog.cpp
src/forms/SettingsDialog.h
src/forms/ConnectInfo.cpp
@ -133,6 +147,7 @@ target_link_libraries(
obs-websocket
PRIVATE OBS::libobs
OBS::frontend-api
OBS::websocket-api
Qt::Core
Qt::Widgets
Qt::Svg

View File

@ -0,0 +1,14 @@
add_library(obs-websocket-api INTERFACE)
add_library(OBS::websocket-api ALIAS obs-websocket-api)
target_sources(obs-websocket-api INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/obs-websocket-api.h>
$<INSTALL_INTERFACE:${OBS_INCLUDE_DESTINATION}/obs-websocket-api.h>)
target_link_libraries(obs-websocket-api INTERFACE OBS::libobs)
target_include_directories(obs-websocket-api INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib>"
"$<INSTALL_INTERFACE:${OBS_INCLUDE_DESTINATION}>")
set_target_properties(obs-websocket-api PROPERTIES PREFIX "" PUBLIC_HEADER lib/obs-websocket-api.h)
target_export(obs-websocket-api)

View File

@ -0,0 +1,8 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(libobs REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
check_required_components("@PROJECT_NAME@")

41
data/locale/be-BY.ini Normal file
View File

@ -0,0 +1,41 @@
OBSWebSocket.Plugin.Description="Аддаленае кіраванне OBS Studio праз WebSocket"
OBSWebSocket.Settings.DialogTitle="Налады сервера WebSocket"
OBSWebSocket.Settings.PluginSettingsTitle="Налады плагіна"
OBSWebSocket.Settings.ServerEnable="Уключыць сервер WebSocket"
OBSWebSocket.Settings.AlertsEnable="Уключыць апавяшчэнні ў вобласці апавяшчэнняў"
OBSWebSocket.Settings.DebugEnable="Уключыць журнал адладкі"
OBSWebSocket.Settings.DebugEnableHoverText="Уключае журнал адладкі толькі для бягучага сеансу OBS. Пасля перазапуску будзе выключана.\nКаб праграма запускалася з уключанай наладай, выкарыстайце параметр --websocket_debug"
OBSWebSocket.Settings.ServerSettingsTitle="Налады сервера"
OBSWebSocket.Settings.AuthRequired="Уключыць аўтэнтыфікацыю"
OBSWebSocket.Settings.Password="Пароль сервера"
OBSWebSocket.Settings.GeneratePassword="Згенераваць"
OBSWebSocket.Settings.ServerPort="Порт сервера"
OBSWebSocket.Settings.ShowConnectInfo="Паказаць звесткі пра злучэнне"
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Увага: ідзе трансляцыя"
OBSWebSocket.Settings.ShowConnectInfoWarningMessage="Выглядае, што ў бягучы момант ідзе вывад (стрым, запіс і г. д.)."
OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Ці вы ўпэўненыя, што хочаце паказаць вашы звесткі пра злучэнне?"
OBSWebSocket.Settings.Save.UserPasswordWarningTitle="Увага: магчымая небяспека"
OBSWebSocket.Settings.Save.UserPasswordWarningMessage="obs-websocket захоўвае пароль сервера ў выглядзе звычайнага тэксту. Настойліва рэкамендуецца выкарыстоўваць пароль, які згенеруе obs-websocket."
OBSWebSocket.Settings.Save.UserPasswordWarningInfoText="Ці вы ўпэўненыя, што хочаце карыстацца сваім паролем?"
OBSWebSocket.Settings.Save.PasswordInvalidErrorTitle="Увага: памылковая канфігурацыя"
OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage="Пароль павінен утрымліваць 6 або больш сімвалаў."
OBSWebSocket.SessionTable.Title="Злучаныя сеансы WebSocket"
OBSWebSocket.SessionTable.RemoteAddressColumnTitle="Аддалены адрас"
OBSWebSocket.SessionTable.SessionDurationColumnTitle="Даўжыня сеансу"
OBSWebSocket.SessionTable.MessagesInOutColumnTitle="Паведамленні I/O"
OBSWebSocket.SessionTable.IdentifiedTitle="Ідэнтыфікавана"
OBSWebSocket.SessionTable.KickButtonColumnTitle="Выгнаць?"
OBSWebSocket.SessionTable.KickButtonText="Выгнаць"
OBSWebSocket.ConnectInfo.DialogTitle="Звесткі пра злучэнне WebSocket"
OBSWebSocket.ConnectInfo.CopyText="Скапіяваць"
OBSWebSocket.ConnectInfo.ServerIp="IP сервера (найлепшая здагадка)"
OBSWebSocket.ConnectInfo.ServerPort="Порт сервера"
OBSWebSocket.ConnectInfo.ServerPassword="Пароль сервера"
OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText="[аўтэнтыфікацыя адкл.]"
OBSWebSocket.ConnectInfo.QrTitle="QR-код злучэння"
OBSWebSocket.TrayNotification.Identified.Title="Новае злучэнне WebSocket"
OBSWebSocket.TrayNotification.Identified.Body="Кліент %1 ідэнтыфікаваны."
OBSWebSocket.TrayNotification.AuthenticationFailed.Title="Збой аўтэнтыфікацыі WebSocket"
OBSWebSocket.TrayNotification.AuthenticationFailed.Body="Кліент %1 не прайшоў аўтэнтыфікацыю."
OBSWebSocket.TrayNotification.Disconnected.Title="Кліент WebSocket адключаны"
OBSWebSocket.TrayNotification.Disconnected.Body="Кліент %1 адключаны."

View File

@ -1,4 +1,5 @@
OBSWebSocket.Plugin.Description="Fjernstyring af OBS Studio via WebSocket"
OBSWebSocket.Settings.DialogTitle="WebSocket-serverindstillinger"
OBSWebSocket.Settings.PluginSettingsTitle="Plugin-indstillinger"
OBSWebSocket.Settings.ServerEnable="Aktivér WebSocket-server"
OBSWebSocket.Settings.AlertsEnable="Aktivér Systembakke Alarmer"

View File

@ -8,7 +8,7 @@ OBSWebSocket.Settings.DebugEnableHoverText="Aktifkan pencatatan awakutu untuk pe
OBSWebSocket.Settings.ServerSettingsTitle="Pengaturan Server"
OBSWebSocket.Settings.AuthRequired="Aktifkan Autentikasi"
OBSWebSocket.Settings.Password="Kata Sandi Server"
OBSWebSocket.Settings.GeneratePassword="Ciptakan Kata Sandi"
OBSWebSocket.Settings.GeneratePassword="Buat Kata Sandi"
OBSWebSocket.Settings.ServerPort="Port Server"
OBSWebSocket.Settings.ShowConnectInfo="Tampilkan Informasi Koneksi"
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Peringatan: Saat Ini Siaran Langsung"
@ -16,7 +16,7 @@ OBSWebSocket.Settings.ShowConnectInfoWarningMessage="Sepertinya sebuah output (s
OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Anda yakin ingin melihat informasi koneksi Anda?"
OBSWebSocket.Settings.Save.UserPasswordWarningTitle="Peringatan: Potensi Masalah Keamanan"
OBSWebSocket.Settings.Save.UserPasswordWarningMessage="obs-websocket menyimpan kata sandi server sebagai teks biasa. Sangat disarankan untuk menggunakan kata sandi yang diciptakan oleh obs-websocket."
OBSWebSocket.Settings.Save.UserPasswordWarningInfoText="Anda yakin ingin menggunakan kata sandi Anda sendiri?"
OBSWebSocket.Settings.Save.UserPasswordWarningInfoText="Apakah Anda yakin ingin menggunakan kata sandi sendiri?"
OBSWebSocket.Settings.Save.PasswordInvalidErrorTitle="Galat: Konfigurasi Tidak Berlaku"
OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage="Anda harus menggunakan kata sandi yang minimal 6 karakter atau lebih."
OBSWebSocket.SessionTable.Title="Sesi WebSocket yang Terhubung"

View File

@ -18,7 +18,7 @@ OBSWebSocket.Settings.Save.UserPasswordWarningTitle="Ostrzeżenie: Potencjalny p
OBSWebSocket.Settings.Save.UserPasswordWarningMessage="obs-websocket przechowuje hasło serwera jako zwykły tekst. Wysoce zalecane jest użycie hasła generowanego przez obs-websocket."
OBSWebSocket.Settings.Save.UserPasswordWarningInfoText="Czy na pewno chcesz użyć własnego hasła?"
OBSWebSocket.Settings.Save.PasswordInvalidErrorTitle="Błąd: Nieprawidłowa konfiguracja"
OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage="Musisz użyć hasła, które ma 6 lub więcej znaków."
OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage="Hasło musi zawierać 6 lub więcej znaków."
OBSWebSocket.SessionTable.Title="Podłączone sesje WebSocket"
OBSWebSocket.SessionTable.RemoteAddressColumnTitle="Adres zdalny"
OBSWebSocket.SessionTable.SessionDurationColumnTitle="Czas trwania sesji"

View File

@ -12,7 +12,7 @@ OBSWebSocket.Settings.GeneratePassword="Generează parola"
OBSWebSocket.Settings.ServerPort="Portul serverului"
OBSWebSocket.Settings.ShowConnectInfo="Afișează informațiile conexiunii"
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Avertisment: În prezent în direct"
OBSWebSocket.Settings.ShowConnectInfoWarningMessage="Se pare că un output (transmisiune, înregistrare etc.) este activ în prezent."
OBSWebSocket.Settings.ShowConnectInfoWarningMessage="Se pare că un output (stream, înregistrare etc.) este activ în prezent."
OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Sigur vrei să afișezi informațiile de conectare?"
OBSWebSocket.Settings.Save.UserPasswordWarningTitle="Avertisment: Potențială problemă de securitate"
OBSWebSocket.Settings.Save.UserPasswordWarningMessage="obs-websocket stochează parola serverului ca text simplu. Este foarte recomandat să folosiți o parolă generată de obs-websocket."

View File

@ -28,7 +28,7 @@ OBSWebSocket.SessionTable.KickButtonColumnTitle="Выгнать?"
OBSWebSocket.SessionTable.KickButtonText="Выгнать"
OBSWebSocket.ConnectInfo.DialogTitle="Сведения о подключении WebSocket"
OBSWebSocket.ConnectInfo.CopyText="Копировать"
OBSWebSocket.ConnectInfo.ServerIp="IP сервера (лучшая догадка)"
OBSWebSocket.ConnectInfo.ServerIp="IP сервера (вероятный)"
OBSWebSocket.ConnectInfo.ServerPort="Порт сервера"
OBSWebSocket.ConnectInfo.ServerPassword="Пароль сервера"
OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText="[Вход отключён]"

View File

@ -1,6 +1,6 @@
OBSWebSocket.Plugin.Description="Fjärrkontroll av OBS Studio via WebSocket"
OBSWebSocket.Settings.DialogTitle="WebSocket-serverinställningar"
OBSWebSocket.Settings.PluginSettingsTitle="Insticksmodulsinställningar"
OBSWebSocket.Settings.PluginSettingsTitle="Insticksprogramsinställningar"
OBSWebSocket.Settings.ServerEnable="Aktivera WebSocket-server"
OBSWebSocket.Settings.AlertsEnable="Aktivera systemfältsmeddelanden"
OBSWebSocket.Settings.DebugEnable="Aktivera felsökningsloggning"
@ -11,7 +11,7 @@ OBSWebSocket.Settings.Password="Serverlösenord"
OBSWebSocket.Settings.GeneratePassword="Generera lösenord"
OBSWebSocket.Settings.ServerPort="Serverport"
OBSWebSocket.Settings.ShowConnectInfo="Visa anslutningsinformation"
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Varning: Sänder för närvarande"
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Varning: Direktsändning pågår"
OBSWebSocket.Settings.ShowConnectInfoWarningMessage="Det verkar som om en utmatning (ström, inspelning, etc.) är för närvarande aktiv."
OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Är du säker på att du vill visa din anslutningsinformation?"
OBSWebSocket.Settings.Save.UserPasswordWarningTitle="Varning: Potentiellt säkerhetsproblem"

9
data/locale/tt-RU.ini Normal file
View File

@ -0,0 +1,9 @@
OBSWebSocket.Settings.ServerSettingsTitle="Сервер көйләүләре"
OBSWebSocket.Settings.Password="Сервер серсүзе"
OBSWebSocket.Settings.GeneratePassword="Серсүзне ясау"
OBSWebSocket.Settings.ServerPort="Сервер порты"
OBSWebSocket.SessionTable.KickButtonColumnTitle="Чыгарыргамы?"
OBSWebSocket.SessionTable.KickButtonText="Чыгару"
OBSWebSocket.ConnectInfo.CopyText="Күчермә алу"
OBSWebSocket.ConnectInfo.ServerPort="Сервер порты"
OBSWebSocket.ConnectInfo.ServerPassword="Сервер серсүзе"

2
data/locale/ug-CN.ini Normal file
View File

@ -0,0 +1,2 @@
OBSWebSocket.Settings.GeneratePassword="ئىم ھاسىللا"
OBSWebSocket.ConnectInfo.CopyText="كۆچۈر"

View File

@ -461,72 +461,72 @@
{
"description": "Unknown state.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_UNKNOWN",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_UNKNOWN"
},
{
"description": "The output is starting.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_STARTING",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_STARTING"
},
{
"description": "The input has started.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_STARTED",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_STARTED"
},
{
"description": "The output is stopping.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_STOPPING",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_STOPPING"
},
{
"description": "The output has stopped.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_STOPPED",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_STOPPED"
},
{
"description": "The output has disconnected and is reconnecting.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_RECONNECTING",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_RECONNECTING"
},
{
"description": "The output has reconnected successfully.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_RECONNECTED",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.1.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_RECONNECTED"
},
{
"description": "The output is now paused.",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_PAUSED",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.1.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_PAUSED"
},
{
"description": "The output has been resumed (unpaused).",
"enumIdentifier": "OBS_WEBSOCKET_OUTPUT_RESUMED",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_OUTPUT_RESUMED"
}
@ -538,56 +538,56 @@
{
"description": "No action.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE"
},
{
"description": "Play the media input.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY"
},
{
"description": "Pause the media input.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE"
},
{
"description": "Stop the media input.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"
},
{
"description": "Restart the media input.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART"
},
{
"description": "Go to the next playlist item.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT"
},
{
"description": "Go to the previous playlist item.",
"enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS",
"rpcVersion": 1,
"deprecated": true,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS"
}
@ -3472,6 +3472,37 @@
"requestFields": [],
"responseFields": []
},
{
"description": "Splits the current file being recorded into a new file.",
"requestType": "SplitRecordFile",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.5.0",
"category": "record",
"requestFields": [],
"responseFields": []
},
{
"description": "Adds a new chapter marker to the file currently being recorded.\n\nNote: As of OBS 30.2.0, the only file format supporting this feature is Hybrid MP4.",
"requestType": "CreateRecordChapter",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.5.0",
"category": "record",
"requestFields": [
{
"valueName": "chapterName",
"valueType": "String",
"valueDescription": "Name of the new chapter",
"valueRestrictions": null,
"valueOptional": true,
"valueOptionalBehavior": "Unknown"
}
],
"responseFields": []
},
{
"description": "Gets a list of all scene items in a scene.\n\nScenes only",
"requestType": "GetSceneItemList",
@ -6046,6 +6077,23 @@
}
]
},
{
"description": "The record output has started writing to a new file. For example, when a file split happens.",
"eventType": "RecordFileChanged",
"eventSubscription": "Outputs",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.5.0",
"category": "outputs",
"dataFields": [
{
"valueName": "newOutputPath",
"valueType": "String",
"valueDescription": "File name that the output has begun writing to"
}
]
},
{
"description": "The state of the replay buffer output has changed.",
"eventType": "ReplayBufferStateChanged",

View File

@ -1336,7 +1336,6 @@ No action.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1347,7 +1346,6 @@ Play the media input.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1358,7 +1356,6 @@ Pause the media input.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1369,7 +1366,6 @@ Stop the media input.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1380,7 +1376,6 @@ Restart the media input.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1391,7 +1386,6 @@ Go to the next playlist item.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1402,7 +1396,6 @@ Go to the previous playlist item.
- Identifier Value: `OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
## ObsOutputState
@ -1413,7 +1406,6 @@ Unknown state.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_UNKNOWN`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1424,7 +1416,6 @@ The output is starting.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_STARTING`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1435,7 +1426,6 @@ The input has started.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_STARTED`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1446,7 +1436,6 @@ The output is stopping.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_STOPPING`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1457,7 +1446,6 @@ The output has stopped.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_STOPPED`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1468,7 +1456,6 @@ The output has disconnected and is reconnecting.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_RECONNECTING`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
---
@ -1479,7 +1466,6 @@ The output has reconnected successfully.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_RECONNECTED`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.1.0
---
@ -1490,7 +1476,6 @@ The output is now paused.
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_PAUSED`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.1.0
---
@ -1501,7 +1486,6 @@ The output has been resumed (unpaused).
- Identifier Value: `OBS_WEBSOCKET_OUTPUT_RESUMED`
- Latest Supported RPC Version: `1`
- **⚠️ Deprecated. ⚠️**
- Added in v5.0.0
# Events
@ -1564,6 +1548,7 @@ The output has been resumed (unpaused).
- [Outputs Events](#outputs-events)
- [StreamStateChanged](#streamstatechanged)
- [RecordStateChanged](#recordstatechanged)
- [RecordFileChanged](#recordfilechanged)
- [ReplayBufferStateChanged](#replaybufferstatechanged)
- [VirtualcamStateChanged](#virtualcamstatechanged)
- [ReplayBufferSaved](#replaybuffersaved)
@ -2449,6 +2434,22 @@ The state of the record output has changed.
---
### RecordFileChanged
The record output has started writing to a new file. For example, when a file split happens.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.5.0
**Data Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| newOutputPath | String | File name that the output has begun writing to |
---
### ReplayBufferStateChanged
The state of the replay buffer output has changed.
@ -2728,6 +2729,8 @@ communication is desired.
- [ToggleRecordPause](#togglerecordpause)
- [PauseRecord](#pauserecord)
- [ResumeRecord](#resumerecord)
- [SplitRecordFile](#splitrecordfile)
- [CreateRecordChapter](#createrecordchapter)
- [Media Inputs Requests](#media-inputs-1-requests)
- [GetMediaInputStatus](#getmediainputstatus)
- [SetMediaInputCursor](#setmediainputcursor)
@ -5281,6 +5284,34 @@ Resumes the record output.
- Latest Supported RPC Version: `1`
- Added in v5.0.0
---
### SplitRecordFile
Splits the current file being recorded into a new file.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.5.0
---
### CreateRecordChapter
Adds a new chapter marker to the file currently being recorded.
Note: As of OBS 30.2.0, the only file format supporting this feature is Hybrid MP4.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.5.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| ?chapterName | String | Name of the new chapter | None | Unknown |
## Media Inputs Requests
### GetMediaInputStatus

View File

@ -22,7 +22,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 2
#define OBS_WEBSOCKET_API_VERSION 3
#ifdef __cplusplus
extern "C" {
@ -30,6 +30,7 @@ extern "C" {
typedef void *obs_websocket_vendor;
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
typedef void (*obs_websocket_event_callback_function)(uint64_t, const char *, const char *, void *);
struct obs_websocket_request_response {
unsigned int status_code;
@ -44,6 +45,11 @@ struct obs_websocket_request_callback {
void *priv_data;
};
struct obs_websocket_event_callback {
obs_websocket_event_callback_function callback;
void *priv_data;
};
static proc_handler_t *_ph;
/* ==================== INTERNAL API FUNCTIONS ==================== */
@ -118,7 +124,6 @@ static inline struct obs_websocket_request_response *obs_websocket_call_request(
request_data_string = obs_data_get_json(request_data);
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "request_type", request_type);
calldata_set_string(&cd, "request_data", request_data_string);
@ -144,6 +149,46 @@ static inline void obs_websocket_request_response_free(struct obs_websocket_requ
bfree(response);
}
// Register an event handler to receive obs-websocket events
static inline bool obs_websocket_register_event_callback(obs_websocket_event_callback_function event_callback, void *priv_data)
{
if (!obs_websocket_ensure_ph())
return false;
struct obs_websocket_event_callback cb = {event_callback, priv_data};
calldata_t cd = {0, 0, 0, 0};
calldata_set_ptr(&cd, "callback", &cb);
proc_handler_call(_ph, "register_event_callback", &cd);
bool ret = calldata_bool(&cd, "success");
calldata_free(&cd);
return ret;
}
// Unregister an existing event handler
static inline bool obs_websocket_unregister_event_callback(obs_websocket_event_callback_function event_callback, void *priv_data)
{
if (!obs_websocket_ensure_ph())
return false;
struct obs_websocket_event_callback cb = {event_callback, priv_data};
calldata_t cd = {0, 0, 0, 0};
calldata_set_ptr(&cd, "callback", &cb);
proc_handler_call(_ph, "unregister_event_callback", &cd);
bool ret = calldata_bool(&cd, "success");
calldata_free(&cd);
return ret;
}
/* ==================== VENDOR API FUNCTIONS ==================== */
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
@ -154,7 +199,6 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven
return NULL;
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "name", vendor_name);
proc_handler_call(_ph, "vendor_register", &cd);
@ -168,10 +212,9 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback, void *priv_data)
{
calldata_t cd = {0, 0, 0, 0};
struct obs_websocket_request_callback cb = {request_callback, priv_data};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb);
@ -185,7 +228,6 @@ static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor ve
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
{
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", request_type);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
@ -199,7 +241,6 @@ static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
{
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void *)event_data);

View File

@ -17,60 +17,80 @@ 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 <filesystem>
#include <obs-frontend-api.h>
#include "Config.h"
#include "utils/Crypto.h"
#include "utils/Platform.h"
#include "utils/Obs.h"
#define CONFIG_SECTION_NAME "OBSWebSocket"
#define CONFIG_PARAM_FIRSTLOAD "FirstLoad"
#define CONFIG_PARAM_ENABLED "ServerEnabled"
#define CONFIG_PARAM_PORT "ServerPort"
#define CONFIG_PARAM_ALERTS "AlertsEnabled"
#define CONFIG_PARAM_AUTHREQUIRED "AuthRequired"
#define CONFIG_PARAM_PASSWORD "ServerPassword"
#define PARAM_FIRSTLOAD "FirstLoad"
#define PARAM_ENABLED "ServerEnabled"
#define PARAM_PORT "ServerPort"
#define PARAM_ALERTS "AlertsEnabled"
#define PARAM_AUTHREQUIRED "AuthRequired"
#define PARAM_PASSWORD "ServerPassword"
#define CONFIG_FILE_NAME "config.json"
#define PARAM_FIRSTLOAD "first_load"
#define PARAM_ENABLED "server_enabled"
#define PARAM_PORT "server_port"
#define PARAM_ALERTS "alerts_enabled"
#define PARAM_AUTHREQUIRED "auth_required"
#define PARAM_PASSWORD "server_password"
#define CMDLINE_WEBSOCKET_PORT "websocket_port"
#define CMDLINE_WEBSOCKET_IPV4_ONLY "websocket_ipv4_only"
#define CMDLINE_WEBSOCKET_PASSWORD "websocket_password"
#define CMDLINE_WEBSOCKET_DEBUG "websocket_debug"
Config::Config()
void Config::Load(json config)
{
SetDefaultsToGlobalStore();
}
void Config::Load()
{
config_t *obsConfig = GetConfigStore();
if (!obsConfig) {
blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!");
return;
// Only load from plugin config directory if there hasn't been a migration
if (config.is_null()) {
std::string configFilePath = Utils::Obs::StringHelper::GetModuleConfigPath(CONFIG_FILE_NAME);
Utils::Json::GetJsonFileContent(configFilePath, config); // Fetch the existing config, which may not exist
}
FirstLoad = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD);
ServerEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED);
AlertsEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS);
ServerPort = config_get_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT);
AuthRequired = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED);
ServerPassword = config_get_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD);
if (!config.is_object()) {
blog(LOG_INFO, "[Config::Load] Existing configuration not found, using defaults.");
config = json::object();
}
if (config.contains(PARAM_FIRSTLOAD) && config[PARAM_FIRSTLOAD].is_boolean())
FirstLoad = config[PARAM_FIRSTLOAD];
if (config.contains(PARAM_ENABLED) && config[PARAM_ENABLED].is_boolean())
ServerEnabled = config[PARAM_ENABLED];
if (config.contains(PARAM_ALERTS) && config[PARAM_ALERTS].is_boolean())
AlertsEnabled = config[PARAM_ALERTS];
if (config.contains(PARAM_PORT) && config[PARAM_PORT].is_number_unsigned())
ServerPort = config[PARAM_PORT];
if (config.contains(PARAM_AUTHREQUIRED) && config[PARAM_AUTHREQUIRED].is_boolean())
AuthRequired = config[PARAM_AUTHREQUIRED];
if (config.contains(PARAM_PASSWORD) && config[PARAM_PASSWORD].is_string())
ServerPassword = config[PARAM_PASSWORD];
// Set server password and save it to the config before processing overrides,
// so that there is always a true configured password regardless of if
// future loads use the override flag.
if (FirstLoad) {
FirstLoad = false;
if (ServerPassword.isEmpty()) {
if (ServerPassword.empty()) {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Generating new server password.");
ServerPassword = QString::fromStdString(Utils::Crypto::GeneratePassword());
ServerPassword = Utils::Crypto::GeneratePassword();
} else {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Not generating new password since one is already configured.");
}
Save();
}
// If there are migrated settings, write them to disk before processing arguments.
if (!config.empty())
Save();
// Process `--websocket_port` override
QString portArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PORT);
if (portArgument != "") {
@ -97,7 +117,7 @@ void Config::Load()
blog(LOG_INFO, "[Config::Load] --websocket_password passed. Overriding WebSocket password.");
PasswordOverridden = true;
AuthRequired = true;
ServerPassword = passwordArgument;
ServerPassword = passwordArgument.toStdString();
}
// Process `--websocket_debug` override
@ -110,43 +130,98 @@ void Config::Load()
void Config::Save()
{
config_t *obsConfig = GetConfigStore();
if (!obsConfig) {
blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!");
return;
}
json config;
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad);
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled);
if (!PortOverridden) {
config_set_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort);
}
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled);
std::string configFilePath = Utils::Obs::StringHelper::GetModuleConfigPath(CONFIG_FILE_NAME);
Utils::Json::GetJsonFileContent(configFilePath, config); // Fetch the existing config, which may not exist
config[PARAM_FIRSTLOAD] = FirstLoad.load();
config[PARAM_ENABLED] = ServerEnabled.load();
if (!PortOverridden)
config[PARAM_PORT] = ServerPort.load();
config[PARAM_ALERTS] = AlertsEnabled.load();
if (!PasswordOverridden) {
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword));
config[PARAM_AUTHREQUIRED] = AuthRequired.load();
config[PARAM_PASSWORD] = ServerPassword;
}
config_save(obsConfig);
if (Utils::Json::SetJsonFileContent(configFilePath, config))
blog(LOG_DEBUG, "[Config::Save] Saved config.");
else
blog(LOG_ERROR, "[Config::Save] Failed to write config file!");
}
void Config::SetDefaultsToGlobalStore()
// Finds any old values in global.ini and removes them, then returns the values as JSON
json MigrateGlobalConfigData()
{
config_t *obsConfig = GetConfigStore();
if (!obsConfig) {
blog(LOG_ERROR, "[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!");
return;
// Get existing global config
config_t *config = obs_frontend_get_global_config();
json ret;
// Move values to temporary JSON blob
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD)) {
ret[PARAM_FIRSTLOAD] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED)) {
ret[PARAM_ENABLED] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT)) {
ret[PARAM_PORT] = config_get_uint(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS)) {
ret[PARAM_ALERTS] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED)) {
ret[PARAM_AUTHREQUIRED] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD)) {
ret[PARAM_PASSWORD] = config_get_string(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD);
}
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled);
config_set_default_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword));
if (!ret.is_null()) {
blog(LOG_INFO, "[MigrateGlobalConfigData] Some configurations have been migrated from old config");
config_save(config);
}
return ret;
}
config_t *Config::GetConfigStore()
// Migration from storing persistent data in obsWebSocketPersistentData.json to the module config directory
// This will overwrite any persistent data in the destination. People doing manual OBS config modification be warned!
bool MigratePersistentData()
{
return obs_frontend_get_global_config();
std::error_code ec;
// Ensure module config directory exists
auto moduleConfigDirectory = std::filesystem::u8path(Utils::Obs::StringHelper::GetModuleConfigPath(""));
if (!std::filesystem::exists(moduleConfigDirectory, ec))
std::filesystem::create_directories(moduleConfigDirectory, ec);
if (ec) {
blog(LOG_ERROR, "[MigratePersistentData] Failed to create directory `%s`: %s", moduleConfigDirectory.c_str(),
ec.message().c_str());
return false;
}
// Move any existing persistent data to module config directory, then delete old file
auto oldPersistentDataPath = std::filesystem::u8path(Utils::Obs::StringHelper::GetCurrentProfilePath() +
"/../../../obsWebSocketPersistentData.json");
if (std::filesystem::exists(oldPersistentDataPath, ec)) {
auto persistentDataPath =
std::filesystem::u8path(Utils::Obs::StringHelper::GetModuleConfigPath("persistent_data.json"));
std::filesystem::copy_file(oldPersistentDataPath, persistentDataPath, ec);
std::filesystem::remove(oldPersistentDataPath, ec);
blog(LOG_INFO, "[MigratePersistentData] Persistent data migrated to new path");
}
if (ec) {
blog(LOG_ERROR, "[MigratePersistentData] Failed to move persistent data: %s", ec.message().c_str());
return false;
}
return true;
}

View File

@ -23,14 +23,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QString>
#include <util/config-file.h>
#include "utils/Json.h"
#include "plugin-macros.generated.h"
struct Config {
Config();
void Load();
void Load(json config = nullptr);
void Save();
void SetDefaultsToGlobalStore();
static config_t *GetConfigStore();
std::atomic<bool> PortOverridden = false;
std::atomic<bool> PasswordOverridden = false;
@ -42,5 +40,8 @@ struct Config {
std::atomic<bool> DebugEnabled = false;
std::atomic<bool> AlertsEnabled = false;
std::atomic<bool> AuthRequired = true;
QString ServerPassword;
std::string ServerPassword;
};
json MigrateGlobalConfigData();
bool MigratePersistentData();

View File

@ -18,7 +18,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "WebSocketApi.h"
#include "requesthandler/RequestHandler.h"
#include "obs-websocket.h"
#include "utils/Json.h"
#define RETURN_STATUS(status) \
@ -48,14 +47,19 @@ WebSocketApi::WebSocketApi()
proc_handler_add(_procHandler, "bool get_api_version(out int version)", &get_api_version, nullptr);
proc_handler_add(_procHandler, "bool call_request(in string request_type, in string request_data, out ptr response)",
&call_request, nullptr);
proc_handler_add(_procHandler, "bool vendor_register(in string name, out ptr vendor)", &vendor_register_cb, this);
proc_handler_add(_procHandler, "bool vendor_request_register(in ptr vendor, in string type, in ptr callback)",
&vendor_request_register_cb, this);
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)",
&vendor_request_unregister_cb, this);
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data)", &vendor_event_emit_cb,
&call_request, this);
proc_handler_add(_procHandler, "bool register_event_callback(in ptr callback, out bool success)", &register_event_callback,
this);
proc_handler_add(_procHandler, "bool unregister_event_callback(in ptr callback, out bool success)",
&unregister_event_callback, this);
proc_handler_add(_procHandler, "bool vendor_register(in string name, out ptr vendor)", &vendor_register_cb, this);
proc_handler_add(_procHandler,
"bool vendor_request_register(in ptr vendor, in string type, in ptr callback, out bool success)",
&vendor_request_register_cb, this);
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type, out bool success)",
&vendor_request_unregister_cb, this);
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data, out bool success)",
&vendor_event_emit_cb, this);
proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL);
@ -71,6 +75,10 @@ WebSocketApi::~WebSocketApi()
proc_handler_destroy(_procHandler);
size_t numEventCallbacks = _eventCallbacks.size();
_eventCallbacks.clear();
blog_debug("[WebSocketApi::~WebSocketApi] Deleted %ld event callbacks", numEventCallbacks);
for (auto vendor : _vendors) {
blog_debug("[WebSocketApi::~WebSocketApi] Deleting vendor: %s", vendor.first.c_str());
delete vendor.second;
@ -79,9 +87,21 @@ WebSocketApi::~WebSocketApi()
blog_debug("[WebSocketApi::~WebSocketApi] Finished.");
}
void WebSocketApi::SetEventCallback(EventCallback cb)
void WebSocketApi::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData, uint8_t rpcVersion)
{
_eventCallback = cb;
if (!_obsReady)
return;
// Only broadcast events applicable to the latest RPC version
if (rpcVersion && rpcVersion != CURRENT_RPC_VERSION)
return;
std::string eventDataString = eventData.dump();
std::shared_lock l(_mutex);
for (auto &cb : _eventCallbacks)
cb.callback(requiredIntent, eventType.c_str(), eventDataString.c_str(), cb.priv_data);
}
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType,
@ -126,14 +146,27 @@ void WebSocketApi::get_api_version(void *, calldata_t *cd)
RETURN_SUCCESS();
}
void WebSocketApi::call_request(void *, calldata_t *cd)
void WebSocketApi::call_request(void *priv_data, calldata_t *cd)
{
auto c = static_cast<WebSocketApi *>(priv_data);
#if !defined(PLUGIN_TESTS)
if (!c->_obsReady)
RETURN_FAILURE();
#endif
const char *request_type = calldata_string(cd, "request_type");
const char *request_data = calldata_string(cd, "request_data");
if (!request_type)
RETURN_FAILURE();
#ifdef PLUGIN_TESTS
// Allow plugin tests to complete, even though OBS wouldn't be ready at the time of the test
if (!c->_obsReady && std::string(request_type) != "GetVersion")
RETURN_FAILURE();
#endif
auto response = static_cast<obs_websocket_request_response *>(bzalloc(sizeof(struct obs_websocket_request_response)));
if (!response)
RETURN_FAILURE();
@ -162,6 +195,52 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
RETURN_SUCCESS();
}
void WebSocketApi::register_event_callback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<WebSocketApi *>(priv_data);
void *voidCallback;
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
blog(LOG_WARNING, "[WebSocketApi::register_event_callback] Failed due to missing `callback` pointer.");
RETURN_FAILURE();
}
auto cb = static_cast<obs_websocket_event_callback *>(voidCallback);
std::unique_lock l(c->_mutex);
int64_t foundIndex = c->GetEventCallbackIndex(*cb);
if (foundIndex != -1)
RETURN_FAILURE();
c->_eventCallbacks.push_back(*cb);
RETURN_SUCCESS();
}
void WebSocketApi::unregister_event_callback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<WebSocketApi *>(priv_data);
void *voidCallback;
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
blog(LOG_WARNING, "[WebSocketApi::register_event_callback] Failed due to missing `callback` pointer.");
RETURN_FAILURE();
}
auto cb = static_cast<obs_websocket_event_callback *>(voidCallback);
std::unique_lock l(c->_mutex);
int64_t foundIndex = c->GetEventCallbackIndex(*cb);
if (foundIndex == -1)
RETURN_FAILURE();
c->_eventCallbacks.erase(c->_eventCallbacks.begin() + foundIndex);
RETURN_SUCCESS();
}
void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
{
auto c = static_cast<WebSocketApi *>(priv_data);
@ -172,7 +251,7 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
RETURN_FAILURE();
}
// Theoretically doesn't need a mutex, but it's good to be safe.
// Theoretically doesn't need a mutex due to module load being single-thread, but it's good to be safe.
std::unique_lock l(c->_mutex);
if (c->_vendors.count(vendorName)) {
@ -289,10 +368,10 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
auto eventData = static_cast<obs_data_t *>(voidEventData);
if (!c->_eventCallback)
if (!c->_vendorEventCallback)
RETURN_FAILURE();
c->_eventCallback(v->_name, eventType, eventData);
c->_vendorEventCallback(v->_name, eventType, eventData);
RETURN_SUCCESS();
}

View File

@ -23,9 +23,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <atomic>
#include <obs.h>
#include <obs-websocket-api.h>
#include "../lib/obs-websocket-api.h"
#include "utils/Json.h"
#include "plugin-macros.generated.h"
class WebSocketApi {
public:
@ -35,8 +38,6 @@ public:
NoVendorRequest,
};
typedef std::function<void(std::string, std::string, obs_data_t *)> EventCallback;
struct Vendor {
std::shared_mutex _mutex;
std::string _name;
@ -45,23 +46,44 @@ public:
WebSocketApi();
~WebSocketApi();
void SetEventCallback(EventCallback cb);
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr,
uint8_t rpcVersion = 0);
void SetObsReady(bool ready) { _obsReady = ready; }
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData,
obs_data_t *responseData);
// Callback for when a vendor emits an event
typedef std::function<void(std::string, std::string, obs_data_t *)> VendorEventCallback;
inline void SetVendorEventCallback(VendorEventCallback cb) { _vendorEventCallback = cb; }
private:
inline int64_t GetEventCallbackIndex(obs_websocket_event_callback &cb)
{
for (int64_t i = 0; i < (int64_t)_eventCallbacks.size(); i++) {
auto currentCb = _eventCallbacks[i];
if (currentCb.callback == cb.callback && currentCb.priv_data == cb.priv_data)
return i;
}
return -1;
}
// Proc handlers
static void get_ph_cb(void *priv_data, calldata_t *cd);
static void get_api_version(void *, calldata_t *cd);
static void call_request(void *, calldata_t *cd);
static void register_event_callback(void *, calldata_t *cd);
static void unregister_event_callback(void *, calldata_t *cd);
static void vendor_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_unregister_cb(void *priv_data, calldata_t *cd);
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
private:
std::shared_mutex _mutex;
EventCallback _eventCallback;
proc_handler_t *_procHandler;
std::map<std::string, Vendor *> _vendors;
std::vector<obs_websocket_event_callback> _eventCallbacks;
std::atomic<bool> _obsReady = false;
VendorEventCallback _vendorEventCallback;
};

View File

@ -27,11 +27,11 @@ EventHandler::EventHandler()
signal_handler_t *coreSignalHandler = obs_get_signal_handler();
if (coreSignalHandler) {
signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_update", SourceUpdatedMultiHandler, this);
coreSignals.emplace_back(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
coreSignals.emplace_back(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
coreSignals.emplace_back(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this);
coreSignals.emplace_back(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this);
coreSignals.emplace_back(coreSignalHandler, "source_update", SourceUpdatedMultiHandler, this);
} else {
blog(LOG_ERROR, "[EventHandler::EventHandler] Unable to get libobs signal handler!");
}
@ -45,16 +45,7 @@ EventHandler::~EventHandler()
obs_frontend_remove_event_callback(OnFrontendEvent, this);
signal_handler_t *coreSignalHandler = obs_get_signal_handler();
if (coreSignalHandler) {
signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_update", SourceUpdatedMultiHandler, this);
} else {
blog(LOG_ERROR, "[EventHandler::~EventHandler] Unable to get libobs signal handler!");
}
coreSignals.clear();
// Revoke callbacks of all inputs and scenes, in case some still have our callbacks attached
auto enumInputs = [](void *param, obs_source_t *source) {
@ -73,58 +64,47 @@ EventHandler::~EventHandler()
blog_debug("[EventHandler::~EventHandler] Finished.");
}
void EventHandler::SetBroadcastCallback(EventHandler::BroadcastCallback cb)
// Function to increment or decrement refcounts for high volume event subscriptions
void EventHandler::ProcessSubscriptionChange(bool type, uint64_t eventSubscriptions)
{
_broadcastCallback = cb;
}
void EventHandler::SetObsReadyCallback(EventHandler::ObsReadyCallback cb)
{
_obsReadyCallback = cb;
}
// Function to increment refcounts for high volume event subscriptions
void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
{
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_add(1) == 0) {
if (_inputVolumeMetersHandler)
blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
else
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(
std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1));
if (type) {
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_add(1) == 0) {
if (_inputVolumeMetersHandler)
blog(LOG_WARNING,
"[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
else
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(
std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1));
}
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef++;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)
_inputShowStateChangedRef++;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef++;
} else {
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_sub(1) == 1)
_inputVolumeMetersHandler.reset();
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef--;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)
_inputShowStateChangedRef--;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef--;
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef++;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)
_inputShowStateChangedRef++;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef++;
}
// Function to decrement refcounts for high volume event subscriptions
void EventHandler::ProcessUnsubscription(uint64_t eventSubscriptions)
{
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_sub(1) == 1)
_inputVolumeMetersHandler.reset();
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef--;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)
_inputShowStateChangedRef--;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef--;
}
// Function required in order to use default arguments
void EventHandler::BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion)
{
if (!_broadcastCallback)
if (!_eventCallback)
return;
_broadcastCallback(requiredIntent, eventType, eventData, rpcVersion);
_eventCallback(requiredIntent, eventType, eventData, rpcVersion);
}
// Connect source signals for Inputs, Scenes, and Transitions. Filters are automatically connected.
@ -389,12 +369,21 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
{
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
if (recordOutput) {
signal_handler_t *sh = obs_output_get_signal_handler(recordOutput);
eventHandler->recordFileChangedSignal.Connect(sh, "file_changed", HandleRecordFileChanged,
private_data);
}
}
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
eventHandler->recordFileChangedSignal.Disconnect();
break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_PAUSED);

View File

@ -34,21 +34,26 @@ public:
EventHandler();
~EventHandler();
typedef std::function<void(uint64_t, std::string, json, uint8_t)>
BroadcastCallback; // uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion
void SetBroadcastCallback(BroadcastCallback cb);
typedef std::function<void(bool)> ObsReadyCallback; // bool ready
void SetObsReadyCallback(ObsReadyCallback cb);
void ProcessSubscriptionChange(bool type, uint64_t eventSubscriptions);
void ProcessSubscription(uint64_t eventSubscriptions);
void ProcessUnsubscription(uint64_t eventSubscriptions);
// Callback when an event fires
typedef std::function<void(uint64_t, std::string, json, uint8_t)>
EventCallback; // uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion
inline void SetEventCallback(EventCallback cb) { _eventCallback = cb; }
// Callback when OBS becomes ready or non-ready
typedef std::function<void(bool)> ObsReadyCallback; // bool ready
inline void SetObsReadyCallback(ObsReadyCallback cb) { _obsReadyCallback = cb; }
private:
BroadcastCallback _broadcastCallback;
EventCallback _eventCallback;
ObsReadyCallback _obsReadyCallback;
std::atomic<bool> _obsReady = false;
std::vector<OBSSignal> coreSignals;
OBSSignal recordFileChangedSignal;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
std::atomic<uint64_t> _inputVolumeMetersRef = 0;
std::atomic<uint64_t> _inputActiveStateChangedRef = 0;
@ -124,7 +129,7 @@ private:
calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param,
calldata_t *data); // Direct callback
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
// Transitions
void HandleCurrentSceneTransitionChanged();
@ -146,13 +151,14 @@ private:
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter);
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter);
static void HandleSourceFilterNameChanged(void *param,
calldata_t *data); // Direct callback
calldata_t *data); // Direct callback
void HandleSourceFilterSettingsChanged(obs_source_t *source);
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
// Outputs
void HandleStreamStateChanged(ObsOutputState state);
void HandleRecordStateChanged(ObsOutputState state);
static void HandleRecordFileChanged(void *param, calldata_t *data); // Direct callback
void HandleReplayBufferStateChanged(ObsOutputState state);
void HandleVirtualcamStateChanged(ObsOutputState state);
void HandleReplayBufferSaved();

View File

@ -87,6 +87,28 @@ void EventHandler::HandleRecordStateChanged(ObsOutputState state)
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData);
}
/**
* The record output has started writing to a new file. For example, when a file split happens.
*
* @dataField newOutputPath | String | File name that the output has begun writing to
*
* @eventType RecordFileChanged
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.5.0
* @api events
* @category outputs
*/
void EventHandler::HandleRecordFileChanged(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler *>(param);
json eventData;
eventData["newOutputPath"] = calldata_string(data, "next_file");
eventHandler->BroadcastEvent(EventSubscription::Outputs, "RecordFileChanged", eventData);
}
/**
* The state of the replay buffer output has changed.
*

View File

@ -64,7 +64,7 @@ void ConnectInfo::RefreshData()
QString serverPassword;
if (conf->AuthRequired) {
ui->copyServerPasswordButton->setEnabled(true);
serverPassword = QUrl::toPercentEncoding(conf->ServerPassword);
serverPassword = QUrl::toPercentEncoding(QString::fromStdString(conf->ServerPassword));
} else {
ui->copyServerPasswordButton->setEnabled(false);
serverPassword = obs_module_text("OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText");

View File

@ -123,7 +123,7 @@ void SettingsDialog::RefreshData()
ui->enableDebugLoggingCheckBox->setChecked(conf->DebugEnabled);
ui->serverPortSpinBox->setValue(conf->ServerPort);
ui->enableAuthenticationCheckBox->setChecked(conf->AuthRequired);
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
ui->serverPasswordLineEdit->setText(QString::fromStdString(conf->ServerPassword));
ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired);
ui->generatePasswordButton->setEnabled(conf->AuthRequired);
@ -158,7 +158,7 @@ void SettingsDialog::SaveFormData()
}
// Show a confirmation box to the user if they attempt to provide their own password
if (passwordManuallyEdited && (conf->ServerPassword != ui->serverPasswordLineEdit->text())) {
if (passwordManuallyEdited && (conf->ServerPassword != ui->serverPasswordLineEdit->text().toStdString())) {
QMessageBox msgBox;
msgBox.setWindowTitle(obs_module_text("OBSWebSocket.Settings.Save.UserPasswordWarningTitle"));
msgBox.setText(obs_module_text("OBSWebSocket.Settings.Save.UserPasswordWarningMessage"));
@ -172,22 +172,22 @@ void SettingsDialog::SaveFormData()
break;
case QMessageBox::No:
default:
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
ui->serverPasswordLineEdit->setText(QString::fromStdString(conf->ServerPassword));
return;
}
}
bool needsRestart =
(conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) ||
(conf->ServerPort != ui->serverPortSpinBox->value()) ||
(ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text());
bool needsRestart = (conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) ||
(conf->ServerPort != ui->serverPortSpinBox->value()) ||
(ui->enableAuthenticationCheckBox->isChecked() &&
conf->ServerPassword != ui->serverPasswordLineEdit->text().toStdString());
conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked();
conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked();
conf->DebugEnabled = ui->enableDebugLoggingCheckBox->isChecked();
conf->ServerPort = ui->serverPortSpinBox->value();
conf->AuthRequired = ui->enableAuthenticationCheckBox->isChecked();
conf->ServerPassword = ui->serverPasswordLineEdit->text();
conf->ServerPassword = ui->serverPasswordLineEdit->text().toStdString();
conf->Save();

View File

@ -48,7 +48,9 @@ WebSocketApiPtr _webSocketApi;
WebSocketServerPtr _webSocketServer;
SettingsDialog *_settingsDialog = nullptr;
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData);
void OnWebSocketApiVendorEvent(std::string vendorName, std::string eventType, obs_data_t *obsEventData);
void OnEvent(uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion);
void OnObsReady(bool ready);
bool obs_module_load(void)
{
@ -60,19 +62,30 @@ bool obs_module_load(void)
// Initialize the cpu stats
_cpuUsageInfo = os_cpu_usage_info_start();
// Handle migrations
if (!MigratePersistentData()) {
os_cpu_usage_info_destroy(_cpuUsageInfo);
return false;
}
json migratedConfig = MigrateGlobalConfigData();
// Create the config manager then load the parameters from storage
_config = ConfigPtr(new Config());
_config->Load();
_config = std::make_shared<Config>();
_config->Load(migratedConfig);
// Initialize the event handler
_eventHandler = EventHandlerPtr(new EventHandler());
_eventHandler = std::make_shared<EventHandler>();
_eventHandler->SetEventCallback(OnEvent);
_eventHandler->SetObsReadyCallback(OnObsReady);
// Initialize the plugin/script API
_webSocketApi = WebSocketApiPtr(new WebSocketApi());
_webSocketApi->SetEventCallback(WebSocketApiEventCallback);
_webSocketApi = std::make_shared<WebSocketApi>();
_webSocketApi->SetVendorEventCallback(OnWebSocketApiVendorEvent);
// Initialize the WebSocket server
_webSocketServer = WebSocketServerPtr(new WebSocketServer());
_webSocketServer = std::make_shared<WebSocketServer>();
_webSocketServer->SetClientSubscriptionCallback(std::bind(&EventHandler::ProcessSubscriptionChange, _eventHandler.get(),
std::placeholders::_1, std::placeholders::_2));
// Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string);
@ -90,12 +103,16 @@ bool obs_module_load(void)
}
#ifdef PLUGIN_TESTS
void test_call_request();
void test_register_event_callback();
void test_register_vendor();
#endif
void obs_module_post_load(void)
{
#ifdef PLUGIN_TESTS
test_call_request();
test_register_event_callback();
test_register_vendor();
#endif
@ -116,17 +133,20 @@ void obs_module_unload(void)
_webSocketServer->Stop();
}
// Destroy the WebSocket server
_webSocketServer.reset();
// Release the WebSocket server
_webSocketServer->SetClientSubscriptionCallback(nullptr);
_webSocketServer = nullptr;
// Destroy the plugin/script api
_webSocketApi.reset();
// Release the plugin/script api
_webSocketApi = nullptr;
// Destroy the event handler
_eventHandler.reset();
// Release the event handler
_eventHandler->SetObsReadyCallback(nullptr);
_eventHandler->SetEventCallback(nullptr);
_eventHandler = nullptr;
// Destroy the config manager
_config.reset();
// Release the config manager
_config = nullptr;
// Destroy the cpu stats
os_cpu_usage_info_destroy(_cpuUsageInfo);
@ -182,7 +202,7 @@ bool IsDebugEnabled()
* @api events
* @category general
*/
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData)
void OnWebSocketApiVendorEvent(std::string vendorName, std::string eventType, obs_data_t *obsEventData)
{
json eventData = Utils::Json::ObsDataToJson(obsEventData);
@ -194,13 +214,62 @@ void WebSocketApiEventCallback(std::string vendorName, std::string eventType, ob
_webSocketServer->BroadcastEvent(EventSubscription::Vendors, "VendorEvent", broadcastEventData);
}
// Sent from: EventHandler
void OnEvent(uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion)
{
if (_webSocketServer)
_webSocketServer->BroadcastEvent(requiredIntent, eventType, eventData, rpcVersion);
if (_webSocketApi)
_webSocketApi->BroadcastEvent(requiredIntent, eventType, eventData, rpcVersion);
}
// Sent from: EventHandler
void OnObsReady(bool ready)
{
if (_webSocketServer)
_webSocketServer->SetObsReady(ready);
if (_webSocketApi)
_webSocketApi->SetObsReady(ready);
}
#ifdef PLUGIN_TESTS
void test_call_request()
{
blog(LOG_INFO, "[test_call_request] Testing obs-websocket plugin API request calling...");
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
if (response) {
blog(LOG_INFO, "[test_call_request] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
response->status_code, response->comment, response->response_data);
obs_websocket_request_response_free(response);
} else {
blog(LOG_ERROR, "[test_call_request] Failed to call GetVersion request via obs-websocket plugin API!");
}
blog(LOG_INFO, "[test_call_request] Test done.");
}
static void test_event_cb(uint64_t eventIntent, const char *eventType, const char *eventData, void *priv_data)
{
blog(LOG_DEBUG, "[test_event_cb] New event! Type: %s | Data: %s", eventType, eventData);
UNUSED_PARAMETER(eventIntent);
UNUSED_PARAMETER(priv_data);
}
void test_register_event_callback()
{
blog(LOG_INFO, "[test_register_event_callback] Registering test event callback...");
if (!obs_websocket_register_event_callback(test_event_cb, nullptr))
blog(LOG_ERROR, "[test_register_event_callback] Failed to register event callback!");
blog(LOG_INFO, "[test_register_event_callback] Test done.");
}
static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *responseData, void *priv_data)
{
blog(LOG_INFO, "[test_vendor_request_cb] Request called!");
blog(LOG_INFO, "[test_vendor_request_cb] Request data: %s", obs_data_get_json(requestData));
blog(LOG_INFO, "[test_vendor_request_cb] Request called! Request data: %s", obs_data_get_json(requestData));
// Set an item to the response data
obs_data_set_string(responseData, "test", "pp");
@ -211,34 +280,25 @@ static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *response
void test_register_vendor()
{
blog(LOG_INFO, "[test_register_vendor] Registering test vendor...");
blog(LOG_INFO, "[test_register_vendor] Testing vendor registration...");
// Test plugin API version fetch
uint apiVersion = obs_websocket_get_api_version();
blog(LOG_INFO, "[test_register_vendor] obs-websocket plugin API version: %u", apiVersion);
// Test calling obs-websocket requests
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
if (response) {
blog(LOG_INFO, "[test_register_vendor] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
response->status_code, response->comment, response->response_data);
obs_websocket_request_response_free(response);
}
// Test vendor creation
auto vendor = obs_websocket_register_vendor("obs-websocket-test");
if (!vendor) {
blog(LOG_WARNING, "[test_register_vendor] Failed to create vendor!");
blog(LOG_ERROR, "[test_register_vendor] Failed to create vendor!");
return;
}
// Test vendor request registration
if (!obs_websocket_vendor_register_request(vendor, "TestRequest", test_vendor_request_cb, vendor)) {
blog(LOG_WARNING, "[test_register_vendor] Failed to register vendor request!");
blog(LOG_ERROR, "[test_register_vendor] Failed to register vendor request!");
return;
}
blog(LOG_INFO, "[test_register_vendor] Post load completed.");
blog(LOG_INFO, "[test_register_vendor] Test done.");
}
#endif

View File

@ -26,6 +26,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "utils/Obs.h"
#include "plugin-macros.generated.h"
#define CURRENT_RPC_VERSION 1
struct Config;
typedef std::shared_ptr<Config> ConfigPtr;

View File

@ -37,7 +37,9 @@ struct SerialFrameBatch {
std::condition_variable condition;
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure)
: requestHandler(requestHandler), variables(variables), haltOnFailure(haltOnFailure)
: requestHandler(requestHandler),
variables(variables),
haltOnFailure(haltOnFailure)
{
}
};

View File

@ -177,6 +177,8 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"ToggleRecordPause", &RequestHandler::ToggleRecordPause},
{"PauseRecord", &RequestHandler::PauseRecord},
{"ResumeRecord", &RequestHandler::ResumeRecord},
{"SplitRecordFile", &RequestHandler::SplitRecordFile},
{"CreateRecordChapter", &RequestHandler::CreateRecordChapter},
// Media Inputs
{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},

View File

@ -196,6 +196,8 @@ private:
RequestResult ToggleRecordPause(const Request &);
RequestResult PauseRecord(const Request &);
RequestResult ResumeRecord(const Request &);
RequestResult SplitRecordFile(const Request &);
RequestResult CreateRecordChapter(const Request &);
// Media Inputs
RequestResult GetMediaInputStatus(const Request &);

View File

@ -22,6 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h"
#define GLOBAL_PERSISTENT_DATA_FILE_NAME "persistent_data.json"
/**
* Gets the value of a "slot" from the selected persistent data realm.
*
@ -47,11 +49,11 @@ RequestResult RequestHandler::GetPersistentData(const Request &request)
std::string realm = request.RequestData["realm"];
std::string slotName = request.RequestData["slotName"];
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
std::string persistentDataPath;
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "/../../../obsWebSocketPersistentData.json";
persistentDataPath = Utils::Obs::StringHelper::GetModuleConfigPath(GLOBAL_PERSISTENT_DATA_FILE_NAME);
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json";
persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath() + "/obsWebSocketPersistentData.json";
else
return RequestResult::Error(RequestStatus::ResourceNotFound,
"You have specified an invalid persistent data realm.");
@ -92,16 +94,16 @@ RequestResult RequestHandler::SetPersistentData(const Request &request)
std::string slotName = request.RequestData["slotName"];
json slotValue = request.RequestData["slotValue"];
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
std::string persistentDataPath;
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "/../../../obsWebSocketPersistentData.json";
persistentDataPath = Utils::Obs::StringHelper::GetModuleConfigPath(GLOBAL_PERSISTENT_DATA_FILE_NAME);
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json";
persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath() + "/obsWebSocketPersistentData.json";
else
return RequestResult::Error(RequestStatus::ResourceNotFound,
"You have specified an invalid persistent data realm.");
json persistentData = json::object();
json persistentData;
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
persistentData[slotName] = slotValue;
if (!Utils::Json::SetJsonFileContent(persistentDataPath, persistentData))

View File

@ -266,6 +266,7 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request &request)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No hotkeys were found by that name.");
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true);
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), false);
return RequestResult::Success();
}

View File

@ -21,11 +21,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
static bool VirtualCamAvailable()
{
OBSDataAutoRelease privateData = obs_get_private_data();
if (!privateData)
return false;
return obs_data_get_bool(privateData, "vcamEnabled");
OBSOutputAutoRelease output = obs_frontend_get_virtualcam_output();
return output != nullptr;
}
static bool ReplayBufferAvailable()

View File

@ -189,3 +189,60 @@ RequestResult RequestHandler::ResumeRecord(const Request &)
return RequestResult::Success();
}
/**
* Splits the current file being recorded into a new file.
*
* @requestType SplitRecordFile
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.5.0
* @api requests
* @category record
*/
RequestResult RequestHandler::SplitRecordFile(const Request &)
{
if (!obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
if (!obs_frontend_recording_split_file())
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
"Verify that file splitting is enabled in the output settings.");
return RequestResult::Success();
}
/**
* Adds a new chapter marker to the file currently being recorded.
*
* Note: As of OBS 30.2.0, the only file format supporting this feature is Hybrid MP4.
*
* @requestField ?chapterName | String | Name of the new chapter
*
* @requestType CreateRecordChapter
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.5.0
* @api requests
* @category record
*/
RequestResult RequestHandler::CreateRecordChapter(const Request &request)
{
std::string chapterName;
if (request.Contains("chapterName")) {
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateOptionalString("chapterName", statusCode, comment))
return RequestResult::Error(statusCode, comment);
chapterName = request.RequestData["chapterName"];
}
if (!obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
if (!obs_frontend_recording_add_chapter(chapterName.empty() ? nullptr : chapterName.c_str()))
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
"Verify that the output being used supports chapter markers.");
return RequestResult::Success();
}

View File

@ -106,8 +106,7 @@ RequestResult RequestHandler::GetSceneItemId(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneAutoRelease scene =
request.ValidateScene2(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneAutoRelease scene = request.ValidateScene2(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(scene && request.ValidateString("sourceName", statusCode, comment))) // TODO: Source UUID support
return RequestResult::Error(statusCode, comment);
@ -280,7 +279,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
// Get destination scene
obs_scene_t *destinationScene;
if (request.Contains("destinationSceneName")) {
OBSSourceAutoRelease destinationSceneSource = request.ValidateSource("destinationSceneName", "destinationSceneUuid", statusCode, comment);
OBSSourceAutoRelease destinationSceneSource =
request.ValidateSource("destinationSceneName", "destinationSceneUuid", statusCode, comment);
if (!destinationSceneSource)
return RequestResult::Error(statusCode, comment);
@ -288,7 +288,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
if (obs_source_get_type(destinationSceneSource) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not a scene.");
if (obs_source_is_group(destinationSceneSource))
return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not a scene. (Is group)");
return RequestResult::Error(RequestStatus::InvalidResourceType,
"The specified source is not a scene. (Is group)");
destinationScene = obs_scene_get_ref(obs_scene_from_source(destinationSceneSource));
} else {
@ -308,7 +309,7 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
bool sceneItemEnabled = obs_sceneitem_visible(sceneItem);
obs_transform_info sceneItemTransform;
obs_sceneitem_crop sceneItemCrop;
obs_sceneitem_get_info(sceneItem, &sceneItemTransform);
obs_sceneitem_get_info2(sceneItem, &sceneItemTransform);
obs_sceneitem_get_crop(sceneItem, &sceneItemCrop);
// Create the new item
@ -346,7 +347,8 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -375,7 +377,8 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateObject("sceneItemTransform", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
@ -386,7 +389,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
bool cropChanged = false;
obs_transform_info sceneItemTransform;
obs_sceneitem_crop sceneItemCrop;
obs_sceneitem_get_info(sceneItem, &sceneItemTransform);
obs_sceneitem_get_info2(sceneItem, &sceneItemTransform);
obs_sceneitem_get_crop(sceneItem, &sceneItemCrop);
OBSSource source = obs_sceneitem_get_source(sceneItem);
@ -499,11 +502,18 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
cropChanged = true;
}
if (r.Contains("cropToBounds")) {
if (!r.ValidateOptionalBoolean("cropToBounds", statusCode, comment))
return RequestResult::Error(statusCode, comment);
sceneItemTransform.crop_to_bounds = r.RequestData["cropToBounds"];
transformChanged = true;
}
if (!transformChanged && !cropChanged)
return RequestResult::Error(RequestStatus::CannotAct, "You have not provided any valid transform changes.");
if (transformChanged)
obs_sceneitem_set_info(sceneItem, &sceneItemTransform);
obs_sceneitem_set_info2(sceneItem, &sceneItemTransform);
if (cropChanged)
obs_sceneitem_set_crop(sceneItem, &sceneItemCrop);
@ -533,7 +543,8 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -564,7 +575,8 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateBoolean("sceneItemEnabled", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
@ -597,7 +609,8 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -628,7 +641,8 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateBoolean("sceneItemLocked", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
@ -663,7 +677,8 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -694,7 +709,8 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateNumber("sceneItemIndex", statusCode, comment, 0, 8192)))
return RequestResult::Error(statusCode, comment);
@ -737,7 +753,8 @@ RequestResult RequestHandler::GetSceneItemBlendMode(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -770,7 +787,8 @@ RequestResult RequestHandler::SetSceneItemBlendMode(const Request &request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
@ -789,7 +807,8 @@ RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request &request
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
@ -806,7 +825,8 @@ RequestResult RequestHandler::SetSceneItemPrivateSettings(const Request &request
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
OBSSceneItemAutoRelease sceneItem =
request.ValidateSceneItem(statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem || !request.ValidateObject("sceneItemSettings", statusCode, comment, true))
return RequestResult::Error(statusCode, comment);

View File

@ -28,8 +28,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0)
{
// Get info about the requested source
const uint32_t sourceWidth = obs_source_get_base_width(source);
const uint32_t sourceHeight = obs_source_get_base_height(source);
const uint32_t sourceWidth = obs_source_get_width(source);
const uint32_t sourceHeight = obs_source_get_height(source);
const double sourceAspectRatio = ((double)sourceWidth / (double)sourceHeight);
uint32_t imgWidth = sourceWidth;

View File

@ -211,8 +211,8 @@ bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestSt
return true;
}
obs_source_t *Request::ValidateSource(const std::string &nameKeyName, const std::string &uuidKeyName, RequestStatus::RequestStatus &statusCode,
std::string &comment) const
obs_source_t *Request::ValidateSource(const std::string &nameKeyName, const std::string &uuidKeyName,
RequestStatus::RequestStatus &statusCode, std::string &comment) const
{
if (ValidateString(nameKeyName, statusCode, comment)) {
std::string sourceName = RequestData[nameKeyName];
@ -237,11 +237,13 @@ obs_source_t *Request::ValidateSource(const std::string &nameKeyName, const std:
}
statusCode = RequestStatus::MissingRequestField;
comment = std::string("Your request must contain at least one of the following fields: `") + nameKeyName + "` or `" + uuidKeyName + "`.";
comment = std::string("Your request must contain at least one of the following fields: `") + nameKeyName + "` or `" +
uuidKeyName + "`.";
return nullptr;
}
obs_source_t *Request::ValidateScene(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
obs_source_t *Request::ValidateScene(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter) const
{
obs_source_t *ret = ValidateSource("sceneName", "sceneUuid", statusCode, comment);
if (!ret)
@ -270,7 +272,8 @@ obs_source_t *Request::ValidateScene(RequestStatus::RequestStatus &statusCode, s
return ret;
}
obs_scene_t *Request::ValidateScene2(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
obs_scene_t *Request::ValidateScene2(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter) const
{
OBSSourceAutoRelease sceneSource = ValidateSource("sceneName", "sceneUuid", statusCode, comment);
if (!sceneSource)
@ -338,7 +341,8 @@ FilterPair Request::ValidateFilter(RequestStatus::RequestStatus &statusCode, std
return FilterPair{source, filter};
}
obs_sceneitem_t *Request::ValidateSceneItem(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
obs_sceneitem_t *Request::ValidateSceneItem(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter) const
{
OBSSceneAutoRelease scene = ValidateScene2(statusCode, comment, filter);
if (!scene)
@ -353,7 +357,8 @@ obs_sceneitem_t *Request::ValidateSceneItem(RequestStatus::RequestStatus &status
if (!sceneItem) {
std::string sceneName = obs_source_get_name(obs_scene_get_source(scene));
statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No scene items were found in scene `") + sceneName + "` with the ID `" + std::to_string(sceneItemId) + "`.";
comment = std::string("No scene items were found in scene `") + sceneName + "` with the ID `" +
std::to_string(sceneItemId) + "`.";
return nullptr;
}

View File

@ -64,13 +64,18 @@ struct Request {
const bool allowEmpty = false) const;
// All return values have incremented refcounts
obs_source_t *ValidateSource(const std::string &nameKeyName, const std::string &uuidKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_source_t *ValidateScene(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_scene_t *ValidateScene2(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateSource(const std::string &nameKeyName, const std::string &uuidKeyName,
RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_source_t *ValidateScene(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_scene_t *ValidateScene2(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateInput(RequestStatus::RequestStatus &statusCode, std::string &comment) const;
FilterPair ValidateFilter(RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_sceneitem_t *ValidateSceneItem(RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_output_t *ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_sceneitem_t *ValidateSceneItem(RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_output_t *ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
std::string &comment) const;
std::string RequestType;
bool HasRequestData;

View File

@ -21,6 +21,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData,
RequestBatchExecutionType::RequestBatchExecutionType executionType,
const json &inputVariables, const json &outputVariables)
: Request(requestType, requestData, executionType), InputVariables(inputVariables), OutputVariables(outputVariables)
: Request(requestType, requestData, executionType),
InputVariables(inputVariables),
OutputVariables(outputVariables)
{
}

View File

@ -20,7 +20,10 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestResult.h"
RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment)
: StatusCode(statusCode), ResponseData(responseData), Comment(comment), SleepFrames(0)
: StatusCode(statusCode),
ResponseData(responseData),
Comment(comment),
SleepFrames(0)
{
}

View File

@ -77,5 +77,8 @@ namespace RequestBatchExecutionType {
Parallel = 2,
};
inline bool IsValid(int8_t executionType) { return executionType >= None && executionType <= Parallel; }
inline bool IsValid(int8_t executionType)
{
return executionType >= None && executionType <= Parallel;
}
}

View File

@ -17,8 +17,9 @@ 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 <fstream>
#include "Json.h"
#include "Platform.h"
#include "plugin-macros.generated.h"
bool Utils::Json::JsonArrayIsValidObsArray(const json &j)
@ -177,21 +178,43 @@ json Utils::Json::ObsDataToJson(obs_data_t *d, bool includeDefault)
bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
{
std::string textContent;
if (!Utils::Platform::GetTextFileContent(fileName, textContent))
std::ifstream f(fileName);
if (!f.is_open())
return false;
try {
content = json::parse(textContent);
content = json::parse(f);
} catch (json::parse_error &e) {
blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what());
blog(LOG_WARNING, "[Utils::Json::GetJsonFileContent] Failed to decode content of JSON file `%s`. Error: %s",
fileName.c_str(), e.what());
return false;
}
return true;
}
bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool createNew)
bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool makeDirs)
{
std::string textContent = content.dump(2);
return Utils::Platform::SetTextFileContent(fileName, textContent, createNew);
if (makeDirs) {
std::error_code ec;
auto p = std::filesystem::path(fileName).parent_path();
if (!ec && !std::filesystem::exists(p, ec))
std::filesystem::create_directories(p, ec);
if (ec) {
blog(LOG_ERROR, "[Utils::Json::SetJsonFileContent] Failed to create path directories: %s",
ec.message().c_str());
return false;
}
}
std::ofstream f(fileName);
if (!f.is_open()) {
blog(LOG_ERROR, "[Utils::Json::SetJsonFileContent] Failed to open file `%s` for writing", fileName.c_str());
return false;
}
// Set indent to 2 spaces, then dump content
f << std::setw(2) << content;
return true;
}

View File

@ -76,7 +76,10 @@ namespace Utils {
obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
bool GetJsonFileContent(std::string fileName, json &content);
bool SetJsonFileContent(std::string fileName, const json &content, bool createNew = true);
static inline bool Contains(const json &j, std::string key) { return j.contains(key) && !j[key].is_null(); }
bool SetJsonFileContent(std::string fileName, const json &content, bool makeDirs = true);
static inline bool Contains(const json &j, std::string key)
{
return j.contains(key) && !j[key].is_null();
}
}
}

View File

@ -29,36 +29,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
inline void ___properties_dummy_addref(obs_properties_t *) {}
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t *, ___properties_dummy_addref, obs_properties_destroy>;
#if !defined(OBS_AUTORELEASE)
inline void ___source_dummy_addref(obs_source_t *) {}
inline void ___scene_dummy_addref(obs_scene_t *) {}
inline void ___sceneitem_dummy_addref(obs_sceneitem_t *) {}
inline void ___data_dummy_addref(obs_data_t *) {}
inline void ___data_array_dummy_addref(obs_data_array_t *) {}
inline void ___output_dummy_addref(obs_output_t *) {}
inline void ___encoder_dummy_addref(obs_encoder_t *) {}
inline void ___service_dummy_addref(obs_service_t *) {}
inline void ___weak_source_dummy_addref(obs_weak_source_t *) {}
inline void ___weak_output_dummy_addref(obs_weak_output_t *) {}
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *) {}
inline void ___weak_service_dummy_addref(obs_weak_service_t *) {}
using OBSSourceAutoRelease = OBSRef<obs_source_t *, ___source_dummy_addref, obs_source_release>;
using OBSSceneAutoRelease = OBSRef<obs_scene_t *, ___scene_dummy_addref, obs_scene_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t *, ___sceneitem_dummy_addref, obs_sceneitem_release>;
using OBSDataAutoRelease = OBSRef<obs_data_t *, ___data_dummy_addref, obs_data_release>;
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t *, ___data_array_dummy_addref, obs_data_array_release>;
using OBSOutputAutoRelease = OBSRef<obs_output_t *, ___output_dummy_addref, obs_output_release>;
using OBSEncoderAutoRelease = OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_release>;
using OBSServiceAutoRelease = OBSRef<obs_service_t *, ___service_dummy_addref, obs_service_release>;
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t *, ___weak_source_dummy_addref, obs_weak_source_release>;
using OBSWeakOutputAutoRelease = OBSRef<obs_weak_output_t *, ___weak_output_dummy_addref, obs_weak_output_release>;
using OBSWeakEncoderAutoRelease = OBSRef<obs_weak_encoder_t *, ___weak_encoder_dummy_addref, obs_weak_encoder_release>;
using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref, obs_weak_service_release>;
#endif
template<typename T> T *GetCalldataPointer(const calldata_t *data, const char *name)
{
void *ptr = nullptr;
@ -72,7 +42,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_UNKNOWN
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -82,7 +52,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_STARTING
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -92,7 +62,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_STARTED
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -102,7 +72,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_STOPPING
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -112,7 +82,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_STOPPED
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -122,7 +92,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_RECONNECTING
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -132,7 +102,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_RECONNECTED
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.1.0
* @api enums
*/
@ -142,7 +112,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_PAUSED
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.1.0
* @api enums
*/
@ -152,7 +122,7 @@ enum ObsOutputState {
*
* @enumIdentifier OBS_WEBSOCKET_OUTPUT_RESUMED
* @enumType ObsOutputState
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -176,7 +146,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -186,7 +156,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -196,7 +166,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -206,7 +176,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -216,7 +186,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -226,7 +196,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -236,7 +206,7 @@ enum ObsMediaInputAction {
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
@ -257,6 +227,7 @@ namespace Utils {
namespace Obs {
namespace StringHelper {
std::string GetObsVersion();
std::string GetModuleConfigPath(std::string fileName);
std::string GetCurrentSceneCollection();
std::string GetCurrentProfile();
std::string GetCurrentProfilePath();

View File

@ -33,7 +33,7 @@ static void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform)
obs_sceneitem_set_info(data->sceneItem, data->sceneItemTransform);
obs_sceneitem_set_info2(data->sceneItem, data->sceneItemTransform);
if (data->sceneItemCrop)
obs_sceneitem_set_crop(data->sceneItem, data->sceneItemCrop);

View File

@ -50,7 +50,7 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
obs_transform_info osi;
obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &osi);
obs_sceneitem_get_info2(item, &osi);
obs_sceneitem_get_crop(item, &crop);
OBSSource source = obs_sceneitem_get_source(item);
@ -83,5 +83,7 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
ret["cropTop"] = (int)crop.top;
ret["cropBottom"] = (int)crop.bottom;
ret["cropToBounds"] = osi.crop_to_bounds;
return ret;
}

View File

@ -32,40 +32,43 @@ obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name, std::s
if (context.empty())
return hotkey;
auto type = obs_hotkey_get_registerer_type(hotkey);
if (type == OBS_HOTKEY_REGISTERER_SOURCE) {
OBSSourceAutoRelease source = obs_weak_source_get_source((obs_weak_source_t *)obs_hotkey_get_registerer(hotkey));
OBSSourceAutoRelease source =
obs_weak_source_get_source((obs_weak_source_t *)obs_hotkey_get_registerer(hotkey));
if (!source)
continue;
if (context != obs_source_get_name(source))
if (context != obs_source_get_name(source))
continue;
} else if (type == OBS_HOTKEY_REGISTERER_OUTPUT) {
OBSOutputAutoRelease output = obs_weak_output_get_output((obs_weak_output_t *)obs_hotkey_get_registerer(hotkey));
OBSOutputAutoRelease output =
obs_weak_output_get_output((obs_weak_output_t *)obs_hotkey_get_registerer(hotkey));
if (!output)
continue;
if (context != obs_output_get_name(output))
continue;
} else if (type == OBS_HOTKEY_REGISTERER_ENCODER) {
OBSEncoderAutoRelease encoder = obs_weak_encoder_get_encoder((obs_weak_encoder_t *)obs_hotkey_get_registerer(hotkey));
OBSEncoderAutoRelease encoder =
obs_weak_encoder_get_encoder((obs_weak_encoder_t *)obs_hotkey_get_registerer(hotkey));
if (!encoder)
continue;
if (context != obs_encoder_get_name(encoder))
if (context != obs_encoder_get_name(encoder))
continue;
} else if (type == OBS_HOTKEY_REGISTERER_SERVICE) {
OBSServiceAutoRelease service = obs_weak_service_get_service((obs_weak_service_t *)obs_hotkey_get_registerer(hotkey));
OBSServiceAutoRelease service =
obs_weak_service_get_service((obs_weak_service_t *)obs_hotkey_get_registerer(hotkey));
if (!service)
continue;
if (context != obs_service_get_name(service))
continue;
}
return hotkey;
}

View File

@ -20,15 +20,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <inttypes.h>
#include <QString>
#include <obs-module.h>
#include <util/util.hpp>
#include "Obs.h"
#include "plugin-macros.generated.h"
#define CASE(x) \
case x: \
return #x;
std::string Utils::Obs::StringHelper::GetObsVersion()
{
uint32_t version = obs_get_version();
@ -42,32 +40,34 @@ std::string Utils::Obs::StringHelper::GetObsVersion()
return combined.toStdString();
}
std::string Utils::Obs::StringHelper::GetModuleConfigPath(std::string fileName)
{
BPtr<char> configPath = obs_module_config_path(fileName.c_str());
return std::string(configPath.Get());
}
std::string Utils::Obs::StringHelper::GetCurrentSceneCollection()
{
BPtr<char> sceneCollectionName = obs_frontend_get_current_scene_collection();
std::string ret = sceneCollectionName.Get();
return ret;
return std::string(sceneCollectionName.Get());
}
std::string Utils::Obs::StringHelper::GetCurrentProfile()
{
BPtr<char> profileName = obs_frontend_get_current_profile();
std::string ret = profileName.Get();
return ret;
return std::string(profileName.Get());
}
std::string Utils::Obs::StringHelper::GetCurrentProfilePath()
{
BPtr<char> profilePath = obs_frontend_get_current_profile_path();
std::string ret = profilePath.Get();
return ret;
return std::string(profilePath.Get());
}
std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
{
BPtr<char> recordOutputPath = obs_frontend_get_current_record_output_path();
std::string ret = recordOutputPath.Get();
return ret;
return std::string(recordOutputPath.Get());
}
std::string Utils::Obs::StringHelper::GetLastRecordFileName()
@ -93,15 +93,13 @@ std::string Utils::Obs::StringHelper::GetLastRecordFileName()
std::string Utils::Obs::StringHelper::GetLastReplayBufferFileName()
{
BPtr<char> replayBufferPath = obs_frontend_get_last_replay();
std::string ret = replayBufferPath.Get();
return ret;
return std::string(replayBufferPath.Get());
}
std::string Utils::Obs::StringHelper::GetLastScreenshotFileName()
{
BPtr<char> screenshotPath = obs_frontend_get_last_screenshot();
std::string ret = screenshotPath.Get();
return ret;
return std::string(screenshotPath.Get());
}
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)

View File

@ -238,7 +238,9 @@ void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, callda
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod)
: _updateCallback(cb), _updatePeriod(updatePeriod), _running(false)
: _updateCallback(cb),
_updatePeriod(updatePeriod),
_running(false)
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)

View File

@ -21,7 +21,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QCommandLineParser>
#include <QNetworkInterface>
#include <QHostAddress>
#include <QFile>
#include <obs-frontend-api.h>
#include "Platform.h"
@ -126,27 +125,3 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS
},
(void *)notification, false);
}
bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &content)
{
QFile f(QString::fromStdString(fileName));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return false;
content = f.readAll().toStdString();
return true;
}
bool Utils::Platform::SetTextFileContent(std::string fileName, std::string content, bool createNew)
{
if (!createNew && !QFile::exists(QString::fromStdString(fileName)))
return false;
QFile f(QString::fromStdString(fileName));
if (!f.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QTextStream out(&f);
out << content.c_str();
return true;
}

View File

@ -29,7 +29,5 @@ namespace Utils {
QString GetCommandLineArgument(QString arg);
bool GetCommandLineFlagSet(QString arg);
void SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title, QString body);
bool GetTextFileContent(std::string fileName, std::string &content);
bool SetTextFileContent(std::string filePath, std::string content, bool createNew = true);
}
}

View File

@ -24,7 +24,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-frontend-api.h>
#include "WebSocketServer.h"
#include "../eventhandler/EventHandler.h"
#include "../obs-websocket.h"
#include "../Config.h"
#include "../utils/Crypto.h"
@ -47,23 +46,10 @@ WebSocketServer::WebSocketServer() : QObject(nullptr)
_server.set_close_handler(websocketpp::lib::bind(&WebSocketServer::onClose, this, websocketpp::lib::placeholders::_1));
_server.set_message_handler(websocketpp::lib::bind(&WebSocketServer::onMessage, this, websocketpp::lib::placeholders::_1,
websocketpp::lib::placeholders::_2));
auto eventHandler = GetEventHandler();
if (eventHandler) {
eventHandler->SetBroadcastCallback(std::bind(&WebSocketServer::BroadcastEvent, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
eventHandler->SetObsReadyCallback(std::bind(&WebSocketServer::onObsReady, this, std::placeholders::_1));
}
}
WebSocketServer::~WebSocketServer()
{
auto eventHandler = GetEventHandler();
if (eventHandler) {
eventHandler->SetObsReadyCallback(nullptr);
eventHandler->SetBroadcastCallback(nullptr);
}
if (_server.is_listening())
Stop();
}
@ -97,7 +83,7 @@ void WebSocketServer::Start()
}
_authenticationSalt = Utils::Crypto::GenerateSalt();
_authenticationSecret = Utils::Crypto::GenerateSecret(conf->ServerPassword.toStdString(), _authenticationSalt);
_authenticationSecret = Utils::Crypto::GenerateSecret(conf->ServerPassword, _authenticationSalt);
// Set log levels if debug is enabled
if (IsDebugEnabled()) {
@ -215,11 +201,6 @@ std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocke
return webSocketSessions;
}
void WebSocketServer::onObsReady(bool ready)
{
_obsReady = ready;
}
bool WebSocketServer::onValidate(websocketpp::connection_hdl hdl)
{
auto conn = _server.get_con_from_hdl(hdl);
@ -327,11 +308,9 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
_sessions.erase(hdl);
lock.unlock();
// If client was identified, decrement appropriate refs in eventhandler.
if (isIdentified) {
auto eventHandler = GetEventHandler();
eventHandler->ProcessUnsubscription(eventSubscriptions);
}
// If client was identified, announce unsubscription
if (isIdentified && _clientSubscriptionCallback)
_clientSubscriptionCallback(false, eventSubscriptions);
// Build SessionState object for signal
WebSocketSessionState state;

View File

@ -30,8 +30,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "rpc/WebSocketSession.h"
#include "types/WebSocketCloseCode.h"
#include "types/WebSocketOpCode.h"
#include "../utils/Json.h"
#include "../requesthandler/rpc/Request.h"
#include "../utils/Json.h"
#include "plugin-macros.generated.h"
class WebSocketServer : QObject {
@ -57,12 +57,14 @@ public:
void InvalidateSession(websocketpp::connection_hdl hdl);
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr,
uint8_t rpcVersion = 0);
bool IsListening() { return _server.is_listening(); }
inline void SetObsReady(bool ready) { _obsReady = ready; }
inline bool IsListening() { return _server.is_listening(); }
std::vector<WebSocketSessionState> GetWebSocketSessions();
inline QThreadPool *GetThreadPool() { return &_threadPool; }
QThreadPool *GetThreadPool() { return &_threadPool; }
// Callback for when a client subscribes or unsubscribes. `true` for sub, `false` for unsub
typedef std::function<void(bool, uint64_t)> ClientSubscriptionCallback; // bool type, uint64_t eventSubscriptions
inline void SetClientSubscriptionCallback(ClientSubscriptionCallback cb) { _clientSubscriptionCallback = cb; }
signals:
void ClientConnected(WebSocketSessionState state);
@ -77,7 +79,6 @@ private:
void ServerRunner();
void onObsReady(bool loaded);
bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl);
@ -98,4 +99,6 @@ private:
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions;
std::atomic<bool> _obsReady = false;
ClientSubscriptionCallback _clientSubscriptionCallback;
};

View File

@ -23,7 +23,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "WebSocketServer.h"
#include "../requesthandler/RequestHandler.h"
#include "../requesthandler/RequestBatchHandler.h"
#include "../eventhandler/EventHandler.h"
#include "../obs-websocket.h"
#include "../Config.h"
#include "../utils/Crypto.h"
@ -32,7 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
static bool IsSupportedRpcVersion(uint8_t requestedVersion)
{
return (requestedVersion == 1);
return (requestedVersion == CURRENT_RPC_VERSION);
}
static json ConstructRequestResult(RequestResult requestResult, const json &requestJson)
@ -149,9 +148,9 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
if (ret.closeCode != WebSocketCloseCode::DontClose)
return;
// Increment refs for event subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessSubscription(session->EventSubscriptions());
// Announce subscribe
if (_clientSubscriptionCallback)
_clientSubscriptionCallback(true, session->EventSubscriptions());
// Mark session as identified
session->SetIsIdentified(true);
@ -172,16 +171,17 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
case WebSocketOpCode::Reidentify: { // Reidentify
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
// Decrement refs for current subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessUnsubscription(session->EventSubscriptions());
// Announce unsubscribe
if (_clientSubscriptionCallback)
_clientSubscriptionCallback(false, session->EventSubscriptions());
SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose)
return;
// Increment refs for new subscriptions
eventHandler->ProcessSubscription(session->EventSubscriptions());
// Announce subscribe
if (_clientSubscriptionCallback)
_clientSubscriptionCallback(true, session->EventSubscriptions());
ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();

View File

@ -122,5 +122,8 @@ namespace WebSocketOpCode {
RequestBatchResponse = 9,
};
inline bool IsValid(uint8_t opCode) { return opCode >= Hello && opCode <= RequestBatchResponse; }
inline bool IsValid(uint8_t opCode)
{
return opCode >= Hello && opCode <= RequestBatchResponse;
}
}