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/
/build64/
/release/
/package/
/installer/Output/
.idea
.vscode

View File

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

View File

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

View File

@ -18,25 +18,25 @@ REM Set up the build flag as undefined.
set "BuildOBS="
REM Check the last tag successfully built by CI.
if exist C:\projects\obs-studio-last-tag-built.txt (
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
if exist "%OBSPath%\obs-studio-last-tag-built.txt" (
set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt"
) else (
set OBSLastTagBuilt=0
)
REM If obs-studio directory exists, run git pull and get the latest tag number.
if exist C:\projects\obs-studio\ (
if exist %OBSPath% (
echo obs-studio directory exists
echo Updating tag info
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\latest-obs-studio-tag-pre-pull.txt
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
cd /D %OBSPath%
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-pre-pull.txt"
set /p OBSLatestTagPrePull=<"%OBSPath%\latest-obs-studio-tag-pre-pull.txt"
git checkout master
git pull
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-post-pull.txt"
set /p OBSLatestTagPostPull=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
set /p OBSLatestTag=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
echo %OBSLatestTagPostPull%> "%OBSPath%\latest-obs-studio-tag.txt"
)
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 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
git clone https://github.com/obsproject/obs-studio
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\obs-studio-latest-tag.txt
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
git clone https://github.com/obsproject/obs-studio %OBSPath%
cd /D %OBSPath%\
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
set BuildOBS=true
)
REM If the needed obs-studio libs for this build_config do not exist,
REM set the build flag.
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
if not exist %OBSPath%\build32\libobs\%build_config%\obs.lib (
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
set BuildOBS=true
)
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
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
set BuildOBS=true
)
@ -95,35 +95,43 @@ echo:
REM If the build flag is set, build obs-studio.
if defined BuildOBS (
echo Building obs-studio...
cd /D %OBSPath%
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
echo Removing previous build dirs...
if exist build rmdir /s /q C:\projects\obs-studio\build
if exist build32 rmdir /s /q C:\projects\obs-studio\build32
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
echo Making new build dirs...
mkdir build
echo Removing previous build dirs...
if exist build32 rmdir /s /q "%OBSPath%\build32"
if exist build64 rmdir /s /q "%OBSPath%\build64"
echo Making new build dirs...
mkdir build32
mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd ./build32
cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ../build64
cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
cd ..
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
REM echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build32\obs-studio.sln
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 ..
git describe --tags --abbrev=0 > "%OBSPath%\obs-studio-last-tag-built.txt"
set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt"
) else (
echo Last OBS tag built is: %OBSLastTagBuilt%
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 "-- NPM version: $(npm -v)"
git fetch origin
git checkout ${CHECKOUT_REF/refs\/heads\//}
cd docs
npm install
npm run build
@ -15,19 +18,14 @@ if git diff --quiet; then
exit 0
fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
exit 0
fi
REMOTE_URL="$(git config remote.origin.url)"
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
git config user.name "Travis CI"
git config user.name "Azure CI"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git add ./generated
git pull
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO

View File

@ -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 FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so"
install_name_tool \
@ -40,4 +39,3 @@ packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME

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

View File

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

View File

@ -1,23 +1,158 @@
pool:
vmImage: 'macOS-10.13'
jobs:
- job: 'GenerateDocs'
condition: |
or(
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
eq(variables['Build.SourceBranch'], 'refs/heads/master')
)
pool:
vmImage: 'ubuntu-18.04'
steps:
- checkout: self
submodules: false
steps:
- checkout: self
submodules: true
- script: ./CI/generate-docs.sh
displayName: 'Generate docs'
env:
CHECKOUT_REF: $(Build.SourceBranch)
GH_TOKEN: $(GithubToken)
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install Dependencies'
- 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-build-obs-macos.sh
displayName: 'Build OBS'
- script: ./CI/install-qt-win.cmd
displayName: 'Install Qt'
env:
QtBaseDir: $(QtBaseDir)
- script: ./CI/build-macos.sh
displayName: 'Build obs-websocket'
- task: Cache@2
displayName: Restore cached OBS Studio dependencies
inputs:
key: 'obsdeps | "$(Agent.OS)"'
restoreKeys: |
obsdeps | "$(Agent.OS)"
path: $(DepsBasePath)
- script: ./CI/package-macos.sh
displayName: 'Package'
- script: ./CI/download-obs-deps.cmd
displayName: 'Download OBS Studio dependencies'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'build'
- 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:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install dependencies'
- script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS'
- script: ./CI/build-macos.sh
displayName: 'Build obs-websocket'
- script: ./CI/package-macos.sh
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'macos_build'

View File

@ -6,9 +6,11 @@
"property": [
"{Number} `cy`",
"{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.",
"{int} `id` Scene item ID",
"{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",
"{Number} `source_cx`",
"{Number} `source_cy`",
@ -30,6 +32,11 @@
"name": "cx",
"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",
"name": "name",
@ -45,6 +52,11 @@
"name": "render",
"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",
"name": "locked",
@ -769,7 +781,7 @@
"return": [
"{String} `name` Transition name.",
"{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} `to-scene` Destination scene of the transition"
],
@ -791,7 +803,7 @@
{
"type": "int",
"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",
@ -829,6 +841,134 @@
"lead": "",
"type": "class",
"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": [
@ -2566,6 +2706,67 @@
"type": "class",
"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": [],
"description": "An item's transform has been changed.",
@ -2840,7 +3041,8 @@
"{double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.",
"{String} `obs-websocket-version` obs-websocket plugin 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",
"name": "GetVersion",
@ -2866,6 +3068,11 @@
"type": "String",
"name": "available-requests",
"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": [
@ -3293,6 +3500,67 @@
"lead": "",
"type": "class",
"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": [
@ -4131,6 +4399,7 @@
"{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.",
"{bool} `visible` If the source is visible.",
"{bool} `muted` If the source is muted.",
"{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\".",
"{int} `bounds.alignment` Alignment of the bounding box.",
@ -4139,9 +4408,8 @@
"{int} `sourceWidth` Base width (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} `height` Scene item height (base source height multiplied by the vertical scaling factor)"
],
"property": [
"{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.",
"{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)"
],
@ -4210,6 +4478,11 @@
"name": "visible",
"description": "If the source is visible."
},
{
"type": "bool",
"name": "muted",
"description": "If the source is muted."
},
{
"type": "bool",
"name": "locked",
@ -4254,6 +4527,21 @@
"type": "double",
"name": "height",
"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": [
@ -4268,18 +4556,6 @@
"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": [
{
"name": "",
@ -7477,9 +7753,9 @@
"{Object (optional)} `stream.settings` Settings for the stream.",
"{String (optional)} `stream.settings.server` The publish URL.",
"{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.",
"{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`."
"{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.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`."
],
"api": "requests",
"name": "StartStreaming",
@ -7518,18 +7794,18 @@
},
{
"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."
},
{
"type": "String (optional)",
"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)",
"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": [
@ -7599,7 +7875,7 @@
"{Object} `settings` The actual settings of the stream.",
"{String (optional)} `settings.server` The publish URL.",
"{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.password` The password for the streaming service.",
"{boolean} `save` Persist the settings to disk."
@ -7631,7 +7907,7 @@
},
{
"type": "boolean (optional)",
"name": "settings.use-auth",
"name": "settings.use_auth",
"description": "Indicates whether authentication should be used when connecting to the streaming server."
},
{
@ -7684,9 +7960,9 @@
"{Object} `settings` Stream settings object.",
"{String} `settings.server` The publish URL.",
"{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.",
"{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`."
"{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.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`."
],
"api": "requests",
"name": "GetStreamSettings",
@ -7715,18 +7991,18 @@
},
{
"type": "boolean",
"name": "settings.use-auth",
"name": "settings.use_auth",
"description": "Indicates whether authentication should be used when connecting to the streaming server."
},
{
"type": "String",
"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",
"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": [

View File

@ -59,6 +59,8 @@ auth_response = base64_encode(auth_response_hash)
+ [TransitionListChanged](#transitionlistchanged)
+ [TransitionDurationChanged](#transitiondurationchanged)
+ [TransitionBegin](#transitionbegin)
+ [TransitionEnd](#transitionend)
+ [TransitionVideoEnd](#transitionvideoend)
* [Profiles](#profiles)
+ [ProfileChanged](#profilechanged)
+ [ProfileListChanged](#profilelistchanged)
@ -101,6 +103,7 @@ auth_response = base64_encode(auth_response_hash)
+ [SceneItemAdded](#sceneitemadded)
+ [SceneItemRemoved](#sceneitemremoved)
+ [SceneItemVisibilityChanged](#sceneitemvisibilitychanged)
+ [SceneItemLockChanged](#sceneitemlockchanged)
+ [SceneItemTransformChanged](#sceneitemtransformchanged)
+ [SceneItemSelected](#sceneitemselected)
+ [SceneItemDeselected](#sceneitemdeselected)
@ -118,6 +121,7 @@ auth_response = base64_encode(auth_response_hash)
+ [GetStats](#getstats)
+ [BroadcastCustomMessage](#broadcastcustommessage-1)
+ [GetVideoInfo](#getvideoinfo)
+ [OpenProjector](#openprojector)
* [Outputs](#outputs)
+ [ListOutputs](#listoutputs)
+ [GetOutputInfo](#getoutputinfo)
@ -222,9 +226,11 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen
| ---- | :---: | ------------|
| `cy` | _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. |
| `id` | _int_ | Scene item ID |
| `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 |
| `source_cx` | _Number_ | |
| `source_cy` | _Number_ | |
@ -429,6 +435,46 @@ A transition (other than "cut") has begun.
**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` | _String_ | Transition name. |
@ -1030,6 +1076,25 @@ An item's visibility has been toggled.
| `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
@ -1159,6 +1224,7 @@ _No specified parameters._
| `obs-websocket-version` | _String_ | obs-websocket plugin 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"). |
| `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) |
---
### 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
@ -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.left` | _int_ | The number of pixels cropped off the left of the source before scaling. |
| `visible` | _bool_ | If the source is visible. |
| `muted` | _bool_ | If the source is muted. |
| `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.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 |
| `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) |
| `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.server` | _String (optional)_ | The publish URL. |
| `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.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.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.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. |
**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.server` | _String (optional)_ | The publish URL. |
| `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.password` | _String (optional)_ | The password for the streaming service. |
| `save` | _boolean_ | Persist the settings to disk. |
@ -2978,9 +3071,9 @@ _No specified parameters._
| `settings` | _Object_ | Stream settings object. |
| `settings.server` | _String_ | The publish URL. |
| `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.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.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.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.
* @property {Number} `cy`
* @property {Number} `cx`
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {String} `name` The name of this Scene Item.
* @property {int} `id` Scene item ID
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
* @property {Number} `source_cx`
* @property {Number} `source_cy`
@ -146,6 +148,8 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
obs_data_set_double(data, "cx", item_width * scale.x);
obs_data_set_double(data, "cy", item_height * scale.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
@ -387,6 +391,13 @@ int Utils::GetTransitionDuration(obs_source_t* transition) {
return 0;
}
if (obs_transition_fixed(transition)) {
// If this transition has a fixed duration (such as a Stinger),
// we don't currently have a way of retrieving that number.
// For now, return -1 to indicate that we don't know the actual duration.
return -1;
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
@ -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() {
uint32_t version = obs_get_version();
@ -787,41 +829,6 @@ void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, Pause
if (pauseRecFuncPtr) {
*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)

View File

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

View File

@ -122,7 +122,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
switch (event) {
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
owner->hookTransitionBeginEvent();
owner->hookTransitionPlaybackEvents();
break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
@ -134,7 +134,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
owner->hookTransitionBeginEvent();
owner->hookTransitionPlaybackEvents();
owner->OnSceneCollectionChange();
break;
@ -147,7 +147,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
owner->hookTransitionBeginEvent();
owner->hookTransitionPlaybackEvents();
owner->OnTransitionListChange();
break;
@ -231,7 +231,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
break;
case OBS_FRONTEND_EVENT_EXIT:
owner->unhookTransitionBeginEvent();
owner->unhookTransitionPlaybackEvents();
owner->OnExit();
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_visible", OnSceneItemVisibilityChanged, this);
signal_handler_connect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_connect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
@ -311,11 +313,15 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) {
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
signal_handler_disconnect(sh,
"item_visible", OnSceneItemVisibilityChanged, this);
signal_handler_disconnect(sh,
"item_locked", OnSceneItemLockChanged, this);
signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this);
signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this);
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
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);
}
void WSEvents::hookTransitionBeginEvent() {
void WSEvents::hookTransitionPlaybackEvents() {
obs_frontend_source_list 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_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
obs_frontend_source_list_free(&transitions);
}
void WSEvents::unhookTransitionBeginEvent() {
void WSEvents::unhookTransitionPlaybackEvents() {
obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions);
@ -360,6 +370,8 @@ void WSEvents::unhookTransitionBeginEvent() {
obs_source_t* transition = transitions.sources.array[i];
signal_handler_t* sh = obs_source_get_signal_handler(transition);
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
}
obs_frontend_source_list_free(&transitions);
@ -742,7 +754,7 @@ void WSEvents::OnExit() {
void WSEvents::StreamStatus() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused();
bool recordingPaused = obs_frontend_recording_paused();
bool replayBufferActive = obs_frontend_replay_buffer_active();
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -826,7 +838,7 @@ void WSEvents::Heartbeat() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused();
bool recordingPaused = obs_frontend_recording_paused();
OBSDataAutoRelease data = obs_data_create();
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
@ -885,7 +897,9 @@ void WSEvents::TransitionDurationChanged(int ms) {
*
* @return {String} `name` Transition name.
* @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} `to-scene` Destination scene of the transition
*
@ -902,29 +916,62 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
return;
}
int duration = Utils::GetTransitionDuration(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));
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionBegin", fields);
}
/**
* A transition (other than "cut") has ended.
* Please note that the `from-scene` field is not available in TransitionEnd.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionEnd", fields);
}
/**
* A stinger transition has finished playing its video.
*
* @return {String} `name` Transition name.
* @return {String} `type` Transition type.
* @return {int} `duration` Transition duration (in milliseconds).
* @return {String} `from-scene` Source scene of the transition
* @return {String} `to-scene` Destination scene of the transition
*
* @api events
* @name TransitionVideoEnd
* @category transitions
* @since 4.8.0
*/
void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) {
auto instance = reinterpret_cast<WSEvents*>(param);
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
if (!transition) {
return;
}
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
instance->broadcastUpdate("TransitionVideoEnd", fields);
}
/**
* A source has been created. A source can be an input, a scene or a transition.
*
@ -1435,6 +1482,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
}
/**
* An item's locked status has been toggled.
*
* @return {String} `scene-name` Name of the scene.
* @return {String} `item-name` Name of the item in the scene.
* @return {int} `item-id` Scene item ID
* @return {boolean} `item-locked` New locked state of the item.
*
* @api events
* @name SceneItemLockChanged
* @category 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.
*

View File

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

View File

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

View File

@ -54,6 +54,7 @@ class WSRequestHandler {
RpcResponse GetStats(const RpcRequest&);
RpcResponse SetHeartbeat(const RpcRequest&);
RpcResponse GetVideoInfo(const RpcRequest&);
RpcResponse OpenProjector(const RpcRequest&);
RpcResponse SetFilenameFormatting(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 "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#include "WSRequestHandler.h"
#define CASE(x) case x: return #x;
const char *describe_output_format(int format) {
switch (format) {
@ -60,6 +63,7 @@ const char *describe_scale_type(int scale) {
* @return {String} `obs-websocket-version` obs-websocket plugin version.
* @return {String} `obs-studio-version` OBS Studio program version.
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
*
* @api requests
* @name GetVersion
@ -70,20 +74,28 @@ RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
QString obsVersion = Utils::OBSVersionString();
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.
QString requests;
names.sort(Qt::CaseInsensitive);
requests += names.takeFirst();
for (QString reqName : names) {
for (const QString& reqName : names) {
requests += ("," + reqName);
}
QString supportedImageExportFormats;
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
for (const QByteArray& format : imageWriterFormats) {
supportedImageExportFormats += ("," + QString::fromUtf8(format));
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8());
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
return request.success(data);
}
@ -304,3 +316,31 @@ RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
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");
}
if (!Utils::RecordingPauseSupported()) {
return request.failed("recording pauses are not available in this version of OBS Studio");
}
return callback();
}
@ -77,11 +73,11 @@ RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
*/
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (Utils::RecordingPaused()) {
if (obs_frontend_recording_paused()) {
return request.failed("recording already paused");
}
Utils::PauseRecording(true);
obs_frontend_recording_pause(true);
return request.success();
});
}
@ -97,11 +93,11 @@ RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
*/
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (!Utils::RecordingPaused()) {
if (!obs_frontend_recording_paused()) {
return request.failed("recording is not paused");
}
Utils::PauseRecording(false);
obs_frontend_recording_pause(false);
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.left` The number of pixels cropped off the left of the source before scaling.
* @return {bool} `visible` If the source is visible.
* @return {bool} `muted` If the source is muted.
* @return {bool} `locked` If the source's transform is locked.
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @return {int} `bounds.alignment` Alignment of the bounding box.
@ -30,8 +31,9 @@
* @return {int} `sourceHeight` Base source (without scaling) of the source
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*
* @api requests
* @name GetSceneItemProperties

View File

@ -335,7 +335,7 @@ RpcResponse WSRequestHandler::SetSyncOffset(const RpcRequest& request)
QString sourceName = obs_data_get_string(request.parameters(), "source");
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");
}

View File

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

View File

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

View File

@ -56,6 +56,6 @@ ConfigPtr GetConfig();
WSServerPtr GetServer();
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__)