Merge branch '4.x-current' into bugfix/crash-get-streaming-time

This commit is contained in:
Stéphane Lepin
2020-03-28 19:17:53 +01:00
33 changed files with 950 additions and 348 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -18,25 +18,25 @@ REM Set up the build flag as undefined.
set "BuildOBS=" set "BuildOBS="
REM Check the last tag successfully built by CI. REM Check the last tag successfully built by CI.
if exist C:\projects\obs-studio-last-tag-built.txt ( if exist "%OBSPath%\obs-studio-last-tag-built.txt" (
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt"
) else ( ) else (
set OBSLastTagBuilt=0 set OBSLastTagBuilt=0
) )
REM If obs-studio directory exists, run git pull and get the latest tag number. REM If obs-studio directory exists, run git pull and get the latest tag number.
if exist C:\projects\obs-studio\ ( if exist %OBSPath% (
echo obs-studio directory exists echo obs-studio directory exists
echo Updating tag info echo Updating tag info
cd C:\projects\obs-studio\ cd /D %OBSPath%
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\latest-obs-studio-tag-pre-pull.txt git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-pre-pull.txt"
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt set /p OBSLatestTagPrePull=<"%OBSPath%\latest-obs-studio-tag-pre-pull.txt"
git checkout master git checkout master
git pull git pull
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\latest-obs-studio-tag-post-pull.txt git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-post-pull.txt"
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt set /p OBSLatestTagPostPull=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt set /p OBSLatestTag=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt echo %OBSLatestTagPostPull%> "%OBSPath%\latest-obs-studio-tag.txt"
) )
REM Check the obs-studio tags for mismatches. REM Check the obs-studio tags for mismatches.
@ -58,22 +58,22 @@ if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
REM If obs-studio directory does not exist, clone the git repo, get the latest REM If obs-studio directory does not exist, clone the git repo, get the latest
REM tag number, and set the build flag. REM tag number, and set the build flag.
if not exist C:\projects\obs-studio ( if not exist %OBSPath% (
echo obs-studio directory does not exist echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio git clone https://github.com/obsproject/obs-studio %OBSPath%
cd C:\projects\obs-studio\ cd /D %OBSPath%\
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\obs-studio-latest-tag.txt git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
set BuildOBS=true set BuildOBS=true
) )
REM If the needed obs-studio libs for this build_config do not exist, REM If the needed obs-studio libs for this build_config do not exist,
REM set the build flag. REM set the build flag.
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib ( if not exist %OBSPath%\build32\libobs\%build_config%\obs.lib (
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
set BuildOBS=true set BuildOBS=true
) )
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib ( if not exist %OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
set BuildOBS=true set BuildOBS=true
) )
@ -95,35 +95,43 @@ echo:
REM If the build flag is set, build obs-studio. REM If the build flag is set, build obs-studio.
if defined BuildOBS ( if defined BuildOBS (
echo Building obs-studio... echo Building obs-studio...
cd /D %OBSPath%
echo git checkout %OBSLatestTag% echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag% git checkout %OBSLatestTag%
echo: echo:
echo Removing previous build dirs... echo Removing previous build dirs...
if exist build rmdir /s /q C:\projects\obs-studio\build if exist build32 rmdir /s /q "%OBSPath%\build32"
if exist build32 rmdir /s /q C:\projects\obs-studio\build32 if exist build64 rmdir /s /q "%OBSPath%\build64"
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
echo Making new build dirs... echo Making new build dirs...
mkdir build
mkdir build32 mkdir build32
mkdir build64 mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit... echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd ./build32 cd build32
cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo: echo:
echo: echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit... echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ../build64 cd ..\build64
cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo: echo:
echo: echo:
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" REM echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)... REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build32\obs-studio.sln
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
REM echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build64\obs-studio.sln
cd .. cd ..
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt git describe --tags --abbrev=0 > "%OBSPath%\obs-studio-last-tag-built.txt"
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt"
) else ( ) else (
echo Last OBS tag built is: %OBSLastTagBuilt% echo Last OBS tag built is: %OBSLastTagBuilt%
echo No need to rebuild OBS. echo No need to rebuild OBS.
) )
dir "%OBSPath%\libobs"

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

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

View File

@ -4,6 +4,9 @@ echo "-- Generating documentation."
echo "-- Node version: $(node -v)" echo "-- Node version: $(node -v)"
echo "-- NPM version: $(npm -v)" echo "-- NPM version: $(npm -v)"
git fetch origin
git checkout ${CHECKOUT_REF/refs\/heads\//}
cd docs cd docs
npm install npm install
npm run build npm run build
@ -15,19 +18,14 @@ if git diff --quiet; then
exit 0 exit 0
fi fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
exit 0
fi
REMOTE_URL="$(git config remote.origin.url)" REMOTE_URL="$(git config remote.origin.url)"
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/} TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO} GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
git config user.name "Travis CI" git config user.name "Azure CI"
git config user.email "$COMMIT_AUTHOR_EMAIL" git config user.email "$COMMIT_AUTHOR_EMAIL"
git add ./generated git add ./generated
git pull git pull
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]" git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH git push -q $GITHUB_REPO

View File

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

View File

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

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

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

View File

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

View File

@ -19,7 +19,6 @@ export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
export LATEST_VERSION="$GIT_BRANCH_OR_TAG" export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
export FILENAME="obs-websocket-$VERSION.pkg" export FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so" echo "[obs-websocket] Modifying obs-websocket.so"
install_name_tool \ install_name_tool \
@ -40,4 +39,3 @@ packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME" echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME

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

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

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

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

View File

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

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

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

View File

@ -9,10 +9,6 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (WIN32 OR APPLE)
include(external/FindLibObs.cmake)
endif()
add_definitions(-DASIO_STANDALONE) add_definitions(-DASIO_STANDALONE)
if (WIN32 OR APPLE) if (WIN32 OR APPLE)
@ -20,8 +16,7 @@ if (WIN32 OR APPLE)
endif() endif()
find_package(LibObs REQUIRED) find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED) find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
find_package(Qt5Widgets REQUIRED)
set(obs-websocket_SOURCES set(obs-websocket_SOURCES
src/obs-websocket.cpp src/obs-websocket.cpp
@ -89,6 +84,10 @@ if(WIN32)
message(FATAL_ERROR "Could not find OBS Frontend API's library !") message(FATAL_ERROR "Could not find OBS Frontend API's library !")
endif() endif()
if(MSVC)
add_compile_options("/MP")
endif()
add_definitions(-D_WEBSOCKETPP_CPP11_STL_) add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(CMAKE_SIZEOF_VOID_P EQUAL 8) if(CMAKE_SIZEOF_VOID_P EQUAL 8)
@ -125,6 +124,16 @@ if(WIN32)
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# In Release mode, copy Qt image format plugins
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
# If config is RelWithDebInfo, package release files # If config is RelWithDebInfo, package release files
COMMAND if $<CONFIG:RelWithDebInfo>==1 ( COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E make_directory "${CMAKE_COMMAND}" -E make_directory

View File

@ -1,40 +0,0 @@
environment:
CURL_VERSION: 7.39.0
install:
- git submodule update --init --recursive
- cd C:\projects\
- if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
- 7z x dependencies2015.zip -odependencies2015
- set DepsPath32=%CD%\dependencies2015\win32
- set DepsPath64=%CD%\dependencies2015\win64
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
- set build_config=RelWithDebInfo
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
- cd C:\projects\obs-websocket\
- mkdir build32
- mkdir build64
- cd ./build32
- cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
- cd ../build64
- cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
build_script:
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
before_deploy:
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
- iscc "C:\projects\obs-websocket\installer\installer.iss"
deploy_script:
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
test: off
cache:
- C:\projects\dependencies2015.zip
- C:\projects\obs-studio-last-tag-built.txt
- C:\projects\obs-studio\

View File

@ -1,12 +1,147 @@
jobs:
- job: 'GenerateDocs'
condition: |
or(
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
eq(variables['Build.SourceBranch'], 'refs/heads/master')
)
pool: pool:
vmImage: 'macOS-10.13' vmImage: 'ubuntu-18.04'
steps:
- checkout: self
submodules: false
- script: ./CI/generate-docs.sh
displayName: 'Generate docs'
env:
CHECKOUT_REF: $(Build.SourceBranch)
GH_TOKEN: $(GithubToken)
- job: 'Build_Windows'
pool:
vmImage: 'windows-2019'
variables:
build_config: RelWithDebInfo
DepsBasePath: 'D:\obsdependencies'
DepsPath32: '$(DepsBasePath)\win32'
DepsPath64: '$(DepsBasePath)\win64'
QtBaseDir: 'D:\QtDep'
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
OBSPath: 'D:\obs-studio'
steps:
- checkout: self
submodules: true
- script: ./CI/install-qt-win.cmd
displayName: 'Install Qt'
env:
QtBaseDir: $(QtBaseDir)
- task: Cache@2
displayName: Restore cached OBS Studio dependencies
inputs:
key: 'obsdeps | "$(Agent.OS)"'
restoreKeys: |
obsdeps | "$(Agent.OS)"
path: $(DepsBasePath)
- script: ./CI/download-obs-deps.cmd
displayName: 'Download OBS Studio dependencies'
- task: Cache@2
displayName: Restore cached OBS Studio builds
inputs:
key: 'obs | "$(Agent.OS)"'
restoreKeys: |
obs | "$(Agent.OS)"
path: $(OBSPath)
- script: ./CI/checkout-cmake-obs-windows.cmd
displayName: 'Checkout & CMake OBS Studio'
env:
build_config: $(build_config)
DepsPath32: $(DepsPath32)
DepsPath64: $(DepsPath64)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build OBS Studio 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build32\obs-studio.sln'
- task: MSBuild@1
displayName: 'Build OBS Studio 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build64\obs-studio.sln'
- script: ./CI/prepare-windows.cmd
displayName: 'CMake obs-websocket'
env:
build_config: $(build_config)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build obs-websocket 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build32\obs-websocket.sln'
- task: MSBuild@1
displayName: 'Build obs-websocket 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build64\obs-websocket.sln'
- script: ./CI/package-windows.cmd
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
displayName: 'Upload package artifacts'
inputs:
pathtoPublish: './package'
artifactName: 'windows_build'
- job: 'Build_Linux'
pool:
vmImage: 'ubuntu-18.04'
variables:
BUILD_REASON: $(Build.Reason)
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
BRANCH_FULL_NAME: $(Build.SourceBranch)
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-ubuntu.sh
displayName: 'Install dependencies'
- script: ./CI/build-ubuntu.sh
displayName: 'Build obs-websocket'
- script: ./CI/package-ubuntu.sh
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './package'
artifactName: 'deb_build'
- job: 'Build_macOS'
pool:
vmImage: 'macos-10.14'
steps: steps:
- checkout: self - checkout: self
submodules: true submodules: true
- script: ./CI/install-dependencies-macos.sh - script: ./CI/install-dependencies-macos.sh
displayName: 'Install Dependencies' displayName: 'Install dependencies'
- script: ./CI/install-build-obs-macos.sh - script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS' displayName: 'Build OBS'
@ -15,9 +150,9 @@ steps:
displayName: 'Build obs-websocket' displayName: 'Build obs-websocket'
- script: ./CI/package-macos.sh - script: ./CI/package-macos.sh
displayName: 'Package' displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
pathtoPublish: './release' pathtoPublish: './release'
artifactName: 'build' artifactName: 'macos_build'

View File

@ -6,9 +6,11 @@
"property": [ "property": [
"{Number} `cy`", "{Number} `cy`",
"{Number} `cx`", "{Number} `cx`",
"{Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.",
"{String} `name` The name of this Scene Item.", "{String} `name` The name of this Scene Item.",
"{int} `id` Scene item ID", "{int} `id` Scene item ID",
"{Boolean} `render` Whether or not this Scene Item is set to \"visible\".", "{Boolean} `render` Whether or not this Scene Item is set to \"visible\".",
"{Boolean} `muted` Whether or not this Scene Item is muted.",
"{Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around", "{Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around",
"{Number} `source_cx`", "{Number} `source_cx`",
"{Number} `source_cy`", "{Number} `source_cy`",
@ -30,6 +32,11 @@
"name": "cx", "name": "cx",
"description": "" "description": ""
}, },
{
"type": "Number",
"name": "alignment",
"description": "The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis."
},
{ {
"type": "String", "type": "String",
"name": "name", "name": "name",
@ -45,6 +52,11 @@
"name": "render", "name": "render",
"description": "Whether or not this Scene Item is set to \"visible\"." "description": "Whether or not this Scene Item is set to \"visible\"."
}, },
{
"type": "Boolean",
"name": "muted",
"description": "Whether or not this Scene Item is muted."
},
{ {
"type": "Boolean", "type": "Boolean",
"name": "locked", "name": "locked",
@ -769,7 +781,7 @@
"return": [ "return": [
"{String} `name` Transition name.", "{String} `name` Transition name.",
"{String} `type` Transition type.", "{String} `type` Transition type.",
"{int} `duration` Transition duration (in milliseconds).", "{int} `duration` Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API.",
"{String} `from-scene` Source scene of the transition", "{String} `from-scene` Source scene of the transition",
"{String} `to-scene` Destination scene of the transition" "{String} `to-scene` Destination scene of the transition"
], ],
@ -791,7 +803,7 @@
{ {
"type": "int", "type": "int",
"name": "duration", "name": "duration",
"description": "Transition duration (in milliseconds)." "description": "Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API."
}, },
{ {
"type": "String", "type": "String",
@ -829,6 +841,134 @@
"lead": "", "lead": "",
"type": "class", "type": "class",
"examples": [] "examples": []
},
{
"subheads": [],
"description": "A transition (other than \"cut\") has ended.\nPlease note that the `from-scene` field is not available in TransitionEnd.",
"return": [
"{String} `name` Transition name.",
"{String} `type` Transition type.",
"{int} `duration` Transition duration (in milliseconds).",
"{String} `to-scene` Destination scene of the transition"
],
"api": "events",
"name": "TransitionEnd",
"category": "transitions",
"since": "4.8.0",
"returns": [
{
"type": "String",
"name": "name",
"description": "Transition name."
},
{
"type": "String",
"name": "type",
"description": "Transition type."
},
{
"type": "int",
"name": "duration",
"description": "Transition duration (in milliseconds)."
},
{
"type": "String",
"name": "to-scene",
"description": "Destination scene of the transition"
}
],
"names": [
{
"name": "",
"description": "TransitionEnd"
}
],
"categories": [
{
"name": "",
"description": "transitions"
}
],
"sinces": [
{
"name": "",
"description": "4.8.0"
}
],
"heading": {
"level": 2,
"text": "TransitionEnd"
},
"lead": "",
"type": "class",
"examples": []
},
{
"subheads": [],
"description": "A stinger transition has finished playing its video.",
"return": [
"{String} `name` Transition name.",
"{String} `type` Transition type.",
"{int} `duration` Transition duration (in milliseconds).",
"{String} `from-scene` Source scene of the transition",
"{String} `to-scene` Destination scene of the transition"
],
"api": "events",
"name": "TransitionVideoEnd",
"category": "transitions",
"since": "4.8.0",
"returns": [
{
"type": "String",
"name": "name",
"description": "Transition name."
},
{
"type": "String",
"name": "type",
"description": "Transition type."
},
{
"type": "int",
"name": "duration",
"description": "Transition duration (in milliseconds)."
},
{
"type": "String",
"name": "from-scene",
"description": "Source scene of the transition"
},
{
"type": "String",
"name": "to-scene",
"description": "Destination scene of the transition"
}
],
"names": [
{
"name": "",
"description": "TransitionVideoEnd"
}
],
"categories": [
{
"name": "",
"description": "transitions"
}
],
"sinces": [
{
"name": "",
"description": "4.8.0"
}
],
"heading": {
"level": 2,
"text": "TransitionVideoEnd"
},
"lead": "",
"type": "class",
"examples": []
} }
], ],
"profiles": [ "profiles": [
@ -2566,6 +2706,67 @@
"type": "class", "type": "class",
"examples": [] "examples": []
}, },
{
"subheads": [],
"description": "An item's locked status has been toggled.",
"return": [
"{String} `scene-name` Name of the scene.",
"{String} `item-name` Name of the item in the scene.",
"{int} `item-id` Scene item ID",
"{boolean} `item-locked` New locked state of the item."
],
"api": "events",
"name": "SceneItemLockChanged",
"category": "sources",
"since": "unreleased",
"returns": [
{
"type": "String",
"name": "scene-name",
"description": "Name of the scene."
},
{
"type": "String",
"name": "item-name",
"description": "Name of the item in the scene."
},
{
"type": "int",
"name": "item-id",
"description": "Scene item ID"
},
{
"type": "boolean",
"name": "item-locked",
"description": "New locked state of the item."
}
],
"names": [
{
"name": "",
"description": "SceneItemLockChanged"
}
],
"categories": [
{
"name": "",
"description": "sources"
}
],
"sinces": [
{
"name": "",
"description": "unreleased"
}
],
"heading": {
"level": 2,
"text": "SceneItemLockChanged"
},
"lead": "",
"type": "class",
"examples": []
},
{ {
"subheads": [], "subheads": [],
"description": "An item's transform has been changed.", "description": "An item's transform has been changed.",
@ -2840,7 +3041,8 @@
"{double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.", "{double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.",
"{String} `obs-websocket-version` obs-websocket plugin version.", "{String} `obs-websocket-version` obs-websocket plugin version.",
"{String} `obs-studio-version` OBS Studio program version.", "{String} `obs-studio-version` OBS Studio program version.",
"{String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")." "{String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\").",
"{String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string"
], ],
"api": "requests", "api": "requests",
"name": "GetVersion", "name": "GetVersion",
@ -2866,6 +3068,11 @@
"type": "String", "type": "String",
"name": "available-requests", "name": "available-requests",
"description": "List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")." "description": "List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")."
},
{
"type": "String",
"name": "supported-image-export-formats",
"description": "List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string"
} }
], ],
"names": [ "names": [
@ -3293,6 +3500,67 @@
"lead": "", "lead": "",
"type": "class", "type": "class",
"examples": [] "examples": []
},
{
"subheads": [],
"description": "Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.",
"param": [
"{String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).",
"{int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.",
"{String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.",
"{String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types)."
],
"api": "requests",
"name": "OpenProjector",
"category": "general",
"since": "unreleased",
"params": [
{
"type": "String (Optional)",
"name": "type",
"description": "Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive)."
},
{
"type": "int (Optional)",
"name": "monitor",
"description": "Monitor to open the projector on. If -1 or omitted, opens a window."
},
{
"type": "String (Optional)",
"name": "geometry",
"description": "Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors."
},
{
"type": "String (Optional)",
"name": "name",
"description": "Name of the source or scene to be displayed (ignored for other projector types)."
}
],
"names": [
{
"name": "",
"description": "OpenProjector"
}
],
"categories": [
{
"name": "",
"description": "general"
}
],
"sinces": [
{
"name": "",
"description": "unreleased"
}
],
"heading": {
"level": 2,
"text": "OpenProjector"
},
"lead": "",
"type": "class",
"examples": []
} }
], ],
"outputs": [ "outputs": [
@ -4131,6 +4399,7 @@
"{int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.", "{int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.",
"{int} `crop.left` The number of pixels cropped off the left of the source before scaling.", "{int} `crop.left` The number of pixels cropped off the left of the source before scaling.",
"{bool} `visible` If the source is visible.", "{bool} `visible` If the source is visible.",
"{bool} `muted` If the source is muted.",
"{bool} `locked` If the source's transform is locked.", "{bool} `locked` If the source's transform is locked.",
"{String} `bounds.type` Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".", "{String} `bounds.type` Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".",
"{int} `bounds.alignment` Alignment of the bounding box.", "{int} `bounds.alignment` Alignment of the bounding box.",
@ -4139,9 +4408,8 @@
"{int} `sourceWidth` Base width (without scaling) of the source", "{int} `sourceWidth` Base width (without scaling) of the source",
"{int} `sourceHeight` Base source (without scaling) of the source", "{int} `sourceHeight` Base source (without scaling) of the source",
"{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)", "{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)",
"{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)" "{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)",
], "{int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.",
"property": [
"{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)",
"{Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)" "{Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)"
], ],
@ -4210,6 +4478,11 @@
"name": "visible", "name": "visible",
"description": "If the source is visible." "description": "If the source is visible."
}, },
{
"type": "bool",
"name": "muted",
"description": "If the source is muted."
},
{ {
"type": "bool", "type": "bool",
"name": "locked", "name": "locked",
@ -4254,6 +4527,21 @@
"type": "double", "type": "double",
"name": "height", "name": "height",
"description": "Scene item height (base source height multiplied by the vertical scaling factor)" "description": "Scene item height (base source height multiplied by the vertical scaling factor)"
},
{
"type": "int",
"name": "alignment",
"description": "The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis."
},
{
"type": "String (optional)",
"name": "parentGroupName",
"description": "Name of the item's parent (if this item belongs to a group)"
},
{
"type": "Array<SceneItemTransform> (optional)",
"name": "groupChildren",
"description": "List of children (if this item is a group)"
} }
], ],
"params": [ "params": [
@ -4268,18 +4556,6 @@
"description": "The name of the source." "description": "The name of the source."
} }
], ],
"properties": [
{
"type": "String (optional)",
"name": "parentGroupName",
"description": "Name of the item's parent (if this item belongs to a group)"
},
{
"type": "Array<SceneItemTransform> (optional)",
"name": "groupChildren",
"description": "List of children (if this item is a group)"
}
],
"names": [ "names": [
{ {
"name": "", "name": "",
@ -7477,9 +7753,9 @@
"{Object (optional)} `stream.settings` Settings for the stream.", "{Object (optional)} `stream.settings` Settings for the stream.",
"{String (optional)} `stream.settings.server` The publish URL.", "{String (optional)} `stream.settings.server` The publish URL.",
"{String (optional)} `stream.settings.key` The publish key of the stream.", "{String (optional)} `stream.settings.key` The publish key of the stream.",
"{boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", "{boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.",
"{String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.", "{String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.",
"{String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`." "{String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`."
], ],
"api": "requests", "api": "requests",
"name": "StartStreaming", "name": "StartStreaming",
@ -7518,18 +7794,18 @@
}, },
{ {
"type": "boolean (optional)", "type": "boolean (optional)",
"name": "stream.settings.use-auth", "name": "stream.settings.use_auth",
"description": "Indicates whether authentication should be used when connecting to the streaming server." "description": "Indicates whether authentication should be used when connecting to the streaming server."
}, },
{ {
"type": "String (optional)", "type": "String (optional)",
"name": "stream.settings.username", "name": "stream.settings.username",
"description": "If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`." "description": "If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`."
}, },
{ {
"type": "String (optional)", "type": "String (optional)",
"name": "stream.settings.password", "name": "stream.settings.password",
"description": "If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`." "description": "If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`."
} }
], ],
"names": [ "names": [
@ -7599,7 +7875,7 @@
"{Object} `settings` The actual settings of the stream.", "{Object} `settings` The actual settings of the stream.",
"{String (optional)} `settings.server` The publish URL.", "{String (optional)} `settings.server` The publish URL.",
"{String (optional)} `settings.key` The publish key.", "{String (optional)} `settings.key` The publish key.",
"{boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", "{boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.",
"{String (optional)} `settings.username` The username for the streaming service.", "{String (optional)} `settings.username` The username for the streaming service.",
"{String (optional)} `settings.password` The password for the streaming service.", "{String (optional)} `settings.password` The password for the streaming service.",
"{boolean} `save` Persist the settings to disk." "{boolean} `save` Persist the settings to disk."
@ -7631,7 +7907,7 @@
}, },
{ {
"type": "boolean (optional)", "type": "boolean (optional)",
"name": "settings.use-auth", "name": "settings.use_auth",
"description": "Indicates whether authentication should be used when connecting to the streaming server." "description": "Indicates whether authentication should be used when connecting to the streaming server."
}, },
{ {
@ -7684,9 +7960,9 @@
"{Object} `settings` Stream settings object.", "{Object} `settings` Stream settings object.",
"{String} `settings.server` The publish URL.", "{String} `settings.server` The publish URL.",
"{String} `settings.key` The publish key of the stream.", "{String} `settings.key` The publish key of the stream.",
"{boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", "{boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.",
"{String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.", "{String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.",
"{String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`." "{String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`."
], ],
"api": "requests", "api": "requests",
"name": "GetStreamSettings", "name": "GetStreamSettings",
@ -7715,18 +7991,18 @@
}, },
{ {
"type": "boolean", "type": "boolean",
"name": "settings.use-auth", "name": "settings.use_auth",
"description": "Indicates whether authentication should be used when connecting to the streaming server." "description": "Indicates whether authentication should be used when connecting to the streaming server."
}, },
{ {
"type": "String", "type": "String",
"name": "settings.username", "name": "settings.username",
"description": "The username to use when accessing the streaming server. Only present if `use-auth` is `true`." "description": "The username to use when accessing the streaming server. Only present if `use_auth` is `true`."
}, },
{ {
"type": "String", "type": "String",
"name": "settings.password", "name": "settings.password",
"description": "The password to use when accessing the streaming server. Only present if `use-auth` is `true`." "description": "The password to use when accessing the streaming server. Only present if `use_auth` is `true`."
} }
], ],
"names": [ "names": [

View File

@ -59,6 +59,8 @@ auth_response = base64_encode(auth_response_hash)
+ [TransitionListChanged](#transitionlistchanged) + [TransitionListChanged](#transitionlistchanged)
+ [TransitionDurationChanged](#transitiondurationchanged) + [TransitionDurationChanged](#transitiondurationchanged)
+ [TransitionBegin](#transitionbegin) + [TransitionBegin](#transitionbegin)
+ [TransitionEnd](#transitionend)
+ [TransitionVideoEnd](#transitionvideoend)
* [Profiles](#profiles) * [Profiles](#profiles)
+ [ProfileChanged](#profilechanged) + [ProfileChanged](#profilechanged)
+ [ProfileListChanged](#profilelistchanged) + [ProfileListChanged](#profilelistchanged)
@ -101,6 +103,7 @@ auth_response = base64_encode(auth_response_hash)
+ [SceneItemAdded](#sceneitemadded) + [SceneItemAdded](#sceneitemadded)
+ [SceneItemRemoved](#sceneitemremoved) + [SceneItemRemoved](#sceneitemremoved)
+ [SceneItemVisibilityChanged](#sceneitemvisibilitychanged) + [SceneItemVisibilityChanged](#sceneitemvisibilitychanged)
+ [SceneItemLockChanged](#sceneitemlockchanged)
+ [SceneItemTransformChanged](#sceneitemtransformchanged) + [SceneItemTransformChanged](#sceneitemtransformchanged)
+ [SceneItemSelected](#sceneitemselected) + [SceneItemSelected](#sceneitemselected)
+ [SceneItemDeselected](#sceneitemdeselected) + [SceneItemDeselected](#sceneitemdeselected)
@ -118,6 +121,7 @@ auth_response = base64_encode(auth_response_hash)
+ [GetStats](#getstats) + [GetStats](#getstats)
+ [BroadcastCustomMessage](#broadcastcustommessage-1) + [BroadcastCustomMessage](#broadcastcustommessage-1)
+ [GetVideoInfo](#getvideoinfo) + [GetVideoInfo](#getvideoinfo)
+ [OpenProjector](#openprojector)
* [Outputs](#outputs) * [Outputs](#outputs)
+ [ListOutputs](#listoutputs) + [ListOutputs](#listoutputs)
+ [GetOutputInfo](#getoutputinfo) + [GetOutputInfo](#getoutputinfo)
@ -222,9 +226,11 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen
| ---- | :---: | ------------| | ---- | :---: | ------------|
| `cy` | _Number_ | | | `cy` | _Number_ | |
| `cx` | _Number_ | | | `cx` | _Number_ | |
| `alignment` | _Number_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. |
| `name` | _String_ | The name of this Scene Item. | | `name` | _String_ | The name of this Scene Item. |
| `id` | _int_ | Scene item ID | | `id` | _int_ | Scene item ID |
| `render` | _Boolean_ | Whether or not this Scene Item is set to "visible". | | `render` | _Boolean_ | Whether or not this Scene Item is set to "visible". |
| `muted` | _Boolean_ | Whether or not this Scene Item is muted. |
| `locked` | _Boolean_ | Whether or not this Scene Item is locked and can't be moved around | | `locked` | _Boolean_ | Whether or not this Scene Item is locked and can't be moved around |
| `source_cx` | _Number_ | | | `source_cx` | _Number_ | |
| `source_cy` | _Number_ | | | `source_cy` | _Number_ | |
@ -429,6 +435,46 @@ A transition (other than "cut") has begun.
**Response Items:** **Response Items:**
| Name | Type | Description |
| ---- | :---: | ------------|
| `name` | _String_ | Transition name. |
| `type` | _String_ | Transition type. |
| `duration` | _int_ | Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API. |
| `from-scene` | _String_ | Source scene of the transition |
| `to-scene` | _String_ | Destination scene of the transition |
---
### TransitionEnd
- Added in v4.8.0
A transition (other than "cut") has ended.
Please note that the `from-scene` field is not available in TransitionEnd.
**Response Items:**
| Name | Type | Description |
| ---- | :---: | ------------|
| `name` | _String_ | Transition name. |
| `type` | _String_ | Transition type. |
| `duration` | _int_ | Transition duration (in milliseconds). |
| `to-scene` | _String_ | Destination scene of the transition |
---
### TransitionVideoEnd
- Added in v4.8.0
A stinger transition has finished playing its video.
**Response Items:**
| Name | Type | Description | | Name | Type | Description |
| ---- | :---: | ------------| | ---- | :---: | ------------|
| `name` | _String_ | Transition name. | | `name` | _String_ | Transition name. |
@ -1030,6 +1076,25 @@ An item's visibility has been toggled.
| `item-visible` | _boolean_ | New visibility state of the item. | | `item-visible` | _boolean_ | New visibility state of the item. |
---
### SceneItemLockChanged
- Unreleased
An item's locked status has been toggled.
**Response Items:**
| Name | Type | Description |
| ---- | :---: | ------------|
| `scene-name` | _String_ | Name of the scene. |
| `item-name` | _String_ | Name of the item in the scene. |
| `item-id` | _int_ | Scene item ID |
| `item-locked` | _boolean_ | New locked state of the item. |
--- ---
### SceneItemTransformChanged ### SceneItemTransformChanged
@ -1159,6 +1224,7 @@ _No specified parameters._
| `obs-websocket-version` | _String_ | obs-websocket plugin version. | | `obs-websocket-version` | _String_ | obs-websocket plugin version. |
| `obs-studio-version` | _String_ | OBS Studio program version. | | `obs-studio-version` | _String_ | OBS Studio program version. |
| `available-requests` | _String_ | List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). | | `available-requests` | _String_ | List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). |
| `supported-image-export-formats` | _String_ | List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string |
--- ---
@ -1333,6 +1399,29 @@ _No specified parameters._
| `colorRange` | _String_ | Color range (full or partial) | | `colorRange` | _String_ | Color range (full or partial) |
---
### OpenProjector
- Unreleased
Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
**Request Fields:**
| Name | Type | Description |
| ---- | :---: | ------------|
| `type` | _String (Optional)_ | Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). |
| `monitor` | _int (Optional)_ | Monitor to open the projector on. If -1 or omitted, opens a window. |
| `geometry` | _String (Optional)_ | Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. |
| `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). |
**Response Items:**
_No additional response items._
--- ---
## Outputs ## Outputs
@ -1791,6 +1880,7 @@ Coordinates are relative to the item's parent (the scene or group it belongs to)
| `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. | | `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. |
| `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. | | `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. |
| `visible` | _bool_ | If the source is visible. | | `visible` | _bool_ | If the source is visible. |
| `muted` | _bool_ | If the source is muted. |
| `locked` | _bool_ | If the source's transform is locked. | | `locked` | _bool_ | If the source's transform is locked. |
| `bounds.type` | _String_ | Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | | `bounds.type` | _String_ | Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". |
| `bounds.alignment` | _int_ | Alignment of the bounding box. | | `bounds.alignment` | _int_ | Alignment of the bounding box. |
@ -1800,6 +1890,9 @@ Coordinates are relative to the item's parent (the scene or group it belongs to)
| `sourceHeight` | _int_ | Base source (without scaling) of the source | | `sourceHeight` | _int_ | Base source (without scaling) of the source |
| `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | | `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) |
| `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | | `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) |
| `alignment` | _int_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. |
| `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) |
| `groupChildren` | _Array&lt;SceneItemTransform&gt; (optional)_ | List of children (if this item is a group) |
--- ---
@ -2903,9 +2996,9 @@ Will return an `error` if streaming is already active.
| `stream.settings` | _Object (optional)_ | Settings for the stream. | | `stream.settings` | _Object (optional)_ | Settings for the stream. |
| `stream.settings.server` | _String (optional)_ | The publish URL. | | `stream.settings.server` | _String (optional)_ | The publish URL. |
| `stream.settings.key` | _String (optional)_ | The publish key of the stream. | | `stream.settings.key` | _String (optional)_ | The publish key of the stream. |
| `stream.settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | | `stream.settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
| `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`. | | `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. |
| `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`. | | `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. |
**Response Items:** **Response Items:**
@ -2947,7 +3040,7 @@ Sets one or more attributes of the current streaming server settings. Any option
| `settings` | _Object_ | The actual settings of the stream. | | `settings` | _Object_ | The actual settings of the stream. |
| `settings.server` | _String (optional)_ | The publish URL. | | `settings.server` | _String (optional)_ | The publish URL. |
| `settings.key` | _String (optional)_ | The publish key. | | `settings.key` | _String (optional)_ | The publish key. |
| `settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | | `settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
| `settings.username` | _String (optional)_ | The username for the streaming service. | | `settings.username` | _String (optional)_ | The username for the streaming service. |
| `settings.password` | _String (optional)_ | The password for the streaming service. | | `settings.password` | _String (optional)_ | The password for the streaming service. |
| `save` | _boolean_ | Persist the settings to disk. | | `save` | _boolean_ | Persist the settings to disk. |
@ -2978,9 +3071,9 @@ _No specified parameters._
| `settings` | _Object_ | Stream settings object. | | `settings` | _Object_ | Stream settings object. |
| `settings.server` | _String_ | The publish URL. | | `settings.server` | _String_ | The publish URL. |
| `settings.key` | _String_ | The publish key of the stream. | | `settings.key` | _String_ | The publish key of the stream. |
| `settings.use-auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. | | `settings.use_auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. |
| `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use-auth` is `true`. | | `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use_auth` is `true`. |
| `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use-auth` is `true`. | | `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use_auth` is `true`. |
--- ---

View File

@ -104,9 +104,11 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
* @typedef {Object} `SceneItem` An OBS Scene Item. * @typedef {Object} `SceneItem` An OBS Scene Item.
* @property {Number} `cy` * @property {Number} `cy`
* @property {Number} `cx` * @property {Number} `cx`
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {String} `name` The name of this Scene Item. * @property {String} `name` The name of this Scene Item.
* @property {int} `id` Scene item ID * @property {int} `id` Scene item ID
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible". * @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around * @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
* @property {Number} `source_cx` * @property {Number} `source_cx`
* @property {Number} `source_cy` * @property {Number} `source_cy`
@ -146,6 +148,8 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
obs_data_set_double(data, "y", pos.y); obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width); obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height); obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
obs_data_set_double(data, "cx", item_width * scale.x); obs_data_set_double(data, "cx", item_width * scale.x);
obs_data_set_double(data, "cy", item_height * scale.y); obs_data_set_double(data, "cy", item_height * scale.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item)); obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
@ -387,6 +391,13 @@ int Utils::GetTransitionDuration(obs_source_t* transition) {
return 0; return 0;
} }
if (obs_transition_fixed(transition)) {
// If this transition has a fixed duration (such as a Stinger),
// we don't currently have a way of retrieving that number.
// For now, return -1 to indicate that we don't know the actual duration.
return -1;
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene); OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
@ -413,6 +424,37 @@ bool Utils::SetTransitionByName(QString transitionName) {
} }
} }
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
int duration = Utils::GetTransitionDuration(transition);
if (duration < 0) {
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
}
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
obs_data_t* transitionData = obs_data_create();
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
obs_data_set_string(transitionData, "type", obs_source_get_id(transition));
obs_data_set_int(transitionData, "duration", duration);
// When a transition starts and while it is running, SOURCE_A is the source scene
// and SOURCE_B is the destination scene.
// Before the transition_end event is triggered on a transition, the destination scene
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
// we don't know what was the source scene
// TODO fix this in libobs
bool isTransitionEndEvent = (sourceScene == destinationScene);
if (!isTransitionEndEvent) {
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene));
}
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
return transitionData;
}
QString Utils::OBSVersionString() { QString Utils::OBSVersionString() {
uint32_t version = obs_get_version(); uint32_t version = obs_get_version();
@ -787,41 +829,6 @@ void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, Pause
if (pauseRecFuncPtr) { if (pauseRecFuncPtr) {
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause"); *pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
} }
os_dlclose(frontendApi);
}
bool Utils::RecordingPauseSupported()
{
RecordingPausedFunction recordingPaused = nullptr;
PauseRecordingFunction pauseRecording = nullptr;
getPauseRecordingFunctions(&recordingPaused, &pauseRecording);
return (recordingPaused && pauseRecording);
}
bool Utils::RecordingPaused()
{
RecordingPausedFunction recordingPaused = nullptr;
getPauseRecordingFunctions(&recordingPaused, nullptr);
if (recordingPaused == nullptr) {
return false;
}
return recordingPaused();
}
void Utils::PauseRecording(bool pause)
{
PauseRecordingFunction pauseRecording = nullptr;
getPauseRecordingFunctions(nullptr, &pauseRecording);
if (pauseRecording == nullptr) {
return;
}
pauseRecording(pause);
} }
QString Utils::nsToTimestamp(uint64_t ns) QString Utils::nsToTimestamp(uint64_t ns)

View File

@ -60,6 +60,7 @@ namespace Utils {
int GetTransitionDuration(obs_source_t* transition); int GetTransitionDuration(obs_source_t* transition);
obs_source_t* GetTransitionFromName(QString transitionName); obs_source_t* GetTransitionFromName(QString transitionName);
bool SetTransitionByName(QString transitionName); bool SetTransitionByName(QString transitionName);
obs_data_t* GetTransitionData(obs_source_t* transition);
QString OBSVersionString(); QString OBSVersionString();
@ -82,9 +83,5 @@ namespace Utils {
const char* GetFilenameFormatting(); const char* GetFilenameFormatting();
bool SetFilenameFormatting(const char* filenameFormatting); bool SetFilenameFormatting(const char* filenameFormatting);
bool RecordingPauseSupported();
bool RecordingPaused();
void PauseRecording(bool pause);
QString nsToTimestamp(uint64_t ns); QString nsToTimestamp(uint64_t ns);
}; };

View File

@ -122,7 +122,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
switch (event) { switch (event) {
case OBS_FRONTEND_EVENT_FINISHED_LOADING: case OBS_FRONTEND_EVENT_FINISHED_LOADING:
owner->hookTransitionBeginEvent(); owner->hookTransitionPlaybackEvents();
break; break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED: case OBS_FRONTEND_EVENT_SCENE_CHANGED:
@ -134,7 +134,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break; break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
owner->hookTransitionBeginEvent(); owner->hookTransitionPlaybackEvents();
owner->OnSceneCollectionChange(); owner->OnSceneCollectionChange();
break; break;
@ -147,7 +147,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break; break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
owner->hookTransitionBeginEvent(); owner->hookTransitionPlaybackEvents();
owner->OnTransitionListChange(); owner->OnTransitionListChange();
break; break;
@ -231,7 +231,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break; break;
case OBS_FRONTEND_EVENT_EXIT: case OBS_FRONTEND_EVENT_EXIT:
owner->unhookTransitionBeginEvent(); owner->unhookTransitionPlaybackEvents();
owner->OnExit(); owner->OnExit();
break; break;
} }
@ -282,6 +282,8 @@ void WSEvents::connectSourceSignals(obs_source_t* source) {
signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this);
signal_handler_connect(sh, signal_handler_connect(sh,
"item_visible", OnSceneItemVisibilityChanged, this); "item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_connect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_connect(sh, "item_select", OnSceneItemSelected, this); signal_handler_connect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this); signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
@ -311,11 +313,15 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) {
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
signal_handler_disconnect(sh, signal_handler_disconnect(sh,
"item_visible", OnSceneItemVisibilityChanged, this); "item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_disconnect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this); signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this); signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
} }
void WSEvents::connectFilterSignals(obs_source_t* filter) { void WSEvents::connectFilterSignals(obs_source_t* filter) {
@ -338,7 +344,7 @@ void WSEvents::disconnectFilterSignals(obs_source_t* filter) {
signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this); signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this);
} }
void WSEvents::hookTransitionBeginEvent() { void WSEvents::hookTransitionPlaybackEvents() {
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
@ -347,12 +353,16 @@ void WSEvents::hookTransitionBeginEvent() {
signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_t* sh = obs_source_get_signal_handler(transition);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this); signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
} }
obs_frontend_source_list_free(&transitions); obs_frontend_source_list_free(&transitions);
} }
void WSEvents::unhookTransitionBeginEvent() { void WSEvents::unhookTransitionPlaybackEvents() {
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
@ -360,6 +370,8 @@ void WSEvents::unhookTransitionBeginEvent() {
obs_source_t* transition = transitions.sources.array[i]; obs_source_t* transition = transitions.sources.array[i];
signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_t* sh = obs_source_get_signal_handler(transition);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
} }
obs_frontend_source_list_free(&transitions); obs_frontend_source_list_free(&transitions);
@ -742,7 +754,7 @@ void WSEvents::OnExit() {
void WSEvents::StreamStatus() { void WSEvents::StreamStatus() {
bool streamingActive = obs_frontend_streaming_active(); bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active(); bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused(); bool recordingPaused = obs_frontend_recording_paused();
bool replayBufferActive = obs_frontend_replay_buffer_active(); bool replayBufferActive = obs_frontend_replay_buffer_active();
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -826,7 +838,7 @@ void WSEvents::Heartbeat() {
bool streamingActive = obs_frontend_streaming_active(); bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active(); bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused(); bool recordingPaused = obs_frontend_recording_paused();
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
@ -886,6 +898,8 @@ void WSEvents::TransitionDurationChanged(int ms) {
* @return {String} `name` Transition name. * @return {String} `name` Transition name.
* @return {String} `type` Transition type. * @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds). * @return {int} `duration` Transition duration (in milliseconds).
* Will be -1 for any transition with a fixed duration,
* such as a Stinger, due to limitations of the OBS API.
* @return {String} `from-scene` Source scene of the transition * @return {String} `from-scene` Source scene of the transition
* @return {String} `to-scene` Destination scene of the transition * @return {String} `to-scene` Destination scene of the transition
* *
@ -902,29 +916,62 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
return; return;
} }
int duration = Utils::GetTransitionDuration(transition); OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
if (duration < 0) {
blog(LOG_WARNING, "OnTransitionBegin: duration is negative !");
}
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "name", obs_source_get_name(transition));
obs_data_set_string(fields, "type", obs_source_get_id(transition));
obs_data_set_int(fields, "duration", duration);
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
if (sourceScene) {
obs_data_set_string(fields, "from-scene", obs_source_get_name(sourceScene));
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
if (destinationScene) {
obs_data_set_string(fields, "to-scene", obs_source_get_name(destinationScene));
}
instance->broadcastUpdate("TransitionBegin", fields); instance->broadcastUpdate("TransitionBegin", fields);
} }
/**
* A transition (other than "cut") has ended.
* Please note that the `from-scene` field is not available in TransitionEnd.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionEnd", fields);
}
/**
* A stinger transition has finished playing its video.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `from-scene` Source scene of the transition
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionVideoEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionVideoEnd", fields);
}
/** /**
* A source has been created. A source can be an input, a scene or a transition. * A source has been created. A source can be an input, a scene or a transition.
* *
@ -1435,6 +1482,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
instance->broadcastUpdate("SceneItemVisibilityChanged", fields); instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
} }
/**
* An item's locked status has been toggled.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item in the scene.
* @return {int} `item-id` Scene item ID
* @return {boolean} `item-locked` New locked state of the item.
*
* @api events
* @name SceneItemLockChanged
* @category sources
* @since unreleased
*/
void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
obs_scene_t* scene = nullptr;
calldata_get_ptr(data, "scene", &scene);
obs_sceneitem_t* sceneItem = nullptr;
calldata_get_ptr(data, "item", &sceneItem);
bool locked = false;
calldata_get_bool(data, "locked", &locked);
const char* sceneName =
obs_source_get_name(obs_scene_get_source(scene));
const char* sceneItemName =
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "scene-name", sceneName);
obs_data_set_string(fields, "item-name", sceneItemName);
obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem));
obs_data_set_bool(fields, "item-locked", locked);
instance->broadcastUpdate("SceneItemLockChanged", fields);
}
/** /**
* An item's transform has been changed. * An item's transform has been changed.
* *

View File

@ -43,8 +43,8 @@ public:
void connectFilterSignals(obs_source_t* filter); void connectFilterSignals(obs_source_t* filter);
void disconnectFilterSignals(obs_source_t* filter); void disconnectFilterSignals(obs_source_t* filter);
void hookTransitionBeginEvent(); void hookTransitionPlaybackEvents();
void unhookTransitionBeginEvent(); void unhookTransitionPlaybackEvents();
uint64_t getStreamingTime(); uint64_t getStreamingTime();
uint64_t getRecordingTime(); uint64_t getRecordingTime();
@ -116,6 +116,8 @@ private:
enum obs_frontend_event event, void* privateData); enum obs_frontend_event event, void* privateData);
static void OnTransitionBegin(void* param, calldata_t* data); static void OnTransitionBegin(void* param, calldata_t* data);
static void OnTransitionEnd(void* param, calldata_t* data);
static void OnTransitionVideoEnd(void* param, calldata_t* data);
static void OnSourceCreate(void* param, calldata_t* data); static void OnSourceCreate(void* param, calldata_t* data);
static void OnSourceDestroy(void* param, calldata_t* data); static void OnSourceDestroy(void* param, calldata_t* data);
@ -136,6 +138,7 @@ private:
static void OnSceneItemAdd(void* param, calldata_t* data); static void OnSceneItemAdd(void* param, calldata_t* data);
static void OnSceneItemDelete(void* param, calldata_t* data); static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data); static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
static void OnSceneItemLockChanged(void* param, calldata_t* data);
static void OnSceneItemTransform(void* param, calldata_t* data); static void OnSceneItemTransform(void* param, calldata_t* data);
static void OnSceneItemSelected(void* param, calldata_t* data); static void OnSceneItemSelected(void* param, calldata_t* data);
static void OnSceneItemDeselected(void* param, calldata_t* data); static void OnSceneItemDeselected(void* param, calldata_t* data);

View File

@ -36,6 +36,7 @@ const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap {
{ "GetStats", &WSRequestHandler::GetStats }, { "GetStats", &WSRequestHandler::GetStats },
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat }, { "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo }, { "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
{ "OpenProjector", &WSRequestHandler::OpenProjector },
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting }, { "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting }, { "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },

View File

@ -54,6 +54,7 @@ class WSRequestHandler {
RpcResponse GetStats(const RpcRequest&); RpcResponse GetStats(const RpcRequest&);
RpcResponse SetHeartbeat(const RpcRequest&); RpcResponse SetHeartbeat(const RpcRequest&);
RpcResponse GetVideoInfo(const RpcRequest&); RpcResponse GetVideoInfo(const RpcRequest&);
RpcResponse OpenProjector(const RpcRequest&);
RpcResponse SetFilenameFormatting(const RpcRequest&); RpcResponse SetFilenameFormatting(const RpcRequest&);
RpcResponse GetFilenameFormatting(const RpcRequest&); RpcResponse GetFilenameFormatting(const RpcRequest&);

View File

@ -1,10 +1,13 @@
#include "WSRequestHandler.h"
#include <QtCore/QByteArray>
#include <QtGui/QImageWriter>
#include "obs-websocket.h" #include "obs-websocket.h"
#include "Config.h" #include "Config.h"
#include "Utils.h" #include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
#include "WSRequestHandler.h"
#define CASE(x) case x: return #x; #define CASE(x) case x: return #x;
const char *describe_output_format(int format) { const char *describe_output_format(int format) {
switch (format) { switch (format) {
@ -60,6 +63,7 @@ const char *describe_scale_type(int scale) {
* @return {String} `obs-websocket-version` obs-websocket plugin version. * @return {String} `obs-websocket-version` obs-websocket plugin version.
* @return {String} `obs-studio-version` OBS Studio program version. * @return {String} `obs-studio-version` OBS Studio program version.
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). * @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
* *
* @api requests * @api requests
* @name GetVersion * @name GetVersion
@ -70,20 +74,28 @@ RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
QString obsVersion = Utils::OBSVersionString(); QString obsVersion = Utils::OBSVersionString();
QList<QString> names = messageMap.keys(); QList<QString> names = messageMap.keys();
names.sort(Qt::CaseInsensitive); QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
// (Palakis) OBS' data arrays only support object arrays, so I improvised. // (Palakis) OBS' data arrays only support object arrays, so I improvised.
QString requests; QString requests;
names.sort(Qt::CaseInsensitive);
requests += names.takeFirst(); requests += names.takeFirst();
for (QString reqName : names) { for (const QString& reqName : names) {
requests += ("," + reqName); requests += ("," + reqName);
} }
QString supportedImageExportFormats;
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
for (const QByteArray& format : imageWriterFormats) {
supportedImageExportFormats += ("," + QString::fromUtf8(format));
}
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_double(data, "version", 1.1); obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION); obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8()); obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8()); obs_data_set_string(data, "available-requests", requests.toUtf8());
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
return request.success(data); return request.success(data);
} }
@ -304,3 +316,31 @@ RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
return request.success(response); return request.success(response);
} }
/**
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
*
* @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
*
* @api requests
* @name OpenProjector
* @category general
* @since unreleased
*/
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
const char* type = obs_data_get_string(request.parameters(), "type");
int monitor = -1;
if (request.hasField("monitor")) {
monitor = obs_data_get_int(request.parameters(), "monitor");
}
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
const char* name = obs_data_get_string(request.parameters(), "name");
obs_frontend_open_projector(type, monitor, geometry, name);
return request.success();
}

View File

@ -10,10 +10,6 @@ RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> c
return request.failed("recording is not active"); return request.failed("recording is not active");
} }
if (!Utils::RecordingPauseSupported()) {
return request.failed("recording pauses are not available in this version of OBS Studio");
}
return callback(); return callback();
} }
@ -77,11 +73,11 @@ RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
*/ */
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) { RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() { return ifCanPause(request, [request]() {
if (Utils::RecordingPaused()) { if (obs_frontend_recording_paused()) {
return request.failed("recording already paused"); return request.failed("recording already paused");
} }
Utils::PauseRecording(true); obs_frontend_recording_pause(true);
return request.success(); return request.success();
}); });
} }
@ -97,11 +93,11 @@ RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
*/ */
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) { RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() { return ifCanPause(request, [request]() {
if (!Utils::RecordingPaused()) { if (!obs_frontend_recording_paused()) {
return request.failed("recording is not paused"); return request.failed("recording is not paused");
} }
Utils::PauseRecording(false); obs_frontend_recording_pause(false);
return request.success(); return request.success();
}); });
} }

View File

@ -21,6 +21,7 @@
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. * @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
* @return {bool} `visible` If the source is visible. * @return {bool} `visible` If the source is visible.
* @return {bool} `muted` If the source is muted.
* @return {bool} `locked` If the source's transform is locked. * @return {bool} `locked` If the source's transform is locked.
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @return {int} `bounds.alignment` Alignment of the bounding box. * @return {int} `bounds.alignment` Alignment of the bounding box.
@ -30,8 +31,9 @@
* @return {int} `sourceHeight` Base source (without scaling) of the source * @return {int} `sourceHeight` Base source (without scaling) of the source
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor) * @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor) * @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) * @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group) * @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
* *
* @api requests * @api requests
* @name GetSceneItemProperties * @name GetSceneItemProperties

View File

@ -335,7 +335,7 @@ RpcResponse WSRequestHandler::SetSyncOffset(const RpcRequest& request)
QString sourceName = obs_data_get_string(request.parameters(), "source"); QString sourceName = obs_data_get_string(request.parameters(), "source");
int64_t sourceSyncOffset = (int64_t)obs_data_get_int(request.parameters(), "offset"); int64_t sourceSyncOffset = (int64_t)obs_data_get_int(request.parameters(), "offset");
if (sourceName.isEmpty() || sourceSyncOffset < 0) { if (sourceName.isEmpty()) {
return request.failed("invalid request parameters"); return request.failed("invalid request parameters");
} }

View File

@ -26,7 +26,7 @@ RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "recording-paused", Utils::RecordingPaused()); obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
obs_data_set_bool(data, "preview-only", false); obs_data_set_bool(data, "preview-only", false);
if (obs_frontend_streaming_active()) { if (obs_frontend_streaming_active()) {
@ -67,9 +67,9 @@ RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
* @param {Object (optional)} `stream.settings` Settings for the stream. * @param {Object (optional)} `stream.settings` Settings for the stream.
* @param {String (optional)} `stream.settings.server` The publish URL. * @param {String (optional)} `stream.settings.server` The publish URL.
* @param {String (optional)} `stream.settings.key` The publish key of the stream. * @param {String (optional)} `stream.settings.key` The publish key of the stream.
* @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`. * @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`. * @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`.
* *
* @api requests * @api requests
* @name StartStreaming * @name StartStreaming
@ -188,7 +188,7 @@ RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
* @param {Object} `settings` The actual settings of the stream. * @param {Object} `settings` The actual settings of the stream.
* @param {String (optional)} `settings.server` The publish URL. * @param {String (optional)} `settings.server` The publish URL.
* @param {String (optional)} `settings.key` The publish key. * @param {String (optional)} `settings.key` The publish key.
* @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `settings.username` The username for the streaming service. * @param {String (optional)} `settings.username` The username for the streaming service.
* @param {String (optional)} `settings.password` The password for the streaming service. * @param {String (optional)} `settings.password` The password for the streaming service.
* @param {boolean} `save` Persist the settings to disk. * @param {boolean} `save` Persist the settings to disk.
@ -213,6 +213,7 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service); OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
service = obs_service_create( service = obs_service_create(
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys); requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
obs_frontend_set_streaming_service(service);
} else { } else {
// If type isn't changing, we should overlay the settings we got // If type isn't changing, we should overlay the settings we got
// to the existing settings. By doing so, you can send a request that // to the existing settings. By doing so, you can send a request that
@ -235,10 +236,12 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
obs_frontend_save_streaming_service(); obs_frontend_save_streaming_service();
} }
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service); OBSService responseService = obs_frontend_get_streaming_service();
OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService);
const char* responseType = obs_service_get_type(responseService);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", requestedType.toUtf8()); obs_data_set_string(response, "type", responseType);
obs_data_set_obj(response, "settings", serviceSettings); obs_data_set_obj(response, "settings", serviceSettings);
return request.success(response); return request.success(response);
@ -251,9 +254,9 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
* @return {Object} `settings` Stream settings object. * @return {Object} `settings` Stream settings object.
* @return {String} `settings.server` The publish URL. * @return {String} `settings.server` The publish URL.
* @return {String} `settings.key` The publish key of the stream. * @return {String} `settings.key` The publish key of the stream.
* @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. * @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`. * @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`. * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`.
* *
* @api requests * @api requests
* @name GetStreamSettings * @name GetStreamSettings

View File

@ -133,7 +133,11 @@ RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(true); obs_frontend_set_preview_program_mode(true);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success(); return request.success();
} }
@ -146,7 +150,12 @@ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(false); obs_frontend_set_preview_program_mode(false);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success(); return request.success();
} }
@ -159,7 +168,12 @@ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) { RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
bool previewProgramMode = obs_frontend_preview_program_mode_active(); bool previewProgramMode = obs_frontend_preview_program_mode_active();
obs_frontend_set_preview_program_mode(!previewProgramMode); obs_frontend_set_preview_program_mode(!previewProgramMode);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success(); return request.success();
} }

View File

@ -56,6 +56,6 @@ ConfigPtr GetConfig();
WSServerPtr GetServer(); WSServerPtr GetServer();
WSEventsPtr GetEventsSystem(); WSEventsPtr GetEventsSystem();
#define OBS_WEBSOCKET_VERSION "4.7.0" #define OBS_WEBSOCKET_VERSION "4.8.0"
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)