mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beb482bd5a | ||
|
|
e561d378dd | ||
|
|
73ce88e2f0 | ||
|
|
dfccc4b16d | ||
|
|
b0ef1fee71 | ||
|
|
1ef8ce4b4a | ||
|
|
1bf4bff014 | ||
|
|
4b6e00b2b2 | ||
|
|
c99cbb0c0f | ||
|
|
2a87334d38 | ||
|
|
1a4724ff21 | ||
|
|
59d8fb5d28 | ||
|
|
8823c05aeb | ||
|
|
2d0a52f8d1 | ||
|
|
1273446ce5 | ||
|
|
177ca4aea2 | ||
|
|
7526cbe1a3 | ||
|
|
e4bb034303 | ||
|
|
78e14df620 | ||
|
|
34f07bfb07 | ||
|
|
ee74d39424 | ||
|
|
33667a1f23 | ||
|
|
7947ad2608 | ||
|
|
920c188213 | ||
|
|
28fbff1adc | ||
|
|
326617aad7 | ||
|
|
499ba7dd21 | ||
|
|
f4902933ab | ||
|
|
9aa0b31bff | ||
|
|
6d5086b971 | ||
|
|
f32f47db4b | ||
|
|
74c0cd1736 | ||
|
|
a74a952b1d | ||
|
|
5bbfef81e0 | ||
|
|
c31d1cd975 | ||
|
|
81d91a9139 |
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
|
||||
submodules: 'recursive'
|
||||
- name: Fetch Git Tags
|
||||
run: |
|
||||
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}
|
||||
@@ -75,8 +76,9 @@ jobs:
|
||||
if: success()
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
|
||||
sudo installer -pkg ./Packages.pkg -target /
|
||||
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
|
||||
sudo hdiutil attach ./Packages.dmg
|
||||
sudo installer -pkg /Volumes/Packages\ 1.2.9/Install\ Packages.pkg -target /
|
||||
- name: Package
|
||||
if: success()
|
||||
shell: bash
|
||||
@@ -102,7 +104,7 @@ jobs:
|
||||
path: ./nightly/*.pkg
|
||||
ubuntu64:
|
||||
name: 'Linux/Ubuntu 64-bit'
|
||||
runs-on: [ubuntu-latest]
|
||||
runs-on: [ubuntu-18.04]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
@@ -113,6 +115,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
|
||||
submodules: 'recursive'
|
||||
- name: Add plugin to obs cmake
|
||||
shell: bash
|
||||
run: echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
|
||||
@@ -161,6 +164,7 @@ jobs:
|
||||
pkg-config \
|
||||
python3-dev \
|
||||
qtbase5-dev \
|
||||
qtbase5-private-dev \
|
||||
libqt5svg5-dev \
|
||||
swig \
|
||||
libxss-dev
|
||||
@@ -169,7 +173,7 @@ jobs:
|
||||
run: |
|
||||
mkdir ./build
|
||||
cd ./build
|
||||
cmake -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
|
||||
cmake -DENABLE_PIPEWIRE=OFF -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
|
||||
- name: 'Build'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/build
|
||||
@@ -209,6 +213,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
path: UI/frontend-plugins/${{ env.PLUGIN_NAME}}
|
||||
submodules: 'recursive'
|
||||
- name: Add plugin to obs cmake
|
||||
shell: cmd
|
||||
run: echo add_subdirectory(${{ env.PLUGIN_NAME }}) >> UI/frontend-plugins/CMakeLists.txt
|
||||
@@ -270,7 +275,7 @@ jobs:
|
||||
release:
|
||||
needs: [macos64, ubuntu64, windows]
|
||||
name: 'Create Release'
|
||||
runs-on: [ubuntu-latest]
|
||||
runs-on: [ubuntu-18.04]
|
||||
steps:
|
||||
- name: "Checkout plugin"
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
15
.github/workflows/clang-format.yml
vendored
15
.github/workflows/clang-format.yml
vendored
@@ -8,23 +8,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Install clang format
|
||||
run: |
|
||||
# gets us newer clang
|
||||
sudo bash -c "cat >> /etc/apt/sources.list" << LLVMAPT
|
||||
# 3.8
|
||||
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
|
||||
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
|
||||
LLVMAPT
|
||||
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
|
||||
sudo apt-get -qq update
|
||||
|
||||
sudo apt-get install -y clang-format-8
|
||||
sudo apt-get install -y clang-format-10
|
||||
|
||||
- name: Check the Formatting
|
||||
run: |
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "deps/asio"]
|
||||
path = deps/asio
|
||||
url = https://github.com/chriskohlhoff/asio.git
|
||||
[submodule "deps/websocketpp"]
|
||||
path = deps/websocketpp
|
||||
url = https://github.com/zaphoyd/websocketpp.git
|
||||
@@ -6,6 +6,8 @@ set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
|
||||
# Runs the Clang Formatter in parallel on the code base.
|
||||
# Return codes:
|
||||
# - 1 there are files to be formatted
|
||||
@@ -19,13 +21,15 @@ if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then
|
||||
fi
|
||||
|
||||
# Discover clang-format
|
||||
if type clang-format-8 2> /dev/null ; then
|
||||
if type clang-format-10 2> /dev/null ; then
|
||||
CLANG_FORMAT=clang-format-10
|
||||
elif type clang-format-8 2> /dev/null ; then
|
||||
CLANG_FORMAT=clang-format-8
|
||||
else
|
||||
CLANG_FORMAT=clang-format
|
||||
fi
|
||||
|
||||
find .. -type d \( -path ./deps \
|
||||
-o -path ./cmake \
|
||||
-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
|
||||
find $SCRIPTPATH/.. -type d \( -path $SCRIPTPATH/../deps \
|
||||
-o -path $SCRIPTPATH/../cmake \
|
||||
-o -path $SCRIPTPATH/../build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
|
||||
| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(advanced-scene-switcher)
|
||||
add_definitions(-DASIO_STANDALONE)
|
||||
|
||||
# generate version info
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
|
||||
if(WIN32)
|
||||
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
|
||||
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR "${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}" ISS_MSVC_REDIST_HELPER_DIR)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in" "${CMAKE_CURRENT_BINARY_DIR}/CI/windows/setup.iss" @ONLY)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
|
||||
@@ -45,6 +53,11 @@ if(UNIX AND NOT APPLE)
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
|
||||
)
|
||||
|
||||
set(advanced-scene-switcher_HEADERS
|
||||
${advanced-scene-switcher_HEADERS}
|
||||
src/headers/advanced-scene-switcher.hpp
|
||||
@@ -59,6 +72,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/switch-file.hpp
|
||||
src/headers/switch-idle.hpp
|
||||
src/headers/switch-media.hpp
|
||||
src/headers/switch-network.hpp
|
||||
src/headers/switch-pause.hpp
|
||||
src/headers/switch-random.hpp
|
||||
src/headers/switch-screen-region.hpp
|
||||
@@ -66,6 +80,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/switch-transitions.hpp
|
||||
src/headers/switch-window.hpp
|
||||
src/headers/switch-sequence.hpp
|
||||
src/headers/switch-video.hpp
|
||||
src/headers/switch-generic.hpp
|
||||
src/headers/version.h
|
||||
)
|
||||
@@ -86,12 +101,14 @@ set(advanced-scene-switcher_SOURCES
|
||||
src/switch-file.cpp
|
||||
src/switch-window.cpp
|
||||
src/switch-media.cpp
|
||||
src/switch-network.cpp
|
||||
src/hotkey.cpp
|
||||
src/general.cpp
|
||||
src/switch-pause.cpp
|
||||
src/switch-random.cpp
|
||||
src/switch-time.cpp
|
||||
src/switch-audio.cpp
|
||||
src/switch-video.cpp
|
||||
src/switch-generic.cpp
|
||||
src/curl-helper.cpp
|
||||
src/volume-control.cpp
|
||||
@@ -104,6 +121,8 @@ set(advanced-scene-switcher_UI
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||
|
||||
set(advanced-scene-switcher_PLATFORM_SOURCES
|
||||
src/win/advanced-scene-switcher-win.cpp)
|
||||
elseif(APPLE)
|
||||
|
||||
@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Wechsle zu:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Nach einem automatisierten Szenenwechsel wechsle nicht mehr für"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="In diesem Zeitraum werden potentielle erfüllte Bedingungen ignoriert!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ausführliches Logging"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Fensterposition und -größe speichern"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deaktiviere UI Tipps"
|
||||
AdvSceneSwitcher.generalTab.priority="Priorität"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Szenenwechselmethoden sortiert nach Priorität"
|
||||
@@ -49,6 +50,7 @@ AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername"
|
||||
AdvSceneSwitcher.generalTab.priority.media="Medien"
|
||||
AdvSceneSwitcher.generalTab.priority.time="Zeit"
|
||||
AdvSceneSwitcher.generalTab.priority.audio="Audio"
|
||||
AdvSceneSwitcher.generalTab.priority.video="Video"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Szenenübergänge"
|
||||
@@ -59,6 +61,8 @@ AdvSceneSwitcher.transitionTab.defaultTransition="Ändere den Szenenübergang we
|
||||
AdvSceneSwitcher.transitionTab.entry="Wechsle von {{scenes}} zu {{scenes2}} mit {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Wenn {{scenes}} aktiv ist ändere den Szenenübergang zu {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Wechsle Szenenübergang {{defTransitionDelay}} nach Szenenwechsel."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="Diese Verzögerung dient dazu Szenenwechsel zu unterbrechen.\nDies kann passieren, wenn der Szenenübergangtyp gewechselt wird während ein Szenenübergang grade noch im Gang ist."
|
||||
|
||||
; Pause Scenes Tab
|
||||
AdvSceneSwitcher.pauseTab.title="Pause"
|
||||
@@ -185,6 +189,8 @@ AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Laden der Einstellungen war erfol
|
||||
AdvSceneSwitcher.sceneSequenceTab.fileType="Text Dateien (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="kann unterbrochen werden"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Andere Szenenwechselmethoden können diese Sequenz unterbrechen"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Szenenfolge erweitern"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Nach {{delay}} {{delayUnits}} wechsle zu {{scenes}} mit {{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry="Wenn {{startScenes}} aktiv ist wechsle zu {{scenes}} nach {{delay}} {{delayUnits}} mit {{transitions}} {{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="Dieser Tab ermöglicht es automatisiert zu einer anderen Szene zu wechseln, nachdem eine Szene für die konfigurierte Zeit aktiv war.\nZum Beispiel kann so automatisch zwischen zwei Szenen hin und her gewechselt werden.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
|
||||
|
||||
@@ -192,11 +198,47 @@ AdvSceneSwitcher.sceneSequenceTab.help="Dieser Tab ermöglicht es automatisiert
|
||||
AdvSceneSwitcher.audioTab.title="Audio"
|
||||
AdvSceneSwitcher.audioTab.condition.above="über"
|
||||
AdvSceneSwitcher.audioTab.condition.below="unter"
|
||||
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="außer Audio Quelle ist inaktiv"
|
||||
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Wenn mehrere Einträge zutreffen ..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="Dieser Tab ermöglicht es basierend auf der Lautstärke von Audioquellen Szenen zu wechseln.\nSo kann zum Beispiel automatisch zu einer Szene gewechselt werden, wenn die Lautstärke eines Mikrofons eine konfigurierte Schwelle überschreitet.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Screenshot für ausgewählten Eintrag erstellen"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Erstellt einen Screenshot für die Video Quelle des ausgewählten Eintrag und setzt diesen als Ziel Bild"
|
||||
AdvSceneSwitcher.videoTab.condition.match="genau übereinstimmt mit"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="Eine genaue Übereinstimmung setzt voraus, dass sowohl Zielquelle als auch das Bild die gleiche Auflösung besitzen.\nZusätzlich muss jeder einzelne Pixel zwischen beiden Bildquellen übereinstimmen weshalb Bildformate, welche das Bild komprimieren (z. B. .JPG) nicht zu empfehlen sind."
|
||||
AdvSceneSwitcher.videoTab.condition.differ="nicht übereinstimmt mit"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="sich nicht verändert"
|
||||
AdvSceneSwitcher.videoTab.ignoreInactiveSource="außer Video Quelle ist inaktiv"
|
||||
AdvSceneSwitcher.videoTab.entry="Wenn {{videoSources}} {{condition}} {{filePath}} {{browseButton}} für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es basierend auf der Videoausgabe von Quellen scenen zu wechseln.<br/>Für eine noch bessere Implementierung dieser Funktionalität siehe <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a>.<br/><br/>Klicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen..</p></body></html>"
|
||||
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Netzwerk"
|
||||
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
|
||||
AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden."
|
||||
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
|
||||
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel"
|
||||
AdvSceneSwitcher.networkTab.startFailed.message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2"
|
||||
AdvSceneSwitcher.networkTab.server.status.currentStatus="Aktueller Status"
|
||||
AdvSceneSwitcher.networkTab.server.status.notRunning="Nicht gestartet"
|
||||
AdvSceneSwitcher.networkTab.server.status.starting="Startet"
|
||||
AdvSceneSwitcher.networkTab.server.status.running="Server läuft"
|
||||
AdvSceneSwitcher.networkTab.server.restart="Server neu starten"
|
||||
AdvSceneSwitcher.networkTab.client="Client starten (Empfängt Szenenwechselnachrichten )"
|
||||
AdvSceneSwitcher.networkTab.client.address="Hostname oder IP-Adresse"
|
||||
AdvSceneSwitcher.networkTab.client.port="Port"
|
||||
AdvSceneSwitcher.networkTab.client.status.currentStatus="Aktueller status"
|
||||
AdvSceneSwitcher.networkTab.client.status.disconnected="Verbindung getrennt"
|
||||
AdvSceneSwitcher.networkTab.client.status.connecting="Verbinde"
|
||||
AdvSceneSwitcher.networkTab.client.status.connected="Verbunden"
|
||||
AdvSceneSwitcher.networkTab.client.reconnect="Erneutes Verbinden erzwingen"
|
||||
|
||||
; Scene Group Tab
|
||||
AdvSceneSwitcher.sceneGroupTab.title="Szenengruppe"
|
||||
AdvSceneSwitcher.sceneGroupTab.list="Szenengruppen"
|
||||
@@ -232,7 +274,10 @@ AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Streamen stop
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Streamen starten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Replay Buffer starten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Replay Buffer stoppen"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Quelle stumm schalten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Audio Quelle stumm schalten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Audio Quelle aktiv schalten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Szenenwechsler starten"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Szenenwechsler stoppen"
|
||||
AdvSceneSwitcher.sceneTriggerTab.entry="Wenn {{scenes}} {{triggers}} {{actions}} {{audioSources}} nach {{duration}} "
|
||||
AdvSceneSwitcher.sceneTriggerTab.help="Dieser Tab ermöglicht es automatisiert Handlungen, wie etwa das Stoppen von einer Aufnahme oder des Streamens, bei einem Szenenwechsel auszuführen."
|
||||
|
||||
@@ -249,9 +294,11 @@ AdvSceneSwitcher.browse="Öffnen"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--Szene auswählen--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Vorhergehende Szene"
|
||||
AdvSceneSwitcher.currentTransition="Aktueller Szenenübergang"
|
||||
AdvSceneSwitcher.selectTransition="--Szenenübergang auswählen--"
|
||||
AdvSceneSwitcher.selectWindow="--Fenster auswählen--"
|
||||
AdvSceneSwitcher.selectAudioSource="--Audio Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectVideoSource="--Video Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectMediaSource="--Medien Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectProcess="--Prozess auswählen--"
|
||||
AdvSceneSwitcher.enterPath="--Pfad eingeben--"
|
||||
|
||||
@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Switch to:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switch scenes for"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
||||
AdvSceneSwitcher.generalTab.priority="Priority"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||
@@ -49,6 +50,7 @@ AdvSceneSwitcher.generalTab.priority.windowTitle="Window Title"
|
||||
AdvSceneSwitcher.generalTab.priority.media="Media"
|
||||
AdvSceneSwitcher.generalTab.priority.time="Time"
|
||||
AdvSceneSwitcher.generalTab.priority.audio="Audio"
|
||||
AdvSceneSwitcher.generalTab.priority.video="Video"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
@@ -59,6 +61,8 @@ AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is
|
||||
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition {{defTransitionDelay}} after scene change."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
|
||||
|
||||
; Pause Scenes Tab
|
||||
AdvSceneSwitcher.pauseTab.title="Pause"
|
||||
@@ -186,17 +190,55 @@ AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} {{delayUnits}} using {{transitions}} {{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Audio Tab
|
||||
AdvSceneSwitcher.audioTab.title="Audio"
|
||||
AdvSceneSwitcher.audioTab.condition.above="above"
|
||||
AdvSceneSwitcher.audioTab.condition.below="below"
|
||||
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match ..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
|
||||
AdvSceneSwitcher.videoTab.condition.match="exactly matches"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="An exact match requires the target and the source image to be of the same resolution.\nAdditionally every single pixel needs to match, which is why use of image formats which use compression (e.g. .JPG) is not recommended!"
|
||||
AdvSceneSwitcher.videoTab.condition.differ="does not match"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="has not changed"
|
||||
AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.videoTab.entry="When {{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
|
||||
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Network"
|
||||
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
|
||||
AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel."
|
||||
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
|
||||
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches"
|
||||
AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
|
||||
AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.server.status.notRunning="Not running"
|
||||
AdvSceneSwitcher.networkTab.server.status.starting="Starting"
|
||||
AdvSceneSwitcher.networkTab.server.status.running="Running"
|
||||
AdvSceneSwitcher.networkTab.server.restart="Restart server"
|
||||
AdvSceneSwitcher.networkTab.client="Start client (Receives scene switches messages)"
|
||||
AdvSceneSwitcher.networkTab.client.address="Hostname or IP address"
|
||||
AdvSceneSwitcher.networkTab.client.port="Port"
|
||||
AdvSceneSwitcher.networkTab.client.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.client.status.disconnected="Disconnected"
|
||||
AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
|
||||
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
|
||||
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
|
||||
|
||||
; Scene Group Tab
|
||||
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
|
||||
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
|
||||
@@ -233,6 +275,9 @@ AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="start stream
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="start replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="stop replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="unmute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="start the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="stop the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.entry="When {{scenes}} {{triggers}} {{actions}} {{audioSources}} after {{duration}}"
|
||||
AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on scene changes, like stopping recording or streaming."
|
||||
|
||||
@@ -248,9 +293,11 @@ AdvSceneSwitcher.browse="Browse"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.currentTransition="Current Transition"
|
||||
AdvSceneSwitcher.selectTransition="--select transition--"
|
||||
AdvSceneSwitcher.selectWindow="--select window--"
|
||||
AdvSceneSwitcher.selectAudioSource="--select audio source--"
|
||||
AdvSceneSwitcher.selectVideoSource="--select video source--"
|
||||
AdvSceneSwitcher.selectMediaSource="--select media source--"
|
||||
AdvSceneSwitcher.selectProcess="--select process--"
|
||||
AdvSceneSwitcher.enterPath="--enter path--"
|
||||
|
||||
1
deps/asio
vendored
Submodule
1
deps/asio
vendored
Submodule
Submodule deps/asio added at 93f87fbdbf
1
deps/websocketpp
vendored
Submodule
1
deps/websocketpp
vendored
Submodule
Submodule deps/websocketpp added at 1b11fd3015
@@ -161,46 +161,8 @@
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="cooldownTime">
|
||||
<property name="suffix">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_32">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_43">
|
||||
@@ -295,7 +257,45 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="cooldownTime">
|
||||
<property name="suffix">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_52">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="verboseLogging">
|
||||
@@ -319,7 +319,31 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_50">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="saveWindowGeo">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_55">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_48">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiHintsDisable">
|
||||
@@ -737,6 +761,9 @@
|
||||
<string>AdvSceneSwitcher.transitionTab.defaultTransition</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="defTransitionDelayLayout"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="0" column="0">
|
||||
@@ -2824,6 +2851,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sequenceEdit">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.sceneSequenceTab.extendEdit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_8">
|
||||
<property name="orientation">
|
||||
@@ -3070,6 +3111,369 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="videoTab">
|
||||
<attribute name="title">
|
||||
<string>AdvSceneSwitcher.videoTab.title</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_29">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_22">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="videoSwitches">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="videoHelp">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.videoTab.help</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_58">
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoAdd">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">addIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoRemove">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">removeIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_124">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_43">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_125">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoUp">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../../../forms/images/up.svg</normaloff>../../../forms/images/up.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoDown">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../../../forms/images/down.svg</normaloff>../../../forms/images/down.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="getScreenshot">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.videoTab.getScreenshot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_62">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="networkTab">
|
||||
<attribute name="title">
|
||||
<string>AdvSceneSwitcher.networkTab.title</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_30">
|
||||
<item>
|
||||
<widget class="QLabel" name="networkTabWarningLabel">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.warning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="serverSettings">
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.networkTab.server</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_31">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_24">
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="serverPort">
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>55555</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="lockToIPv4">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.lockToIPv4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.status.currentStatus</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="serverStatus">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.status.notRunning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="restrictSend">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="serverRestart">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.server.restart</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="clientSettings">
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.networkTab.client</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_23">
|
||||
<item row="0" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_21">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.client.port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.client.address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="clientHostname">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="clientPort">
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>55555</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="clientStatus">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.client.status.disconnected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.client.status.currentStatus</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="clientReconnect">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.networkTab.client.reconnect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="sceneGroupTab">
|
||||
<attribute name="title">
|
||||
<string>AdvSceneSwitcher.sceneGroupTab.title</string>
|
||||
|
||||
@@ -59,10 +59,13 @@ void AdvSceneSwitcher::loadUI()
|
||||
setupFileTab();
|
||||
setupTimeTab();
|
||||
setupAudioTab();
|
||||
setupVideoTab();
|
||||
setupNetworkTab();
|
||||
setupSceneGroupTab();
|
||||
setupTriggerTab();
|
||||
|
||||
setTabOrder();
|
||||
restoreWindowGeo();
|
||||
|
||||
loading = false;
|
||||
}
|
||||
@@ -92,8 +95,9 @@ bool AdvSceneSwitcher::DisplayMessage(QString msg, bool question)
|
||||
return false;
|
||||
}
|
||||
|
||||
void addSelectionEntry(QComboBox *sel, const char *description,
|
||||
const char *tooltip = "")
|
||||
void AdvSceneSwitcher::addSelectionEntry(QComboBox *sel,
|
||||
const char *description,
|
||||
bool selectable, const char *tooltip)
|
||||
{
|
||||
sel->addItem(description);
|
||||
|
||||
@@ -106,26 +110,28 @@ void addSelectionEntry(QComboBox *sel, const char *description,
|
||||
QModelIndex firstIndex =
|
||||
model->index(0, sel->modelColumn(), sel->rootModelIndex());
|
||||
QStandardItem *firstItem = model->itemFromIndex(firstIndex);
|
||||
firstItem->setSelectable(false);
|
||||
firstItem->setEnabled(false);
|
||||
if (!selectable) {
|
||||
firstItem->setSelectable(false);
|
||||
firstItem->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
|
||||
bool addSceneGroup,
|
||||
bool addSelect,
|
||||
std::string selectText)
|
||||
std::string selectText,
|
||||
bool selectable)
|
||||
{
|
||||
sel->clear();
|
||||
|
||||
if (addSelect) {
|
||||
if (selectText.empty()) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectScene"),
|
||||
selectable,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
|
||||
} else {
|
||||
addSelectionEntry(sel, selectText.c_str());
|
||||
addSelectionEntry(sel, selectText.c_str(), selectable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,12 +156,20 @@ void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateTransitionSelection(QComboBox *sel,
|
||||
bool addSelect)
|
||||
bool addCurrent,
|
||||
bool addSelect,
|
||||
bool selectable)
|
||||
{
|
||||
if (addSelect) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectTransition"));
|
||||
obs_module_text("AdvSceneSwitcher.selectTransition"),
|
||||
selectable);
|
||||
}
|
||||
|
||||
if (addCurrent) {
|
||||
sel->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.currentTransition"));
|
||||
}
|
||||
|
||||
obs_frontend_source_list *transitions = new obs_frontend_source_list();
|
||||
@@ -197,6 +211,7 @@ void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectAudioSource"),
|
||||
false,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
|
||||
}
|
||||
@@ -221,12 +236,49 @@ void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateVideoSelection(QComboBox *sel, bool addScenes,
|
||||
bool addSelect)
|
||||
{
|
||||
if (addSelect) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectVideoSource"),
|
||||
false,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
|
||||
}
|
||||
|
||||
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
|
||||
{
|
||||
std::vector<std::string> *list =
|
||||
reinterpret_cast<std::vector<std::string> *>(data);
|
||||
uint32_t flags = obs_source_get_output_flags(source);
|
||||
std::string test = obs_source_get_name(source);
|
||||
if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) {
|
||||
list->push_back(obs_source_get_name(source));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<std::string> videoSources;
|
||||
obs_enum_sources(sourceEnum, &videoSources);
|
||||
sort(videoSources.begin(), videoSources.end());
|
||||
for (std::string &source : videoSources) {
|
||||
sel->addItem(source.c_str());
|
||||
}
|
||||
|
||||
if (addScenes) {
|
||||
populateSceneSelection(sel, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect)
|
||||
{
|
||||
if (addSelect) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectMediaSource"),
|
||||
false,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
|
||||
}
|
||||
@@ -428,9 +480,6 @@ void SwitcherData::Thread()
|
||||
OBSWeakSource scene;
|
||||
OBSWeakSource transition;
|
||||
|
||||
bool defTransitionMatch = false;
|
||||
OBSWeakSource defTransition;
|
||||
|
||||
endTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto runTime =
|
||||
@@ -449,7 +498,7 @@ void SwitcherData::Thread()
|
||||
}
|
||||
}
|
||||
|
||||
vblog(LOG_INFO, "sleep for %ld", duration.count());
|
||||
vblog(LOG_INFO, "try to sleep for %ld", duration.count());
|
||||
cv.wait_for(lock, duration);
|
||||
|
||||
startTime = std::chrono::high_resolution_clock::now();
|
||||
@@ -457,63 +506,17 @@ void SwitcherData::Thread()
|
||||
linger = 0;
|
||||
|
||||
switcher->Prune();
|
||||
|
||||
if (switcher->stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (checkPause()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
checkDefaultSceneTransitions(defTransitionMatch, defTransition);
|
||||
|
||||
for (int switchFuncName : functionNamesByPriority) {
|
||||
switch (switchFuncName) {
|
||||
case read_file_func:
|
||||
checkSwitchInfoFromFile(match, scene,
|
||||
transition);
|
||||
checkFileContent(match, scene, transition);
|
||||
break;
|
||||
case idle_func:
|
||||
checkIdleSwitch(match, scene, transition);
|
||||
break;
|
||||
case exe_func:
|
||||
checkExeSwitch(match, scene, transition);
|
||||
break;
|
||||
case screen_region_func:
|
||||
checkScreenRegionSwitch(match, scene,
|
||||
transition);
|
||||
break;
|
||||
case window_title_func:
|
||||
checkWindowTitleSwitch(match, scene,
|
||||
transition);
|
||||
break;
|
||||
case round_trip_func:
|
||||
checkSceneSequence(match, scene, transition,
|
||||
linger);
|
||||
break;
|
||||
case media_func:
|
||||
checkMediaSwitch(match, scene, transition);
|
||||
break;
|
||||
case time_func:
|
||||
checkTimeSwitch(match, scene, transition);
|
||||
break;
|
||||
case audio_func:
|
||||
checkAudioSwitch(match, scene, transition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (switcher->stop) {
|
||||
goto endLoop;
|
||||
}
|
||||
if (match) {
|
||||
break;
|
||||
}
|
||||
match = checkForMatch(scene, transition, linger);
|
||||
if (switcher->stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
checkNoMatchSwitch(match, scene, transition, sleep);
|
||||
|
||||
checkSwitchCooldown(match);
|
||||
|
||||
if (linger) {
|
||||
@@ -544,21 +547,77 @@ void SwitcherData::Thread()
|
||||
// leading to a deadlock, so we have to unlock()
|
||||
lock.unlock();
|
||||
|
||||
if (!match && defTransitionMatch) {
|
||||
setCurrentDefTransition(defTransition);
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (networkConfig.ServerEnabled) {
|
||||
server.sendMessage(scene, transition);
|
||||
}
|
||||
switchScene(scene, transition,
|
||||
tansitionOverrideOverride);
|
||||
}
|
||||
|
||||
writeSceneInfoToFile();
|
||||
}
|
||||
endLoop:
|
||||
|
||||
blog(LOG_INFO, "stopped");
|
||||
}
|
||||
|
||||
bool SwitcherData::checkForMatch(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition, int &linger)
|
||||
{
|
||||
bool match = false;
|
||||
|
||||
if (uninterruptibleSceneSequenceActive) {
|
||||
checkSceneSequence(match, scene, transition, linger);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
for (int switchFuncName : functionNamesByPriority) {
|
||||
switch (switchFuncName) {
|
||||
case read_file_func:
|
||||
checkSwitchInfoFromFile(match, scene, transition);
|
||||
checkFileContent(match, scene, transition);
|
||||
break;
|
||||
case idle_func:
|
||||
checkIdleSwitch(match, scene, transition);
|
||||
break;
|
||||
case exe_func:
|
||||
checkExeSwitch(match, scene, transition);
|
||||
break;
|
||||
case screen_region_func:
|
||||
checkScreenRegionSwitch(match, scene, transition);
|
||||
break;
|
||||
case window_title_func:
|
||||
checkWindowTitleSwitch(match, scene, transition);
|
||||
break;
|
||||
case round_trip_func:
|
||||
checkSceneSequence(match, scene, transition, linger);
|
||||
break;
|
||||
case media_func:
|
||||
checkMediaSwitch(match, scene, transition);
|
||||
break;
|
||||
case time_func:
|
||||
checkTimeSwitch(match, scene, transition);
|
||||
break;
|
||||
case audio_func:
|
||||
checkAudioSwitch(match, scene, transition);
|
||||
break;
|
||||
case video_func:
|
||||
checkVideoSwitch(match, scene, transition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (switcher->stop) {
|
||||
return false;
|
||||
}
|
||||
if (match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
|
||||
bool &transitionOverrideOverride)
|
||||
{
|
||||
@@ -598,6 +657,15 @@ void SwitcherData::Start()
|
||||
// Will be overwritten quickly but might be useful
|
||||
writeToStatusFile("Advanced Scene Switcher running");
|
||||
}
|
||||
|
||||
if (networkConfig.ServerEnabled) {
|
||||
server.start(networkConfig.ServerPort,
|
||||
networkConfig.LockToIPv4);
|
||||
}
|
||||
|
||||
if (networkConfig.ClientEnabled) {
|
||||
client.connect(networkConfig.GetClientUri());
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::Stop()
|
||||
@@ -612,6 +680,9 @@ void SwitcherData::Stop()
|
||||
|
||||
writeToStatusFile("Advanced Scene Switcher stopped");
|
||||
}
|
||||
|
||||
server.stop();
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
bool SwitcherData::sceneChangedDuringWait()
|
||||
@@ -643,7 +714,7 @@ extern "C" void FreeSceneSwitcher()
|
||||
|
||||
void handleSceneChange(SwitcherData *s)
|
||||
{
|
||||
// Stop waiting if scene was manually changed
|
||||
// Stop waiting if scene was changed
|
||||
if (s->sceneChangedDuringWait()) {
|
||||
s->cv.notify_one();
|
||||
}
|
||||
@@ -658,12 +729,13 @@ void handleSceneChange(SwitcherData *s)
|
||||
s->previousSceneHelper = ws;
|
||||
}
|
||||
|
||||
switcher->checkTriggers();
|
||||
}
|
||||
s->checkTriggers();
|
||||
s->checkDefaultSceneTransitions();
|
||||
|
||||
void handleTransitionStop(SwitcherData *s)
|
||||
{
|
||||
s->checkedDefTransition = false;
|
||||
if (switcher->networkConfig.ServerEnabled &&
|
||||
switcher->networkConfig.SendAll) {
|
||||
switcher->server.sendMessage(ws, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void setLiveTime(SwitcherData *s)
|
||||
@@ -702,9 +774,6 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||
handleSceneChange((SwitcherData *)switcher);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
|
||||
handleTransitionStop((SwitcherData *)switcher);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
||||
setLiveTime((SwitcherData *)switcher);
|
||||
checkAutoStartRecording((SwitcherData *)switcher);
|
||||
|
||||
151
src/general.cpp
151
src/general.cpp
@@ -4,7 +4,7 @@
|
||||
#include "headers/utility.hpp"
|
||||
#include "headers/version.h"
|
||||
|
||||
constexpr auto tab_count = 15;
|
||||
constexpr auto tab_count = 17;
|
||||
|
||||
QMetaObject::Connection inactivePluse;
|
||||
|
||||
@@ -129,6 +129,7 @@ void AdvSceneSwitcher::SetStarted()
|
||||
ui->pluginRunningText->setText(
|
||||
obs_module_text("AdvSceneSwitcher.status.active"));
|
||||
ui->pluginRunningText->disconnect(inactivePluse);
|
||||
currentStatusActive = true;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetStopped()
|
||||
@@ -139,6 +140,7 @@ void AdvSceneSwitcher::SetStopped()
|
||||
obs_module_text("AdvSceneSwitcher.status.inactive"));
|
||||
inactivePluse = PulseWidget(ui->pluginRunningText, QColor(Qt::red),
|
||||
QColor(0, 0, 0, 0), "QLabel ");
|
||||
currentStatusActive = false;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_toggleStartButton_clicked()
|
||||
@@ -154,6 +156,9 @@ void AdvSceneSwitcher::on_toggleStartButton_clicked()
|
||||
|
||||
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
|
||||
{
|
||||
switcher->windowPos = this->pos();
|
||||
switcher->windowSize = this->size();
|
||||
|
||||
obs_frontend_save();
|
||||
}
|
||||
|
||||
@@ -166,6 +171,15 @@ void AdvSceneSwitcher::on_verboseLogging_stateChanged(int state)
|
||||
switcher->verbose = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->saveWindowGeo = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
@@ -324,9 +338,15 @@ int findTabIndex(QTabWidget *tabWidget, int pos)
|
||||
tabName = "audioTab";
|
||||
break;
|
||||
case 13:
|
||||
tabName = "sceneGroupTab";
|
||||
tabName = "videoTab";
|
||||
break;
|
||||
case 14:
|
||||
tabName = "networkTab";
|
||||
break;
|
||||
case 15:
|
||||
tabName = "sceneGroupTab";
|
||||
break;
|
||||
case 16:
|
||||
tabName = "sceneTriggerTab";
|
||||
break;
|
||||
}
|
||||
@@ -357,6 +377,14 @@ void AdvSceneSwitcher::setTabOrder()
|
||||
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::restoreWindowGeo()
|
||||
{
|
||||
if (switcher->saveWindowGeo) {
|
||||
this->resize(switcher->windowSize);
|
||||
this->move(switcher->windowPos);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_tabMoved(int from, int to)
|
||||
{
|
||||
if (loading) {
|
||||
@@ -381,22 +409,24 @@ void SwitcherData::loadSettings(obs_data_t *obj)
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->loadSceneGroups(obj);
|
||||
switcher->loadWindowTitleSwitches(obj);
|
||||
switcher->loadScreenRegionSwitches(obj);
|
||||
switcher->loadPauseSwitches(obj);
|
||||
switcher->loadSceneSequenceSwitches(obj);
|
||||
switcher->loadSceneTransitions(obj);
|
||||
switcher->loadIdleSwitches(obj);
|
||||
switcher->loadExecutableSwitches(obj);
|
||||
switcher->loadRandomSwitches(obj);
|
||||
switcher->loadFileSwitches(obj);
|
||||
switcher->loadMediaSwitches(obj);
|
||||
switcher->loadTimeSwitches(obj);
|
||||
switcher->loadAudioSwitches(obj);
|
||||
switcher->loadSceneTriggers(obj);
|
||||
switcher->loadGeneralSettings(obj);
|
||||
switcher->loadHotkeys(obj);
|
||||
loadSceneGroups(obj);
|
||||
loadWindowTitleSwitches(obj);
|
||||
loadScreenRegionSwitches(obj);
|
||||
loadPauseSwitches(obj);
|
||||
loadSceneSequenceSwitches(obj);
|
||||
loadSceneTransitions(obj);
|
||||
loadIdleSwitches(obj);
|
||||
loadExecutableSwitches(obj);
|
||||
loadRandomSwitches(obj);
|
||||
loadFileSwitches(obj);
|
||||
loadMediaSwitches(obj);
|
||||
loadTimeSwitches(obj);
|
||||
loadAudioSwitches(obj);
|
||||
loadVideoSwitches(obj);
|
||||
loadNetworkSettings(obj);
|
||||
loadSceneTriggers(obj);
|
||||
loadGeneralSettings(obj);
|
||||
loadHotkeys(obj);
|
||||
}
|
||||
|
||||
void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
@@ -418,6 +448,8 @@ void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
saveMediaSwitches(obj);
|
||||
saveTimeSwitches(obj);
|
||||
saveAudioSwitches(obj);
|
||||
saveVideoSwitches(obj);
|
||||
saveNetworkSwitches(obj);
|
||||
saveSceneTriggers(obj);
|
||||
saveGeneralSettings(obj);
|
||||
saveHotkeys(obj);
|
||||
@@ -465,15 +497,15 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
switcher->functionNamesByPriority[7]);
|
||||
obs_data_set_int(obj, "priority8",
|
||||
switcher->functionNamesByPriority[8]);
|
||||
obs_data_set_int(obj, "priority9",
|
||||
switcher->functionNamesByPriority[9]);
|
||||
|
||||
obs_data_set_int(obj, "threadPriority", switcher->threadPriority);
|
||||
|
||||
// After fresh install of OBS the vector can be empty
|
||||
// as save() might be called before first load()
|
||||
if (switcher->tabOrder.size() < tab_count) {
|
||||
switcher->tabOrder = std::vector<int>(tab_count);
|
||||
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(),
|
||||
0);
|
||||
if (tabOrder.size() < tab_count) {
|
||||
resetTabOrder();
|
||||
}
|
||||
|
||||
obs_data_set_int(obj, "generalTabPos", switcher->tabOrder[0]);
|
||||
@@ -489,8 +521,16 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "idleTabPos", switcher->tabOrder[10]);
|
||||
obs_data_set_int(obj, "sequenceTabPos", switcher->tabOrder[11]);
|
||||
obs_data_set_int(obj, "audioTabPos", switcher->tabOrder[12]);
|
||||
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[13]);
|
||||
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[14]);
|
||||
obs_data_set_int(obj, "videoTabPos", switcher->tabOrder[13]);
|
||||
obs_data_set_int(obj, "networkTabPos", switcher->tabOrder[14]);
|
||||
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[15]);
|
||||
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[16]);
|
||||
|
||||
obs_data_set_bool(obj, "saveWindowGeo", saveWindowGeo);
|
||||
obs_data_set_int(obj, "windowPosX", switcher->windowPos.x());
|
||||
obs_data_set_int(obj, "windowPosY", switcher->windowPos.y());
|
||||
obs_data_set_int(obj, "windowWidth", switcher->windowSize.width());
|
||||
obs_data_set_int(obj, "windowHeight", switcher->windowSize.height());
|
||||
}
|
||||
|
||||
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
@@ -553,6 +593,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
(obs_data_get_int(obj, "priority7"));
|
||||
switcher->functionNamesByPriority[8] =
|
||||
(obs_data_get_int(obj, "priority8"));
|
||||
switcher->functionNamesByPriority[9] =
|
||||
(obs_data_get_int(obj, "priority9"));
|
||||
if (!switcher->prioFuncsValid()) {
|
||||
switcher->functionNamesByPriority[0] = (default_priority_0);
|
||||
switcher->functionNamesByPriority[1] = (default_priority_1);
|
||||
@@ -563,6 +605,7 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
switcher->functionNamesByPriority[6] = (default_priority_6);
|
||||
switcher->functionNamesByPriority[7] = (default_priority_7);
|
||||
switcher->functionNamesByPriority[8] = (default_priority_8);
|
||||
switcher->functionNamesByPriority[9] = (default_priority_9);
|
||||
}
|
||||
|
||||
obs_data_set_default_int(obj, "threadPriority",
|
||||
@@ -582,8 +625,10 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_default_int(obj, "idleTabPos", 10);
|
||||
obs_data_set_default_int(obj, "sequenceTabPos", 11);
|
||||
obs_data_set_default_int(obj, "audioTabPos", 12);
|
||||
obs_data_set_default_int(obj, "sceneGroupTabPos", 13);
|
||||
obs_data_set_default_int(obj, "triggerTabPos", 14);
|
||||
obs_data_set_default_int(obj, "videoTabPos", 13);
|
||||
obs_data_set_default_int(obj, "networkTabPos", 14);
|
||||
obs_data_set_default_int(obj, "sceneGroupTabPos", 15);
|
||||
obs_data_set_default_int(obj, "triggerTabPos", 16);
|
||||
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "generalTabPos")));
|
||||
@@ -611,10 +656,38 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
(int)(obs_data_get_int(obj, "sequenceTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "audioTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "videoTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "networkTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "sceneGroupTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(int)(obs_data_get_int(obj, "triggerTabPos")));
|
||||
|
||||
if (!tabOrderValid()) {
|
||||
resetTabOrder();
|
||||
}
|
||||
|
||||
saveWindowGeo = obs_data_get_bool(obj, "saveWindowGeo");
|
||||
windowPos = {(int)obs_data_get_int(obj, "windowPosX"),
|
||||
(int)obs_data_get_int(obj, "windowPosY")};
|
||||
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
|
||||
(int)obs_data_get_int(obj, "windowHeight")};
|
||||
}
|
||||
|
||||
bool SwitcherData::tabOrderValid()
|
||||
{
|
||||
auto tmp = tabOrder;
|
||||
std::sort(tmp.begin(), tmp.end());
|
||||
auto it = std::unique(tmp.begin(), tmp.end());
|
||||
return it == tmp.end();
|
||||
}
|
||||
|
||||
void SwitcherData::resetTabOrder()
|
||||
{
|
||||
tabOrder = std::vector<int>(tab_count);
|
||||
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(), 0);
|
||||
}
|
||||
|
||||
void SwitcherData::checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
|
||||
@@ -681,6 +754,21 @@ void populateAutoStartEventSelection(QComboBox *cb)
|
||||
"AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming"));
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::updateStatus()
|
||||
{
|
||||
if (switcher->th && switcher->th->isRunning()) {
|
||||
if (currentStatusActive) {
|
||||
return;
|
||||
}
|
||||
SetStarted();
|
||||
} else {
|
||||
if (!currentStatusActive) {
|
||||
return;
|
||||
}
|
||||
SetStopped();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupGeneralTab()
|
||||
{
|
||||
populateSceneSelection(ui->noMatchSwitchScene, false);
|
||||
@@ -707,6 +795,7 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));
|
||||
|
||||
ui->verboseLogging->setChecked(switcher->verbose);
|
||||
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
|
||||
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
||||
|
||||
for (int p : switcher->functionNamesByPriority) {
|
||||
@@ -748,6 +837,10 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
s = obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.priority.audio");
|
||||
break;
|
||||
case video_func:
|
||||
s = obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.priority.video");
|
||||
break;
|
||||
}
|
||||
QString text(s.c_str());
|
||||
QListWidgetItem *item =
|
||||
@@ -780,4 +873,10 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
} else {
|
||||
SetStopped();
|
||||
}
|
||||
|
||||
// Updates the UI status element if the status changed externally
|
||||
// (e.g. via hotkeys)
|
||||
QTimer *statusTimer = new QTimer(this);
|
||||
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
statusTimer->start(1000);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include "switcher-data-structs.hpp"
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
|
||||
#define vblog(level, msg, ...) \
|
||||
if (verbose) { \
|
||||
blog(level, "[adv-ss] " msg, ##__VA_ARGS__); \
|
||||
#define vblog(level, msg, ...) \
|
||||
if (verbose) { \
|
||||
blog(level, msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
class QCloseEvent;
|
||||
@@ -23,6 +23,7 @@ class AdvSceneSwitcher : public QDialog {
|
||||
public:
|
||||
std::unique_ptr<Ui_AdvSceneSwitcher> ui;
|
||||
bool loading = true;
|
||||
bool currentStatusActive = false;
|
||||
|
||||
AdvSceneSwitcher(QWidget *parent);
|
||||
|
||||
@@ -39,6 +40,7 @@ public:
|
||||
int IgnoreIdleWindowsFindByData(const QString &window);
|
||||
|
||||
void UpdateNonMatchingScene(const QString &name);
|
||||
void OpenSequenceExtendEdit(SequenceWidget *sw);
|
||||
void SetEditSceneGroup(SceneGroup &sg);
|
||||
|
||||
void loadUI();
|
||||
@@ -57,23 +59,35 @@ public:
|
||||
void setupAudioTab();
|
||||
void setupSceneGroupTab();
|
||||
void setupTriggerTab();
|
||||
void setupVideoTab();
|
||||
void setupNetworkTab();
|
||||
void setTabOrder();
|
||||
void restoreWindowGeo();
|
||||
|
||||
static bool DisplayMessage(QString msg, bool question = false);
|
||||
static void AskBackup(obs_data_t *obj);
|
||||
|
||||
static void addSelectionEntry(QComboBox *sel, const char *description,
|
||||
bool selectable = false,
|
||||
const char *tooltip = "");
|
||||
static void populateSceneSelection(QComboBox *sel,
|
||||
bool addPrevious = false,
|
||||
bool addSceneGroup = false,
|
||||
bool addSelect = true,
|
||||
std::string selectText = "");
|
||||
std::string selectText = "",
|
||||
bool selectable = false);
|
||||
|
||||
static void populateTransitionSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
bool addCurrent = true,
|
||||
bool addSelect = true,
|
||||
bool selectable = false);
|
||||
static void populateWindowSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
static void populateAudioSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
static void populateVideoSelection(QComboBox *sel,
|
||||
bool addScenes = true,
|
||||
bool addSelect = true);
|
||||
static void populateMediaSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
static void populateProcessSelection(QComboBox *sel,
|
||||
@@ -129,12 +143,15 @@ public slots:
|
||||
|
||||
void on_sceneSequenceAdd_clicked();
|
||||
void on_sceneSequenceRemove_clicked();
|
||||
void on_sceneSequenceSave_clicked();
|
||||
void on_sceneSequenceLoad_clicked();
|
||||
void on_sceneSequenceUp_clicked();
|
||||
void on_sceneSequenceDown_clicked();
|
||||
void on_sceneSequenceSave_clicked();
|
||||
void on_sceneSequenceLoad_clicked();
|
||||
void on_sequenceEdit_clicked();
|
||||
void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
|
||||
|
||||
void on_verboseLogging_stateChanged(int state);
|
||||
void on_saveWindowGeo_stateChanged(int state);
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
|
||||
void on_exportSettings_clicked();
|
||||
@@ -149,6 +166,7 @@ public slots:
|
||||
void on_defaultTransitionsUp_clicked();
|
||||
void on_defaultTransitionsDown_clicked();
|
||||
void on_transitionOverridecheckBox_stateChanged(int state);
|
||||
void defTransitionDelayValueChanged(int value);
|
||||
|
||||
void on_browseButton_clicked();
|
||||
void on_readFileCheckBox_stateChanged(int state);
|
||||
@@ -191,6 +209,24 @@ public slots:
|
||||
void on_audioDown_clicked();
|
||||
void on_audioFallback_toggled(bool on);
|
||||
|
||||
void on_videoAdd_clicked();
|
||||
void on_videoRemove_clicked();
|
||||
void on_videoUp_clicked();
|
||||
void on_videoDown_clicked();
|
||||
void on_getScreenshot_clicked();
|
||||
|
||||
void on_serverSettings_toggled(bool on);
|
||||
void on_serverPort_valueChanged(int value);
|
||||
void on_lockToIPv4_stateChanged(int state);
|
||||
void on_serverRestart_clicked();
|
||||
void updateServerStatus();
|
||||
void on_clientSettings_toggled(bool on);
|
||||
void on_clientHostname_textChanged(const QString &text);
|
||||
void on_clientPort_valueChanged(int value);
|
||||
void on_restrictSend_stateChanged(int state);
|
||||
void on_clientReconnect_clicked();
|
||||
void updateClientStatus();
|
||||
|
||||
void on_sceneGroupAdd_clicked();
|
||||
void on_sceneGroupRemove_clicked();
|
||||
void on_sceneGroupUp_clicked();
|
||||
@@ -213,6 +249,7 @@ public slots:
|
||||
void on_threadPriority_currentTextChanged(const QString &text);
|
||||
|
||||
void updateScreenRegionCursorPos();
|
||||
void updateStatus();
|
||||
|
||||
void on_close_clicked();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ struct SceneGroup {
|
||||
|
||||
int currentCount = -1;
|
||||
std::chrono::high_resolution_clock::time_point lastAdvTime;
|
||||
OBSWeakSource lastRandomScene = nullptr;
|
||||
int lastRandomScene = -1;
|
||||
|
||||
inline SceneGroup(){};
|
||||
inline SceneGroup(std::string name_) : name(name_){};
|
||||
|
||||
@@ -23,6 +23,10 @@ enum class sceneTriggerAction {
|
||||
STOP_REPLAY_BUFFER,
|
||||
|
||||
MUTE_SOURCE,
|
||||
UNMUTE_SOURCE,
|
||||
|
||||
START_SWITCHER,
|
||||
STOP_SWITCHER,
|
||||
};
|
||||
|
||||
struct SceneTrigger : SceneSwitcherEntry {
|
||||
@@ -36,7 +40,8 @@ struct SceneTrigger : SceneSwitcherEntry {
|
||||
void save(obs_data_t *obj);
|
||||
void load(obs_data_t *obj);
|
||||
|
||||
bool checkMatch(OBSWeakSource previousScene);
|
||||
bool checkMatch(OBSWeakSource currentScene,
|
||||
OBSWeakSource previousScene);
|
||||
void performAction();
|
||||
void logMatch();
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <QSpinBox>
|
||||
#include <limits>
|
||||
|
||||
#include "switch-generic.hpp"
|
||||
#include "volume-control.hpp"
|
||||
@@ -18,8 +19,9 @@ struct AudioSwitch : virtual SceneSwitcherEntry {
|
||||
int volumeThreshold = 0;
|
||||
audioCondition condition = ABOVE;
|
||||
double duration = 0;
|
||||
bool ignoreInactiveSource = true;
|
||||
unsigned int matchCount = 0;
|
||||
float peak = -1;
|
||||
float peak = -std::numeric_limits<float>::infinity();
|
||||
obs_volmeter_t *volmeter = nullptr;
|
||||
|
||||
const char *getType() { return "audio"; }
|
||||
@@ -69,12 +71,14 @@ private slots:
|
||||
void VolumeThresholdChanged(int vol);
|
||||
void ConditionChanged(int cond);
|
||||
void DurationChanged(double dur);
|
||||
void IgnoreInactiveChanged(int state);
|
||||
|
||||
private:
|
||||
QComboBox *audioSources;
|
||||
QComboBox *condition;
|
||||
QSpinBox *audioVolumeThreshold;
|
||||
QDoubleSpinBox *duration;
|
||||
QCheckBox *ignoreInactiveSource;
|
||||
VolControl *volMeter;
|
||||
|
||||
AudioSwitch *switchData;
|
||||
|
||||
@@ -8,6 +8,7 @@ struct SceneSwitcherEntry {
|
||||
OBSWeakSource scene = nullptr;
|
||||
OBSWeakSource transition = nullptr;
|
||||
bool usePreviousScene = false;
|
||||
bool useCurrentTransition = false;
|
||||
|
||||
virtual const char *getType() = 0;
|
||||
virtual bool initialized();
|
||||
@@ -64,14 +65,15 @@ class SwitchWidget : public QWidget {
|
||||
|
||||
public:
|
||||
SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
|
||||
bool usePreviousScene = true, bool addSceneGroup = false);
|
||||
bool usePreviousScene = true, bool addSceneGroup = false,
|
||||
bool addCurrentTransition = true);
|
||||
virtual SceneSwitcherEntry *getSwitchData();
|
||||
virtual void setSwitchData(SceneSwitcherEntry *s);
|
||||
void showSwitchData();
|
||||
|
||||
static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2);
|
||||
|
||||
private slots:
|
||||
protected slots:
|
||||
void SceneChanged(const QString &text);
|
||||
void TransitionChanged(const QString &text);
|
||||
void SceneGroupAdd(const QString &name);
|
||||
|
||||
144
src/headers/switch-network.hpp
Normal file
144
src/headers/switch-network.hpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Most of this code is based on https://github.com/Palakis/obs-websocket
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Due to iussues with asio the network functionality will be disabled on macOS
|
||||
#if !(__APPLE__)
|
||||
|
||||
#include <set>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QVariantHash>
|
||||
#include <QtCore/QThreadPool>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <websocketpp/config/asio_no_tls.hpp>
|
||||
#include <websocketpp/server.hpp>
|
||||
|
||||
#include <websocketpp/config/asio_no_tls_client.hpp>
|
||||
#include <websocketpp/client.hpp>
|
||||
|
||||
using websocketpp::connection_hdl;
|
||||
typedef websocketpp::server<websocketpp::config::asio> server;
|
||||
typedef websocketpp::client<websocketpp::config::asio_client> client;
|
||||
|
||||
class WSServer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSServer();
|
||||
virtual ~WSServer();
|
||||
void start(quint16 port, bool lockToIPv4);
|
||||
void stop();
|
||||
void sendMessage(OBSWeakSource scene, OBSWeakSource transition);
|
||||
QThreadPool *threadPool() { return &_threadPool; }
|
||||
|
||||
private:
|
||||
void onOpen(connection_hdl hdl);
|
||||
void onMessage(connection_hdl hdl, server::message_ptr message);
|
||||
void onClose(connection_hdl hdl);
|
||||
|
||||
QString getRemoteEndpoint(connection_hdl hdl);
|
||||
|
||||
server _server;
|
||||
quint16 _serverPort;
|
||||
bool _lockToIPv4;
|
||||
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
|
||||
QMutex _clMutex;
|
||||
QThreadPool _threadPool;
|
||||
};
|
||||
|
||||
class WSClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSClient();
|
||||
virtual ~WSClient();
|
||||
void connect(std::string uri);
|
||||
void disconnect();
|
||||
std::string getFail() { return _failMsg; }
|
||||
|
||||
private:
|
||||
void onOpen(connection_hdl hdl);
|
||||
void onFail(connection_hdl hdl);
|
||||
void onMessage(connection_hdl hdl, client::message_ptr message);
|
||||
void onClose(connection_hdl hdl);
|
||||
void connectThread();
|
||||
|
||||
client _client;
|
||||
std::string _uri;
|
||||
connection_hdl _connection;
|
||||
std::thread _thread;
|
||||
bool _retry = false;
|
||||
std::atomic_bool _connected = {false};
|
||||
std::mutex _waitMtx;
|
||||
std::condition_variable _cv;
|
||||
std::string _failMsg;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class WSServer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSServer(){};
|
||||
virtual ~WSServer(){};
|
||||
void start(quint16 port, bool lockToIPv4){};
|
||||
void stop(){};
|
||||
void sendMessage(OBSWeakSource scene, OBSWeakSource transition){};
|
||||
};
|
||||
|
||||
class WSClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSClient(){};
|
||||
virtual ~WSClient(){};
|
||||
void connect(std::string uri){};
|
||||
void disconnect(){};
|
||||
std::string getFail() { return _failMsg; }
|
||||
|
||||
private:
|
||||
std::string _failMsg;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class NetworkConfig {
|
||||
public:
|
||||
NetworkConfig();
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj);
|
||||
void SetDefaults(obs_data_t *obj);
|
||||
|
||||
std::string GetClientUri();
|
||||
|
||||
// Server
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
bool LockToIPv4;
|
||||
|
||||
// Client
|
||||
bool ClientEnabled;
|
||||
std::string Address;
|
||||
uint64_t ClientPort;
|
||||
bool SendAll;
|
||||
};
|
||||
|
||||
enum class ServerStatus {
|
||||
NOT_RUNNING,
|
||||
STARTING,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
enum class ClientStatus {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAIL,
|
||||
};
|
||||
@@ -19,6 +19,7 @@ enum class PauseTarget {
|
||||
Idle,
|
||||
Sequence,
|
||||
Audio,
|
||||
Video,
|
||||
};
|
||||
|
||||
struct PauseEntry : SceneSwitcherEntry {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "switch-generic.hpp"
|
||||
|
||||
constexpr auto round_trip_func = 1;
|
||||
@@ -12,42 +14,77 @@ typedef enum {
|
||||
|
||||
struct SceneSequenceSwitch : SceneSwitcherEntry {
|
||||
static bool pause;
|
||||
SwitchTargetType startTargetType = SwitchTargetType::Scene;
|
||||
OBSWeakSource startScene = nullptr;
|
||||
double delay = 0;
|
||||
int delayMultiplier = 1;
|
||||
bool interruptible = false;
|
||||
unsigned int matchCount = 0;
|
||||
|
||||
// nullptr marks start point and reaching end of extended sequence
|
||||
SceneSequenceSwitch *activeSequence = nullptr;
|
||||
|
||||
std::unique_ptr<SceneSequenceSwitch> extendedSequence = nullptr;
|
||||
|
||||
const char *getType() { return "sequence"; }
|
||||
bool initialized();
|
||||
bool valid();
|
||||
void save(obs_data_t *obj);
|
||||
void load(obs_data_t *obj);
|
||||
void save(obs_data_t *obj, bool saveExt = true);
|
||||
void load(obs_data_t *obj, bool saveExt = true);
|
||||
|
||||
bool reduce();
|
||||
SceneSequenceSwitch *extend();
|
||||
|
||||
bool checkMatch(OBSWeakSource currentScene, int &linger,
|
||||
SceneSequenceSwitch *root = nullptr);
|
||||
bool checkDurationMatchInterruptible();
|
||||
void prepareUninterruptibleMatch(OBSWeakSource currentScene,
|
||||
int &linger);
|
||||
void advanceActiveSequence();
|
||||
void logAdvanceSequence();
|
||||
void logSequenceCanceled();
|
||||
};
|
||||
|
||||
class SequenceWidget : public SwitchWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s);
|
||||
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
bool extendSequence = false, bool editExtendMode = false,
|
||||
bool showExtendText = true);
|
||||
SceneSequenceSwitch *getSwitchData();
|
||||
void setSwitchData(SceneSequenceSwitch *s);
|
||||
|
||||
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
|
||||
|
||||
void UpdateDelay();
|
||||
void UpdateWidgetStatus(bool showExtendText);
|
||||
void setExtendedSequenceStartScene();
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const QString &text);
|
||||
void DelayChanged(double delay);
|
||||
void DelayUnitsChanged(int idx);
|
||||
void StartSceneChanged(const QString &text);
|
||||
void InterruptibleChanged(int state);
|
||||
void ExtendClicked();
|
||||
void ReduceClicked();
|
||||
|
||||
private:
|
||||
protected:
|
||||
QDoubleSpinBox *delay;
|
||||
QComboBox *delayUnits;
|
||||
QComboBox *startScenes;
|
||||
QCheckBox *interruptible;
|
||||
QVBoxLayout *extendSequenceLayout;
|
||||
QPushButton *extend;
|
||||
QPushButton *reduce;
|
||||
|
||||
// I would prefer having a list of only widgets of type editExtendMode
|
||||
// but I am not sure how to best implement that using a QListWidget.
|
||||
//
|
||||
// So use edit button to bring up edit widget and
|
||||
// add a label to disaplay current extended sequence state.
|
||||
QLabel *extendText;
|
||||
|
||||
SceneSequenceSwitch *switchData;
|
||||
};
|
||||
|
||||
@@ -18,8 +18,11 @@ struct SceneTransition : SceneSwitcherEntry {
|
||||
|
||||
struct DefaultSceneTransition : SceneSwitcherEntry {
|
||||
static bool pause;
|
||||
static unsigned int delay;
|
||||
|
||||
const char *getType() { return "def_transition"; }
|
||||
bool checkMatch(OBSWeakSource currentScene);
|
||||
void setTransition();
|
||||
|
||||
inline DefaultSceneTransition(){};
|
||||
inline DefaultSceneTransition(OBSWeakSource scene_,
|
||||
|
||||
98
src/headers/switch-video.hpp
Normal file
98
src/headers/switch-video.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "switch-generic.hpp"
|
||||
|
||||
constexpr auto video_func = 9;
|
||||
constexpr auto default_priority_9 = video_func;
|
||||
|
||||
enum class videoSwitchType {
|
||||
MATCH,
|
||||
DIFFER,
|
||||
HAS_NOT_CHANGED,
|
||||
};
|
||||
|
||||
class AdvSSScreenshotObj {
|
||||
public:
|
||||
AdvSSScreenshotObj(obs_source_t *source);
|
||||
~AdvSSScreenshotObj();
|
||||
|
||||
void Screenshot();
|
||||
void Download();
|
||||
void Copy();
|
||||
void MarkDone();
|
||||
|
||||
gs_texrender_t *texrender = nullptr;
|
||||
gs_stagesurf_t *stagesurf = nullptr;
|
||||
OBSWeakSource weakSource;
|
||||
std::string path;
|
||||
QImage image;
|
||||
uint32_t cx = 0;
|
||||
uint32_t cy = 0;
|
||||
|
||||
int stage = 0;
|
||||
|
||||
bool done = false;
|
||||
std::chrono::high_resolution_clock::time_point time;
|
||||
};
|
||||
|
||||
struct VideoSwitch : virtual SceneSwitcherEntry {
|
||||
static bool pause;
|
||||
|
||||
videoSwitchType condition = videoSwitchType::MATCH;
|
||||
OBSWeakSource videoSource = nullptr;
|
||||
std::string file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
double duration = 0;
|
||||
bool ignoreInactiveSource = false;
|
||||
|
||||
std::unique_ptr<AdvSSScreenshotObj> screenshotData = nullptr;
|
||||
std::chrono::high_resolution_clock::time_point previousTime{};
|
||||
QImage matchImage;
|
||||
|
||||
std::chrono::milliseconds currentMatchDuration{};
|
||||
|
||||
const char *getType() { return "video"; }
|
||||
bool initialized();
|
||||
bool valid();
|
||||
void save(obs_data_t *obj);
|
||||
void load(obs_data_t *obj);
|
||||
void getScreenshot();
|
||||
bool loadImageFromFile();
|
||||
bool checkMatch();
|
||||
|
||||
VideoSwitch(){};
|
||||
friend void swap(VideoSwitch &first, VideoSwitch &second);
|
||||
};
|
||||
|
||||
class VideoSwitchWidget : public SwitchWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoSwitchWidget(QWidget *parent, VideoSwitch *s);
|
||||
VideoSwitch *getSwitchData();
|
||||
void setSwitchData(VideoSwitch *s);
|
||||
|
||||
static void swapSwitchData(VideoSwitchWidget *as1,
|
||||
VideoSwitchWidget *as2);
|
||||
|
||||
void UpdatePreviewTooltip();
|
||||
void SetFilePath(const QString &text);
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const QString &text);
|
||||
void ConditionChanged(int cond);
|
||||
void DurationChanged(double dur);
|
||||
void FilePathChanged();
|
||||
void BrowseButtonClicked();
|
||||
void IgnoreInactiveChanged(int state);
|
||||
|
||||
private:
|
||||
QComboBox *videoSources;
|
||||
QComboBox *condition;
|
||||
QDoubleSpinBox *duration;
|
||||
QLineEdit *filePath;
|
||||
QPushButton *browseButton;
|
||||
QCheckBox *ignoreInactiveSource;
|
||||
|
||||
VideoSwitch *switchData;
|
||||
};
|
||||
@@ -21,9 +21,12 @@
|
||||
#include "switch-transitions.hpp"
|
||||
#include "switch-window.hpp"
|
||||
#include "switch-sequence.hpp"
|
||||
#include "switch-video.hpp"
|
||||
#include "switch-network.hpp"
|
||||
|
||||
constexpr auto default_interval = 300;
|
||||
constexpr auto previous_scene_name = "Previous Scene";
|
||||
constexpr auto current_transition_name = "Current Transition";
|
||||
|
||||
typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch;
|
||||
typedef enum { PERSIST = 0, START = 1, STOP = 2 } StartupBehavior;
|
||||
@@ -66,9 +69,10 @@ struct SwitcherData {
|
||||
OBSWeakSource previousScene = nullptr;
|
||||
OBSWeakSource previousSceneHelper = nullptr;
|
||||
OBSWeakSource lastRandomScene;
|
||||
SceneGroup *lastRandomSceneGroup;
|
||||
OBSWeakSource nonMatchingScene;
|
||||
NoMatch switchIfNotMatching = NO_SWITCH;
|
||||
double noMatchDelay;
|
||||
double noMatchDelay = 0;
|
||||
double noMatchCount = 0;
|
||||
StartupBehavior startupBehavior = PERSIST;
|
||||
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
|
||||
@@ -84,6 +88,7 @@ struct SwitcherData {
|
||||
|
||||
std::vector<std::string> ignoreWindowsSwitches;
|
||||
|
||||
bool uninterruptibleSceneSequenceActive = false;
|
||||
std::deque<SceneSequenceSwitch> sceneSequenceSwitches;
|
||||
|
||||
std::deque<RandomSwitch> randomSwitches;
|
||||
@@ -100,7 +105,6 @@ struct SwitcherData {
|
||||
|
||||
std::deque<SceneTransition> sceneTransitions;
|
||||
std::deque<DefaultSceneTransition> defaultSceneTransitions;
|
||||
bool checkedDefTransition = false;
|
||||
|
||||
std::deque<MediaSwitch> mediaSwitches;
|
||||
|
||||
@@ -112,12 +116,21 @@ struct SwitcherData {
|
||||
std::deque<AudioSwitch> audioSwitches;
|
||||
AudioSwitchFallback audioFallback;
|
||||
|
||||
WSServer server;
|
||||
ServerStatus serverStatus = ServerStatus::NOT_RUNNING;
|
||||
WSClient client;
|
||||
ClientStatus clientStatus = ClientStatus::DISCONNECTED;
|
||||
NetworkConfig networkConfig;
|
||||
|
||||
std::deque<VideoSwitch> videoSwitches;
|
||||
|
||||
std::deque<SceneGroup> sceneGroups;
|
||||
|
||||
std::vector<int> functionNamesByPriority = std::vector<int>{
|
||||
default_priority_0, default_priority_1, default_priority_2,
|
||||
default_priority_3, default_priority_4, default_priority_5,
|
||||
default_priority_6, default_priority_7, default_priority_8};
|
||||
default_priority_6, default_priority_7, default_priority_8,
|
||||
default_priority_9};
|
||||
|
||||
struct ThreadPrio {
|
||||
std::string name;
|
||||
@@ -153,6 +166,10 @@ struct SwitcherData {
|
||||
obs_hotkey_id stopHotkey = 0;
|
||||
obs_hotkey_id toggleHotkey = 0;
|
||||
|
||||
bool saveWindowGeo = false;
|
||||
QPoint windowPos = {};
|
||||
QSize windowSize = {};
|
||||
|
||||
bool versionChanged(obs_data_t *obj, std::string currentVersion);
|
||||
|
||||
void Thread();
|
||||
@@ -160,13 +177,17 @@ struct SwitcherData {
|
||||
void Stop();
|
||||
|
||||
bool sceneChangedDuringWait();
|
||||
|
||||
bool prioFuncsValid();
|
||||
|
||||
bool tabOrderValid();
|
||||
void resetTabOrder();
|
||||
|
||||
void writeSceneInfoToFile();
|
||||
void writeToStatusFile(QString status);
|
||||
bool checkPause();
|
||||
void checkDefaultSceneTransitions(bool &match,
|
||||
OBSWeakSource &transition);
|
||||
void setCurrentDefTransition(OBSWeakSource &transition);
|
||||
|
||||
bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
|
||||
int &linger);
|
||||
void checkSceneSequence(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition, int &linger);
|
||||
void checkIdleSwitch(bool &match, OBSWeakSource &scene,
|
||||
@@ -191,10 +212,14 @@ struct SwitcherData {
|
||||
OBSWeakSource &transition);
|
||||
void checkAudioSwitchFallback(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition);
|
||||
void checkVideoSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition);
|
||||
void checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition, int &sleep);
|
||||
void checkSwitchCooldown(bool &match);
|
||||
void checkTriggers();
|
||||
bool checkPause();
|
||||
void checkDefaultSceneTransitions();
|
||||
|
||||
void saveSettings(obs_data_t *obj);
|
||||
void saveWindowTitleSwitches(obs_data_t *obj);
|
||||
@@ -211,6 +236,8 @@ struct SwitcherData {
|
||||
void saveAudioSwitches(obs_data_t *obj);
|
||||
void saveSceneGroups(obs_data_t *obj);
|
||||
void saveSceneTriggers(obs_data_t *obj);
|
||||
void saveVideoSwitches(obs_data_t *obj);
|
||||
void saveNetworkSwitches(obs_data_t *obj);
|
||||
void saveGeneralSettings(obs_data_t *obj);
|
||||
void saveHotkeys(obs_data_t *obj);
|
||||
void saveVersion(obs_data_t *obj, std::string currentVersion);
|
||||
@@ -231,6 +258,8 @@ struct SwitcherData {
|
||||
void loadAudioSwitches(obs_data_t *obj);
|
||||
void loadSceneGroups(obs_data_t *obj);
|
||||
void loadSceneTriggers(obs_data_t *obj);
|
||||
void loadVideoSwitches(obs_data_t *obj);
|
||||
void loadNetworkSettings(obs_data_t *obj);
|
||||
void loadGeneralSettings(obs_data_t *obj);
|
||||
void loadHotkeys(obs_data_t *obj);
|
||||
|
||||
|
||||
@@ -51,27 +51,26 @@ OBSWeakSource SceneGroup::getNextSceneTime()
|
||||
OBSWeakSource SceneGroup::getNextSceneRandom()
|
||||
{
|
||||
if (scenes.size() == 1) {
|
||||
return *scenes.begin();
|
||||
currentIdx = 0;
|
||||
return scenes[currentIdx];
|
||||
}
|
||||
|
||||
std::vector<OBSWeakSource> rs(scenes);
|
||||
std::random_device rng;
|
||||
std::mt19937 urng(rng());
|
||||
std::shuffle(rs.begin(), rs.end(), urng);
|
||||
for (OBSWeakSource &s : rs) {
|
||||
if (s == lastRandomScene) {
|
||||
continue;
|
||||
}
|
||||
int rIdx = lastRandomScene;
|
||||
do {
|
||||
rIdx = rand() % scenes.size();
|
||||
} while (rIdx == lastRandomScene);
|
||||
|
||||
lastRandomScene = s;
|
||||
lastRandomScene = rIdx;
|
||||
currentIdx = rIdx;
|
||||
|
||||
return s;
|
||||
}
|
||||
return nullptr;
|
||||
return scenes[currentIdx];
|
||||
}
|
||||
|
||||
OBSWeakSource SceneGroup::getCurrentScene()
|
||||
{
|
||||
if (scenes.size() < currentIdx + 1) {
|
||||
return nullptr;
|
||||
}
|
||||
return scenes[currentIdx];
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,15 @@ void SceneTrigger::logMatch()
|
||||
case sceneTriggerAction::MUTE_SOURCE:
|
||||
actionName = "MUTE (" + GetWeakSourceName(audioSource) + ")";
|
||||
break;
|
||||
case sceneTriggerAction::UNMUTE_SOURCE:
|
||||
actionName = "UNMUTE (" + GetWeakSourceName(audioSource) + ")";
|
||||
break;
|
||||
case sceneTriggerAction::START_SWITCHER:
|
||||
actionName = "START SCENE SWITCHER";
|
||||
break;
|
||||
case sceneTriggerAction::STOP_SWITCHER:
|
||||
actionName = "STOP SCENE SWITCHER";
|
||||
break;
|
||||
default:
|
||||
actionName = "UNKOWN";
|
||||
break;
|
||||
@@ -176,24 +185,35 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
|
||||
case sceneTriggerAction::STOP_REPLAY_BUFFER:
|
||||
obs_frontend_replay_buffer_stop();
|
||||
break;
|
||||
case sceneTriggerAction::MUTE_SOURCE:
|
||||
obs_frontend_replay_buffer_stop();
|
||||
break;
|
||||
default:
|
||||
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
|
||||
static_cast<int>(action));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void muteThread(OBSWeakSource source, double delay)
|
||||
void muteThread(OBSWeakSource source, double delay, bool mute)
|
||||
{
|
||||
long long mil = delay * 1000;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
|
||||
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
obs_source_set_muted(s, true);
|
||||
obs_source_set_muted(s, mute);
|
||||
obs_source_release(s);
|
||||
}
|
||||
|
||||
void statusThread(double delay, bool stop)
|
||||
{
|
||||
long long mil = delay * 1000;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
|
||||
|
||||
if (stop) {
|
||||
switcher->Stop();
|
||||
} else {
|
||||
switcher->Start();
|
||||
}
|
||||
}
|
||||
|
||||
bool isFrontendAction(sceneTriggerAction triggerAction)
|
||||
{
|
||||
return triggerAction == sceneTriggerAction::START_RECORDING ||
|
||||
@@ -206,6 +226,18 @@ bool isFrontendAction(sceneTriggerAction triggerAction)
|
||||
triggerAction == sceneTriggerAction::STOP_REPLAY_BUFFER;
|
||||
}
|
||||
|
||||
bool isAudioAction(sceneTriggerAction t)
|
||||
{
|
||||
return t == sceneTriggerAction::MUTE_SOURCE ||
|
||||
t == sceneTriggerAction::UNMUTE_SOURCE;
|
||||
}
|
||||
|
||||
bool isSwitcherStatusAction(sceneTriggerAction t)
|
||||
{
|
||||
return t == sceneTriggerAction::START_SWITCHER ||
|
||||
t == sceneTriggerAction::STOP_SWITCHER;
|
||||
}
|
||||
|
||||
void SceneTrigger::performAction()
|
||||
{
|
||||
if (triggerAction == sceneTriggerAction::NONE) {
|
||||
@@ -216,20 +248,23 @@ void SceneTrigger::performAction()
|
||||
|
||||
if (isFrontendAction(triggerAction)) {
|
||||
t = std::thread(frontEndActionThread, triggerAction, duration);
|
||||
} else if (isAudioAction(triggerAction)) {
|
||||
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
|
||||
t = std::thread(muteThread, audioSource, duration, mute);
|
||||
} else if (isSwitcherStatusAction(triggerAction)) {
|
||||
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
|
||||
t = std::thread(statusThread, duration, stop);
|
||||
} else {
|
||||
t = std::thread(muteThread, audioSource, duration);
|
||||
blog(LOG_WARNING, "ignoring unkown action '%d'",
|
||||
static_cast<int>(triggerAction));
|
||||
}
|
||||
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool SceneTrigger::checkMatch(OBSWeakSource previousScene)
|
||||
bool SceneTrigger::checkMatch(OBSWeakSource currentScene,
|
||||
OBSWeakSource previousScene)
|
||||
{
|
||||
OBSSource source = obs_frontend_get_current_scene();
|
||||
OBSWeakSource currentScene = obs_source_get_weak_source(source);
|
||||
obs_source_release(source);
|
||||
obs_weak_source_release(currentScene);
|
||||
|
||||
switch (triggerType) {
|
||||
case sceneTriggerType::NONE:
|
||||
return false;
|
||||
@@ -245,16 +280,26 @@ bool SceneTrigger::checkMatch(OBSWeakSource previousScene)
|
||||
|
||||
void SwitcherData::checkTriggers()
|
||||
{
|
||||
if (SceneTrigger::pause || stop) {
|
||||
if (SceneTrigger::pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSource source = obs_frontend_get_current_scene();
|
||||
OBSWeakSource currentScene = obs_source_get_weak_source(source);
|
||||
|
||||
for (auto &t : sceneTriggers) {
|
||||
if (t.checkMatch(previousScene)) {
|
||||
if (stop && !isSwitcherStatusAction(t.triggerAction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (t.checkMatch(currentScene, previousScene)) {
|
||||
t.logMatch();
|
||||
t.performAction();
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_release(source);
|
||||
obs_weak_source_release(currentScene);
|
||||
}
|
||||
|
||||
void SwitcherData::saveSceneTriggers(obs_data_t *obj)
|
||||
@@ -395,19 +440,12 @@ void AdvSceneSwitcher::setupTriggerTab()
|
||||
|
||||
void SceneTrigger::save(obs_data_t *obj)
|
||||
{
|
||||
obs_source_t *sceneSource = obs_weak_source_get_source(scene);
|
||||
const char *sceneName = obs_source_get_name(sceneSource);
|
||||
obs_data_set_string(obj, "scene", sceneName);
|
||||
obs_source_release(sceneSource);
|
||||
|
||||
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
|
||||
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
|
||||
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
|
||||
obs_source_t *source = obs_weak_source_get_source(audioSource);
|
||||
const char *audioSourceName = obs_source_get_name(source);
|
||||
obs_data_set_string(obj, "audioSource", audioSourceName);
|
||||
obs_source_release(source);
|
||||
obs_data_set_string(obj, "audioSource",
|
||||
GetWeakSourceName(audioSource).c_str());
|
||||
}
|
||||
|
||||
void SceneTrigger::load(obs_data_t *obj)
|
||||
@@ -425,10 +463,13 @@ void SceneTrigger::load(obs_data_t *obj)
|
||||
audioSource = GetWeakSourceByName(audioSourceName);
|
||||
}
|
||||
|
||||
inline void populateTriggers(QComboBox *list)
|
||||
static inline void populateTriggers(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
|
||||
AdvSceneSwitcher::addSelectionEntry(
|
||||
list,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
|
||||
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive"));
|
||||
list->addItem(obs_module_text(
|
||||
@@ -439,8 +480,11 @@ inline void populateTriggers(QComboBox *list)
|
||||
|
||||
inline void populateActions(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
|
||||
AdvSceneSwitcher::addSelectionEntry(
|
||||
list,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
|
||||
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording"));
|
||||
list->addItem(obs_module_text(
|
||||
@@ -459,6 +503,12 @@ inline void populateActions(QComboBox *list)
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher"));
|
||||
}
|
||||
|
||||
SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
|
||||
@@ -495,7 +545,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
|
||||
audioSources->setCurrentText(
|
||||
GetWeakSourceName(s->audioSource).c_str());
|
||||
|
||||
if (s->triggerAction == sceneTriggerAction::MUTE_SOURCE) {
|
||||
if (isAudioAction(s->triggerAction)) {
|
||||
audioSources->show();
|
||||
} else {
|
||||
audioSources->hide();
|
||||
@@ -560,7 +610,7 @@ void SceneTriggerWidget::TriggerActionChanged(int index)
|
||||
static_cast<sceneTriggerAction>(index);
|
||||
}
|
||||
|
||||
if (switchData->triggerAction == sceneTriggerAction::MUTE_SOURCE) {
|
||||
if (isAudioAction(switchData->triggerAction)) {
|
||||
audioSources->show();
|
||||
} else {
|
||||
audioSources->hide();
|
||||
|
||||
@@ -125,9 +125,16 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
|
||||
continue;
|
||||
}
|
||||
|
||||
obs_source_t *as = obs_weak_source_get_source(s.audioSource);
|
||||
bool audioActive = obs_source_active(as);
|
||||
obs_source_release(as);
|
||||
if (s.ignoreInactiveSource) {
|
||||
obs_source_t *as =
|
||||
obs_weak_source_get_source(s.audioSource);
|
||||
bool audioActive = obs_source_active(as);
|
||||
obs_source_release(as);
|
||||
|
||||
if (!audioActive) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// peak will have a value from -60 db to 0 db
|
||||
bool volumeThresholdreached = false;
|
||||
@@ -154,7 +161,7 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
|
||||
1000.0 >=
|
||||
s.duration;
|
||||
|
||||
if (volumeThresholdreached && durationReached && audioActive) {
|
||||
if (volumeThresholdreached && durationReached) {
|
||||
if (match) {
|
||||
checkAudioSwitchFallback(scene, transition);
|
||||
fallbackChecked = true;
|
||||
@@ -250,7 +257,7 @@ void AudioSwitch::setVolumeLevel(void *data,
|
||||
UNUSED_PARAMETER(inputPeak);
|
||||
AudioSwitch *s = static_cast<AudioSwitch *>(data);
|
||||
|
||||
for (int i = 1; i < MAX_AUDIO_CHANNELS; i++) {
|
||||
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
|
||||
if (peak[i] > s->peak) {
|
||||
s->peak = peak[i];
|
||||
}
|
||||
@@ -301,6 +308,7 @@ void AudioSwitch::save(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "volume", volumeThreshold);
|
||||
obs_data_set_int(obj, "condition", condition);
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
|
||||
}
|
||||
|
||||
// To be removed in future version
|
||||
@@ -346,6 +354,7 @@ void AudioSwitch::load(obs_data_t *obj)
|
||||
volumeThreshold = obs_data_get_int(obj, "volume");
|
||||
condition = (audioCondition)obs_data_get_int(obj, "condition");
|
||||
duration = obs_data_get_double(obj, "duration");
|
||||
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
|
||||
|
||||
volmeter = AddVolmeterToSource(this, audioSource);
|
||||
}
|
||||
@@ -438,7 +447,7 @@ void swap(AudioSwitch &first, AudioSwitch &second)
|
||||
second.resetVolmeter();
|
||||
}
|
||||
|
||||
void populateConditionSelection(QComboBox *list)
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.audioTab.condition.above"));
|
||||
@@ -453,6 +462,8 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
|
||||
condition = new QComboBox();
|
||||
audioVolumeThreshold = new QSpinBox();
|
||||
duration = new QDoubleSpinBox();
|
||||
ignoreInactiveSource = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.audioTab.ignoreInactiveSource"));
|
||||
|
||||
obs_source_t *source = nullptr;
|
||||
if (s) {
|
||||
@@ -482,6 +493,8 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
|
||||
QWidget::connect(audioSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SourceChanged(const QString &)));
|
||||
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreInactiveChanged(int)));
|
||||
|
||||
AdvSceneSwitcher::populateAudioSelection(audioSources);
|
||||
populateConditionSelection(condition);
|
||||
@@ -492,6 +505,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
|
||||
audioVolumeThreshold->setValue(s->volumeThreshold);
|
||||
condition->setCurrentIndex(s->condition);
|
||||
duration->setValue(s->duration);
|
||||
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
|
||||
}
|
||||
|
||||
QHBoxLayout *switchLayout = new QHBoxLayout;
|
||||
@@ -500,6 +514,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
|
||||
{"{{volumeWidget}}", audioVolumeThreshold},
|
||||
{"{{condition}}", condition},
|
||||
{"{{duration}}", duration},
|
||||
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{transitions}}", transitions}};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.audioTab.entry"),
|
||||
@@ -599,6 +614,16 @@ void AudioSwitchWidget::DurationChanged(double dur)
|
||||
switchData->duration = dur;
|
||||
}
|
||||
|
||||
void AudioSwitchWidget::IgnoreInactiveChanged(int state)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->ignoreInactiveSource = state;
|
||||
}
|
||||
|
||||
AudioSwitchFallbackWidget::AudioSwitchFallbackWidget(QWidget *parent,
|
||||
AudioSwitchFallback *s)
|
||||
: SwitchWidget(parent, s, true, true)
|
||||
|
||||
@@ -118,29 +118,37 @@ void SwitcherData::writeToStatusFile(QString msg)
|
||||
void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
if (!fileIO.readEnabled || fileIO.readPath.empty()) {
|
||||
if (!fileIO.readEnabled || fileIO.readPath.empty() ||
|
||||
FileSwitch::pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(QString::fromStdString(fileIO.readPath));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QTextStream in(&file);
|
||||
QString sceneStr = in.readLine();
|
||||
obs_source_t *sceneRead =
|
||||
obs_get_source_by_name(sceneStr.toUtf8().constData());
|
||||
if (sceneRead) {
|
||||
obs_weak_source_t *sceneReadWs =
|
||||
obs_source_get_weak_source(sceneRead);
|
||||
|
||||
match = true;
|
||||
scene = sceneReadWs;
|
||||
transition = nullptr;
|
||||
|
||||
obs_weak_source_release(sceneReadWs);
|
||||
obs_source_release(sceneRead);
|
||||
}
|
||||
file.close();
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream in(&file);
|
||||
|
||||
QString sceneStr = in.readLine();
|
||||
OBSWeakSource sceneRead = GetWeakSourceByQString(sceneStr);
|
||||
|
||||
QString transitionStr = in.readLine();
|
||||
OBSWeakSource transitionRead =
|
||||
GetWeakTransitionByQString(transitionStr);
|
||||
|
||||
if (sceneRead) {
|
||||
match = true;
|
||||
scene = sceneRead;
|
||||
transition = transitionRead;
|
||||
|
||||
vblog(LOG_INFO,
|
||||
"match for 'file' - read scene '%s' and transition '%s' from file '%s'",
|
||||
sceneStr.toUtf8().constData(),
|
||||
transitionStr.toUtf8().constData(),
|
||||
fileIO.readPath.c_str());
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
|
||||
|
||||
@@ -5,14 +5,15 @@ bool SceneSwitcherEntry::initialized()
|
||||
{
|
||||
return (usePreviousScene || WeakSourceValid(scene) ||
|
||||
SceneGroupValid(group)) &&
|
||||
transition;
|
||||
(useCurrentTransition || transition);
|
||||
}
|
||||
|
||||
bool SceneSwitcherEntry::valid()
|
||||
{
|
||||
return !initialized() || ((usePreviousScene || WeakSourceValid(scene) ||
|
||||
SceneGroupValid(group)) &&
|
||||
WeakSourceValid(transition));
|
||||
return !initialized() ||
|
||||
((usePreviousScene || WeakSourceValid(scene) ||
|
||||
SceneGroupValid(group)) &&
|
||||
(useCurrentTransition || WeakSourceValid(transition)));
|
||||
}
|
||||
|
||||
void SceneSwitcherEntry::logMatchScene()
|
||||
@@ -82,8 +83,11 @@ void SceneSwitcherEntry::save(obs_data_t *obj, const char *targetTypeSaveName,
|
||||
|
||||
obs_data_set_string(obj, targetSaveName, targetName.c_str());
|
||||
|
||||
obs_data_set_string(obj, transitionSaveName,
|
||||
GetWeakSourceName(transition).c_str());
|
||||
std::string transitionName = current_transition_name;
|
||||
if (!useCurrentTransition) {
|
||||
transitionName = GetWeakSourceName(transition);
|
||||
}
|
||||
obs_data_set_string(obj, transitionSaveName, transitionName.c_str());
|
||||
}
|
||||
|
||||
void SceneSwitcherEntry::load(obs_data_t *obj, const char *targetTypeLoadName,
|
||||
@@ -104,11 +108,14 @@ void SceneSwitcherEntry::load(obs_data_t *obj, const char *targetTypeLoadName,
|
||||
group = GetSceneGroupByName(targetName);
|
||||
}
|
||||
|
||||
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
|
||||
|
||||
const char *transitionName =
|
||||
obs_data_get_string(obj, transitionLoadName);
|
||||
transition = GetWeakTransitionByName(transitionName);
|
||||
|
||||
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
|
||||
useCurrentTransition =
|
||||
strcmp(transitionName, current_transition_name) == 0;
|
||||
}
|
||||
|
||||
void SwitchWidget::SceneGroupAdd(const QString &name)
|
||||
@@ -166,7 +173,8 @@ void SwitchWidget::SceneGroupRename(const QString &oldName,
|
||||
}
|
||||
|
||||
SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
|
||||
bool usePreviousScene, bool addSceneGroup)
|
||||
bool usePreviousScene, bool addSceneGroup,
|
||||
bool addCurrentTransition)
|
||||
{
|
||||
scenes = new QComboBox();
|
||||
transitions = new QComboBox();
|
||||
@@ -194,7 +202,8 @@ SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
|
||||
|
||||
AdvSceneSwitcher::populateSceneSelection(scenes, usePreviousScene,
|
||||
addSceneGroup);
|
||||
AdvSceneSwitcher::populateTransitionSelection(transitions);
|
||||
AdvSceneSwitcher::populateTransitionSelection(transitions,
|
||||
addCurrentTransition);
|
||||
|
||||
switchData = s;
|
||||
showSwitchData();
|
||||
@@ -218,6 +227,10 @@ void SwitchWidget::showSwitchData()
|
||||
|
||||
transitions->setCurrentText(
|
||||
GetWeakSourceName(switchData->transition).c_str());
|
||||
if (switchData->useCurrentTransition) {
|
||||
transitions->setCurrentText(
|
||||
obs_module_text("AdvSceneSwitcher.currentTransition"));
|
||||
}
|
||||
|
||||
if (switchData->usePreviousScene) {
|
||||
scenes->setCurrentText(obs_module_text(
|
||||
@@ -278,4 +291,5 @@ void SwitchWidget::TransitionChanged(const QString &text)
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->transition = GetWeakTransitionByQString(text);
|
||||
switchData->useCurrentTransition = switchData->transition == nullptr;
|
||||
}
|
||||
|
||||
644
src/switch-network.cpp
Normal file
644
src/switch-network.cpp
Normal file
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
Most of this code is based on https://github.com/Palakis/obs-websocket
|
||||
*/
|
||||
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QTime>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
#define PARAM_SERVER_ENABLE "ServerEnabled"
|
||||
#define PARAM_SERVER_PORT "ServerPort"
|
||||
#define PARAM_LOCKTOIPV4 "LockToIPv4"
|
||||
|
||||
#define PARAM_CLIENT_ENABLE "ClientEnabled"
|
||||
#define PARAM_CLIENT_PORT "ClientPort"
|
||||
#define PARAM_ADDRESS "Address"
|
||||
#define PARAM_CLIENT_SENDALL "SendAll"
|
||||
|
||||
#define RECONNECT_DELAY 10
|
||||
|
||||
#define SCENE_ENTRY "scene"
|
||||
#define TRANSITION_ENTRY "transition"
|
||||
|
||||
#if !(__APPLE__)
|
||||
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
using websocketpp::lib::bind;
|
||||
|
||||
WSServer::WSServer()
|
||||
: QObject(nullptr), _connections(), _clMutex(QMutex::Recursive)
|
||||
{
|
||||
_server.get_alog().clear_channels(
|
||||
websocketpp::log::alevel::frame_header |
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
_server.init_asio();
|
||||
#ifndef _WIN32
|
||||
_server.set_reuse_addr(true);
|
||||
#endif
|
||||
|
||||
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
|
||||
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
|
||||
_server.set_message_handler(
|
||||
bind(&WSServer::onMessage, this, ::_1, ::_2));
|
||||
}
|
||||
|
||||
WSServer::~WSServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void WSServer::start(quint16 port, bool lockToIPv4)
|
||||
{
|
||||
if (_server.is_listening() &&
|
||||
(port == _serverPort && _lockToIPv4 == lockToIPv4)) {
|
||||
blog(LOG_INFO,
|
||||
"WSServer::start: server already on this port and protocol mode. no restart needed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_server.is_listening()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
_server.reset();
|
||||
|
||||
_serverPort = port;
|
||||
_lockToIPv4 = lockToIPv4;
|
||||
|
||||
websocketpp::lib::error_code errorCode;
|
||||
if (lockToIPv4) {
|
||||
blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings");
|
||||
_server.listen(websocketpp::lib::asio::ip::tcp::v4(),
|
||||
_serverPort, errorCode);
|
||||
} else {
|
||||
blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings");
|
||||
_server.listen(_serverPort, errorCode);
|
||||
}
|
||||
|
||||
if (errorCode) {
|
||||
std::string errorCodeMessage = errorCode.message();
|
||||
blog(LOG_INFO, "server: listen failed: %s",
|
||||
errorCodeMessage.c_str());
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString errorTitle = tr("AdvSceneSwitcher.windowTitle");
|
||||
QString errorMessage =
|
||||
tr("AdvSceneSwitcher.networkTab.startFailed.message")
|
||||
.arg(_serverPort)
|
||||
.arg(errorCodeMessage.c_str());
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
QMainWindow *mainWindow = reinterpret_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window());
|
||||
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
|
||||
|
||||
return;
|
||||
}
|
||||
switcher->serverStatus = ServerStatus::STARTING;
|
||||
|
||||
_server.start_accept();
|
||||
|
||||
QtConcurrent::run([=]() {
|
||||
blog(LOG_INFO, "WSServer::start: io thread started");
|
||||
_server.run();
|
||||
blog(LOG_INFO, "WSServer::start: io thread exited");
|
||||
});
|
||||
|
||||
switcher->serverStatus = ServerStatus::RUNNING;
|
||||
blog(LOG_INFO,
|
||||
"WSServer::start: server started successfully on port %d",
|
||||
_serverPort);
|
||||
}
|
||||
|
||||
void WSServer::stop()
|
||||
{
|
||||
if (!_server.is_listening()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_server.stop_listening();
|
||||
for (connection_hdl hdl : _connections) {
|
||||
websocketpp::lib::error_code ec;
|
||||
_server.close(hdl, websocketpp::close::status::going_away,
|
||||
"Server stopping", ec);
|
||||
}
|
||||
|
||||
_threadPool.waitForDone();
|
||||
|
||||
while (_connections.size() > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
switcher->serverStatus = ServerStatus::NOT_RUNNING;
|
||||
blog(LOG_INFO, "server stopped successfully");
|
||||
}
|
||||
|
||||
void WSServer::sendMessage(OBSWeakSource scene, OBSWeakSource transition)
|
||||
{
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSData data = obs_data_create();
|
||||
obs_data_set_string(data, SCENE_ENTRY,
|
||||
GetWeakSourceName(scene).c_str());
|
||||
obs_data_set_string(data, TRANSITION_ENTRY,
|
||||
GetWeakSourceName(transition).c_str());
|
||||
std::string message = obs_data_get_json(data);
|
||||
obs_data_release(data);
|
||||
|
||||
for (connection_hdl hdl : _connections) {
|
||||
websocketpp::lib::error_code ec;
|
||||
_server.send(hdl, message, websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
if (ec) {
|
||||
std::string errorCodeMessage = ec.message();
|
||||
blog(LOG_INFO, "server: send failed: %s",
|
||||
errorCodeMessage.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (switcher->verbose) {
|
||||
blog(LOG_INFO, "server sent message:\n%s", message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onOpen(connection_hdl hdl)
|
||||
{
|
||||
QMutexLocker locker(&_clMutex);
|
||||
_connections.insert(hdl);
|
||||
locker.unlock();
|
||||
|
||||
QString clientIp = getRemoteEndpoint(hdl);
|
||||
blog(LOG_INFO, "new client connection from %s",
|
||||
clientIp.toUtf8().constData());
|
||||
}
|
||||
|
||||
std::string processMessage(std::string payload)
|
||||
{
|
||||
auto config = switcher->networkConfig;
|
||||
std::string msgContainer(payload);
|
||||
const char *msg = msgContainer.c_str();
|
||||
|
||||
OBSData data = obs_data_create_from_json(msg);
|
||||
if (!data) {
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||
return "invalid JSON payload";
|
||||
}
|
||||
|
||||
if (!obs_data_has_user_value(data, SCENE_ENTRY) ||
|
||||
!obs_data_has_user_value(data, TRANSITION_ENTRY)) {
|
||||
return "missing request parameters";
|
||||
}
|
||||
|
||||
std::string sceneName = obs_data_get_string(data, SCENE_ENTRY);
|
||||
std::string transitionName =
|
||||
obs_data_get_string(data, TRANSITION_ENTRY);
|
||||
|
||||
obs_data_release(data);
|
||||
|
||||
auto scene = GetWeakSourceByName(sceneName.c_str());
|
||||
if (!scene) {
|
||||
return "ignoring request - unknown scene '" + sceneName + "'";
|
||||
}
|
||||
|
||||
std::string ret = "message ok";
|
||||
|
||||
auto transition = GetWeakTransitionByName(transitionName.c_str());
|
||||
if (switcher->verbose && !transition) {
|
||||
ret += " - ignoring invalid transition: '" + transitionName +
|
||||
"'";
|
||||
}
|
||||
|
||||
switchScene(scene, transition, switcher->tansitionOverrideOverride);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
|
||||
{
|
||||
UNUSED_PARAMETER(hdl);
|
||||
|
||||
auto opcode = message->get_opcode();
|
||||
if (opcode != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
}
|
||||
|
||||
QtConcurrent::run(&_threadPool, [=]() {
|
||||
if (message->get_payload() != "message ok") {
|
||||
blog(LOG_WARNING, "received response: %s",
|
||||
message->get_payload().c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WSServer::onClose(connection_hdl hdl)
|
||||
{
|
||||
QMutexLocker locker(&_clMutex);
|
||||
_connections.erase(hdl);
|
||||
locker.unlock();
|
||||
|
||||
auto conn = _server.get_con_from_hdl(hdl);
|
||||
auto localCloseCode = conn->get_local_close_code();
|
||||
|
||||
if (localCloseCode != websocketpp::close::status::going_away) {
|
||||
QString clientIp = getRemoteEndpoint(hdl);
|
||||
blog(LOG_INFO, "client %s disconnected",
|
||||
clientIp.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
|
||||
{
|
||||
auto conn = _server.get_con_from_hdl(hdl);
|
||||
return QString::fromStdString(conn->get_remote_endpoint());
|
||||
}
|
||||
|
||||
WSClient::WSClient() : QObject(nullptr)
|
||||
{
|
||||
_client.get_alog().clear_channels(
|
||||
websocketpp::log::alevel::frame_header |
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
_client.init_asio();
|
||||
#ifndef _WIN32
|
||||
_client.set_reuse_addr(true);
|
||||
#endif
|
||||
|
||||
_client.set_open_handler(bind(&WSClient::onOpen, this, ::_1));
|
||||
_client.set_fail_handler(bind(&WSClient::onFail, this, ::_1));
|
||||
_client.set_message_handler(
|
||||
bind(&WSClient::onMessage, this, ::_1, ::_2));
|
||||
_client.set_close_handler(bind(&WSClient::onClose, this, ::_1));
|
||||
}
|
||||
|
||||
WSClient::~WSClient()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void WSClient::connectThread()
|
||||
{
|
||||
while (_retry) {
|
||||
_client.reset();
|
||||
switcher->clientStatus = ClientStatus::CONNECTING;
|
||||
// Create a connection to the given URI and queue it for connection once
|
||||
// the event loop starts
|
||||
websocketpp::lib::error_code ec;
|
||||
client::connection_ptr con = _client.get_connection(_uri, ec);
|
||||
if (ec) {
|
||||
_failMsg = ec.message();
|
||||
blog(LOG_INFO, "client: connect failed: %s",
|
||||
_failMsg.c_str());
|
||||
switcher->clientStatus = ClientStatus::FAIL;
|
||||
} else {
|
||||
_client.connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
|
||||
// Start the ASIO io_service run loop
|
||||
blog(LOG_INFO, "WSClient::connect: io thread started");
|
||||
_connected = true;
|
||||
_client.run();
|
||||
_connected = false;
|
||||
blog(LOG_INFO, "WSClient::connect: io thread exited");
|
||||
}
|
||||
|
||||
if (_retry) {
|
||||
std::unique_lock<std::mutex> lck(_waitMtx);
|
||||
blog(LOG_INFO,
|
||||
"trying to reconnect to %s in %d seconds.",
|
||||
_uri.c_str(), RECONNECT_DELAY);
|
||||
_cv.wait_for(lck,
|
||||
std::chrono::seconds(RECONNECT_DELAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WSClient::connect(std::string uri)
|
||||
{
|
||||
disconnect();
|
||||
_uri = uri;
|
||||
_retry = true;
|
||||
|
||||
_thread = std::thread(&WSClient::connectThread, this);
|
||||
|
||||
switcher->clientStatus = ClientStatus::DISCONNECTED;
|
||||
blog(LOG_INFO, "WSClient::connect: exited");
|
||||
}
|
||||
|
||||
void WSClient::disconnect()
|
||||
{
|
||||
_retry = false;
|
||||
websocketpp::lib::error_code ec;
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Client stopping", ec);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> waitLck(_waitMtx);
|
||||
blog(LOG_INFO, "trying to reconnect to %s in %d seconds.",
|
||||
_uri.c_str(), RECONNECT_DELAY);
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
while (_connected) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Client stopping", ec);
|
||||
}
|
||||
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WSClient::onOpen(connection_hdl hdl)
|
||||
{
|
||||
UNUSED_PARAMETER(hdl);
|
||||
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
|
||||
switcher->clientStatus = ClientStatus::CONNECTED;
|
||||
}
|
||||
|
||||
void WSClient::onFail(connection_hdl hdl)
|
||||
{
|
||||
UNUSED_PARAMETER(hdl);
|
||||
blog(LOG_INFO, "connection to %s failed", _uri.c_str());
|
||||
}
|
||||
|
||||
void WSClient::onMessage(connection_hdl hdl, client::message_ptr message)
|
||||
{
|
||||
auto opcode = message->get_opcode();
|
||||
if (opcode != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string payload = message->get_payload();
|
||||
std::string response = processMessage(payload);
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_client.send(hdl, response, websocketpp::frame::opcode::text,
|
||||
errorCode);
|
||||
|
||||
if (errorCode) {
|
||||
std::string errorCodeMessage = errorCode.message();
|
||||
blog(LOG_INFO, "client(response): send failed: %s",
|
||||
errorCodeMessage.c_str());
|
||||
}
|
||||
|
||||
if (switcher->verbose) {
|
||||
blog(LOG_INFO, "client sent message:\n%s", response.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WSClient::onClose(connection_hdl hdl)
|
||||
{
|
||||
UNUSED_PARAMETER(hdl);
|
||||
blog(LOG_INFO, "client-connection to %s closed.", _uri.c_str());
|
||||
switcher->clientStatus = ClientStatus::DISCONNECTED;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
NetworkConfig::NetworkConfig()
|
||||
: ServerEnabled(false),
|
||||
ServerPort(55555),
|
||||
LockToIPv4(false),
|
||||
ClientEnabled(false),
|
||||
Address(""),
|
||||
ClientPort(55555),
|
||||
SendAll(true)
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkConfig::Load(obs_data_t *obj)
|
||||
{
|
||||
SetDefaults(obj);
|
||||
|
||||
ServerEnabled = obs_data_get_bool(obj, PARAM_SERVER_ENABLE);
|
||||
ServerPort = obs_data_get_int(obj, PARAM_SERVER_PORT);
|
||||
LockToIPv4 = obs_data_get_bool(obj, PARAM_LOCKTOIPV4);
|
||||
|
||||
ClientEnabled = obs_data_get_bool(obj, PARAM_CLIENT_ENABLE);
|
||||
Address = obs_data_get_string(obj, PARAM_ADDRESS);
|
||||
ClientPort = obs_data_get_int(obj, PARAM_CLIENT_PORT);
|
||||
SendAll = obs_data_get_bool(obj, PARAM_CLIENT_SENDALL);
|
||||
}
|
||||
|
||||
void NetworkConfig::Save(obs_data_t *obj)
|
||||
{
|
||||
obs_data_set_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
|
||||
obs_data_set_int(obj, PARAM_SERVER_PORT, ServerPort);
|
||||
obs_data_set_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
|
||||
|
||||
obs_data_set_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
|
||||
obs_data_set_string(obj, PARAM_ADDRESS, Address.c_str());
|
||||
obs_data_set_int(obj, PARAM_CLIENT_PORT, ClientPort);
|
||||
obs_data_set_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
|
||||
}
|
||||
|
||||
void NetworkConfig::SetDefaults(obs_data_t *obj)
|
||||
{
|
||||
obs_data_set_default_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
|
||||
obs_data_set_default_int(obj, PARAM_SERVER_PORT, ServerPort);
|
||||
obs_data_set_default_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
|
||||
|
||||
obs_data_set_default_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
|
||||
obs_data_set_default_string(obj, PARAM_ADDRESS, Address.c_str());
|
||||
obs_data_set_default_int(obj, PARAM_CLIENT_PORT, ClientPort);
|
||||
obs_data_set_default_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
|
||||
}
|
||||
|
||||
std::string NetworkConfig::GetClientUri()
|
||||
{
|
||||
return "ws://" + Address + ":" + std::to_string(ClientPort);
|
||||
}
|
||||
|
||||
void SwitcherData::loadNetworkSettings(obs_data_t *obj)
|
||||
{
|
||||
networkConfig.Load(obj);
|
||||
}
|
||||
|
||||
void SwitcherData::saveNetworkSwitches(obs_data_t *obj)
|
||||
{
|
||||
networkConfig.Save(obj);
|
||||
if (!networkConfig.ServerEnabled) {
|
||||
switcher->server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupNetworkTab()
|
||||
{
|
||||
#if __APPLE__
|
||||
ui->clientSettings->setDisabled(true);
|
||||
ui->serverSettings->setDisabled(true);
|
||||
ui->networkTabWarningLabel->setText(
|
||||
obs_module_text("AdvSceneSwitcher.networkTab.DisabledWarning"));
|
||||
#endif
|
||||
|
||||
ui->serverSettings->setChecked(switcher->networkConfig.ServerEnabled);
|
||||
ui->serverPort->setValue(switcher->networkConfig.ServerPort);
|
||||
ui->lockToIPv4->setChecked(switcher->networkConfig.LockToIPv4);
|
||||
|
||||
ui->clientSettings->setChecked(switcher->networkConfig.ClientEnabled);
|
||||
ui->clientHostname->setText(switcher->networkConfig.Address.c_str());
|
||||
ui->clientPort->setValue(switcher->networkConfig.ClientPort);
|
||||
ui->restrictSend->setChecked(!switcher->networkConfig.SendAll);
|
||||
|
||||
QTimer *statusTimer = new QTimer(this);
|
||||
connect(statusTimer, SIGNAL(timeout()), this,
|
||||
SLOT(updateClientStatus()));
|
||||
connect(statusTimer, SIGNAL(timeout()), this,
|
||||
SLOT(updateServerStatus()));
|
||||
statusTimer->start(500);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_serverSettings_toggled(bool on)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.ServerEnabled = on;
|
||||
if (on) {
|
||||
switcher->server.start(switcher->networkConfig.ServerPort,
|
||||
switcher->networkConfig.LockToIPv4);
|
||||
} else {
|
||||
switcher->server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_serverPort_valueChanged(int value)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.ServerPort = value;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_lockToIPv4_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.LockToIPv4 = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_serverRestart_clicked()
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->server.start(switcher->networkConfig.ServerPort,
|
||||
switcher->networkConfig.LockToIPv4);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::updateServerStatus()
|
||||
{
|
||||
switch (switcher->serverStatus) {
|
||||
case ServerStatus::NOT_RUNNING:
|
||||
ui->serverStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.server.status.notRunning"));
|
||||
break;
|
||||
case ServerStatus::STARTING:
|
||||
ui->serverStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.server.status.starting"));
|
||||
break;
|
||||
case ServerStatus::RUNNING:
|
||||
ui->serverStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.server.status.running"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_clientSettings_toggled(bool on)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.ClientEnabled = on;
|
||||
|
||||
if (on) {
|
||||
switcher->client.connect(
|
||||
switcher->networkConfig.GetClientUri());
|
||||
} else {
|
||||
switcher->client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_clientHostname_textChanged(const QString &text)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.Address = text.toUtf8().constData();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_clientPort_valueChanged(int value)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.ClientPort = value;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_restrictSend_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->networkConfig.SendAll = !state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_clientReconnect_clicked()
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->client.connect(switcher->networkConfig.GetClientUri());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::updateClientStatus()
|
||||
{
|
||||
switch (switcher->clientStatus) {
|
||||
case ClientStatus::DISCONNECTED:
|
||||
ui->clientStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.client.status.disconnected"));
|
||||
break;
|
||||
case ClientStatus::CONNECTING:
|
||||
ui->clientStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.client.status.connecting"));
|
||||
break;
|
||||
case ClientStatus::CONNECTED:
|
||||
ui->clientStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.networkTab.client.status.connected"));
|
||||
break;
|
||||
case ClientStatus::FAIL:
|
||||
ui->clientStatus->setText(QString("Error: ") +
|
||||
switcher->client.getFail().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@ void resetPause()
|
||||
IdleData::pause = false;
|
||||
SceneSequenceSwitch::pause = false;
|
||||
AudioSwitch::pause = false;
|
||||
VideoSwitch::pause = false;
|
||||
}
|
||||
|
||||
void setPauseTarget(PauseTarget &target, bool &verbose)
|
||||
@@ -139,6 +140,10 @@ void setPauseTarget(PauseTarget &target, bool &verbose)
|
||||
vblog(LOG_INFO, "pause audio switching");
|
||||
AudioSwitch::pause = true;
|
||||
break;
|
||||
case PauseTarget::Video:
|
||||
vblog(LOG_INFO, "pause video switching");
|
||||
VideoSwitch::pause = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +345,7 @@ void populatePauseTargets(QComboBox *list)
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.sceneSequenceTab.title"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.audioTab.title"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.VideoTab.title"));
|
||||
}
|
||||
|
||||
PauseEntryWidget::PauseEntryWidget(QWidget *parent, PauseEntry *s)
|
||||
|
||||
@@ -55,15 +55,18 @@ void AdvSceneSwitcher::on_priorityDown_clicked()
|
||||
|
||||
bool SwitcherData::prioFuncsValid()
|
||||
{
|
||||
auto it = std::unique(functionNamesByPriority.begin(),
|
||||
functionNamesByPriority.end());
|
||||
bool wasUnique = (it == functionNamesByPriority.end());
|
||||
auto fNBPCopy = functionNamesByPriority;
|
||||
|
||||
std::sort(fNBPCopy.begin(), fNBPCopy.end());
|
||||
auto it = std::unique(fNBPCopy.begin(), fNBPCopy.end());
|
||||
bool wasUnique = (it == fNBPCopy.end());
|
||||
|
||||
if (!wasUnique) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int p : functionNamesByPriority) {
|
||||
if (p < 0 || p > 8) {
|
||||
if (p < 0 || p > 9) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,15 @@ void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.scene == lastRandomScene && randomSwitches.size() != 1) {
|
||||
continue;
|
||||
if (randomSwitches.size() != 1) {
|
||||
if (r.targetType == SwitchTargetType::Scene &&
|
||||
r.scene == lastRandomScene) {
|
||||
continue;
|
||||
} else if (r.targetType ==
|
||||
SwitchTargetType::SceneGroup &&
|
||||
r.group && r.group == lastRandomSceneGroup) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
scene = r.getScene();
|
||||
@@ -61,6 +68,7 @@ void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
|
||||
delay = (int)r.delay * 1000;
|
||||
match = true;
|
||||
lastRandomScene = r.scene;
|
||||
lastRandomSceneGroup = r.group;
|
||||
|
||||
if (verbose) {
|
||||
r.logMatch();
|
||||
@@ -142,7 +150,7 @@ void RandomSwitch::load(obs_data_t *obj)
|
||||
}
|
||||
|
||||
RandomSwitchWidget::RandomSwitchWidget(QWidget *parent, RandomSwitch *s)
|
||||
: SwitchWidget(parent, s, false, false)
|
||||
: SwitchWidget(parent, s, false, true)
|
||||
{
|
||||
delay = new QDoubleSpinBox();
|
||||
|
||||
|
||||
@@ -353,7 +353,8 @@ ScreenRegionWidget::ScreenRegionWidget(QWidget *parent, ScreenRegionSwitch *s)
|
||||
AdvSceneSwitcher::populateSceneSelection(
|
||||
excludeScenes, false, false, true,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.screenRegionTab.excludeScenes.None"));
|
||||
"AdvSceneSwitcher.screenRegionTab.excludeScenes.None"),
|
||||
true);
|
||||
|
||||
if (s) {
|
||||
excludeScenes->setCurrentText(
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
constexpr auto max_extend_text_size = 150;
|
||||
|
||||
bool SceneSequenceSwitch::pause = false;
|
||||
static QMetaObject::Connection addPulse;
|
||||
|
||||
@@ -28,20 +30,10 @@ void AdvSceneSwitcher::on_sceneSequenceRemove_clicked()
|
||||
}
|
||||
|
||||
{
|
||||
// Might be in waiting state of sequence
|
||||
// causing invalid access after wakeup
|
||||
// thus we need to stop the main thread before delete
|
||||
bool wasRunning = !switcher->stop;
|
||||
switcher->Stop();
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int idx = ui->sceneSequenceSwitches->currentRow();
|
||||
auto &switches = switcher->sceneSequenceSwitches;
|
||||
switches.erase(switches.begin() + idx);
|
||||
|
||||
if (wasRunning) {
|
||||
switcher->Start();
|
||||
}
|
||||
}
|
||||
|
||||
delete item;
|
||||
@@ -140,18 +132,8 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
// Might be in waiting state of sequence
|
||||
// causing invalid access after wakeup
|
||||
// thus we need to stop the main thread before delete
|
||||
bool wasRunning = !switcher->stop;
|
||||
switcher->Stop();
|
||||
|
||||
switcher->loadSceneSequenceSwitches(obj);
|
||||
|
||||
if (wasRunning) {
|
||||
switcher->Start();
|
||||
}
|
||||
|
||||
obs_data_release(obj);
|
||||
|
||||
DisplayMessage(obs_module_text(
|
||||
@@ -159,30 +141,42 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
|
||||
close();
|
||||
}
|
||||
|
||||
bool matchInterruptible(SwitcherData *switcher, SceneSequenceSwitch &s)
|
||||
void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
|
||||
{
|
||||
bool durationReached = s.matchCount * (switcher->interval / 1000.0) >=
|
||||
s.delay;
|
||||
QDialog edit;
|
||||
SequenceWidget editWidget(this, sw->getSwitchData(), false, true,
|
||||
false);
|
||||
QHBoxLayout layout;
|
||||
layout.setSizeConstraint(QLayout::SetFixedSize);
|
||||
layout.addWidget(&editWidget);
|
||||
edit.setLayout(&layout);
|
||||
edit.setWindowTitle(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.extendEdit"));
|
||||
edit.exec();
|
||||
|
||||
s.matchCount++;
|
||||
|
||||
if (durationReached) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
sw->UpdateWidgetStatus(true);
|
||||
}
|
||||
|
||||
bool matchUninterruptible(SwitcherData *switcher, SceneSequenceSwitch &s,
|
||||
obs_source_t *currentSource, int &linger)
|
||||
void AdvSceneSwitcher::on_sequenceEdit_clicked()
|
||||
{
|
||||
// scene was already active for the previous cycle so remove this time
|
||||
int dur = s.delay * 1000 - switcher->interval;
|
||||
if (dur > 0) {
|
||||
switcher->waitScene = currentSource;
|
||||
linger = dur;
|
||||
int index = ui->sceneSequenceSwitches->currentRow();
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
SequenceWidget *currentWidget =
|
||||
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
|
||||
ui->sceneSequenceSwitches->item(index));
|
||||
|
||||
OpenSequenceExtendEdit(currentWidget);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked(
|
||||
QListWidgetItem *item)
|
||||
{
|
||||
SequenceWidget *currentWidget =
|
||||
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(item);
|
||||
OpenSequenceExtendEdit(currentWidget);
|
||||
}
|
||||
|
||||
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
|
||||
@@ -192,38 +186,53 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t *currentSource = obs_frontend_get_current_scene();
|
||||
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
|
||||
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
|
||||
obs_weak_source_t *currentScene =
|
||||
obs_source_get_weak_source(currentSceneSource);
|
||||
|
||||
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
|
||||
if (!s.initialized()) {
|
||||
// Continue the active uninterruptible sequence and skip others
|
||||
if (uninterruptibleSceneSequenceActive &&
|
||||
s.activeSequence == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.startScene == ws) {
|
||||
if (!match) {
|
||||
if (s.interruptible) {
|
||||
match = matchInterruptible(switcher, s);
|
||||
} else {
|
||||
match = matchUninterruptible(
|
||||
switcher, s, currentSource,
|
||||
linger);
|
||||
}
|
||||
bool matched = s.checkMatch(currentScene, linger);
|
||||
|
||||
if (match) {
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
if (switcher->verbose) {
|
||||
s.logMatch();
|
||||
}
|
||||
if (!match && matched) {
|
||||
match = matched;
|
||||
|
||||
if (s.activeSequence) {
|
||||
scene = s.activeSequence->getScene();
|
||||
transition = s.activeSequence->transition;
|
||||
} else {
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
if (verbose) {
|
||||
s.logMatch();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.matchCount = 0;
|
||||
|
||||
s.advanceActiveSequence();
|
||||
if (verbose) {
|
||||
s.logAdvanceSequence();
|
||||
}
|
||||
|
||||
// Ignore other switching methods if sequence is not
|
||||
// interruptible and has not reached its end
|
||||
if (s.activeSequence) {
|
||||
uninterruptibleSceneSequenceActive =
|
||||
!s.interruptible;
|
||||
}
|
||||
}
|
||||
}
|
||||
obs_source_release(currentSource);
|
||||
obs_weak_source_release(ws);
|
||||
|
||||
if (!match) {
|
||||
uninterruptibleSceneSequenceActive = false;
|
||||
}
|
||||
|
||||
obs_source_release(currentSceneSource);
|
||||
obs_weak_source_release(currentScene);
|
||||
}
|
||||
|
||||
void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
|
||||
@@ -291,18 +300,32 @@ bool SceneSequenceSwitch::valid()
|
||||
(SceneSwitcherEntry::valid() && WeakSourceValid(startScene));
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::save(obs_data_t *obj)
|
||||
void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
|
||||
{
|
||||
SceneSwitcherEntry::save(obj);
|
||||
|
||||
obs_data_set_int(obj, "startTargetType",
|
||||
static_cast<int>(startTargetType));
|
||||
obs_data_set_string(obj, "startScene",
|
||||
GetWeakSourceName(startScene).c_str());
|
||||
|
||||
obs_data_set_double(obj, "delay", delay);
|
||||
|
||||
obs_data_set_int(obj, "delayMultiplier", delayMultiplier);
|
||||
|
||||
obs_data_set_bool(obj, "interruptible", interruptible);
|
||||
|
||||
if (saveExt) {
|
||||
auto cur = extendedSequence.get();
|
||||
|
||||
obs_data_array_t *extendScenes = obs_data_array_create();
|
||||
while (cur) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
cur->save(array_obj, false);
|
||||
obs_data_array_push_back(extendScenes, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
cur = cur->extendedSequence.get();
|
||||
}
|
||||
obs_data_set_array(obj, "extendScenes", extendScenes);
|
||||
obs_data_array_release(extendScenes);
|
||||
}
|
||||
}
|
||||
|
||||
// To be removed in future version
|
||||
@@ -341,14 +364,15 @@ bool loadOldScequence(obs_data_t *obj, SceneSequenceSwitch *s)
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::load(obs_data_t *obj)
|
||||
void SceneSequenceSwitch::load(obs_data_t *obj, bool saveExt)
|
||||
{
|
||||
if (loadOldScequence(obj, this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SceneSwitcherEntry::load(obj);
|
||||
|
||||
startTargetType = static_cast<SwitchTargetType>(
|
||||
obs_data_get_int(obj, "startTargetType"));
|
||||
const char *scene = obs_data_get_string(obj, "startScene");
|
||||
startScene = GetWeakSourceByName(scene);
|
||||
|
||||
@@ -360,6 +384,220 @@ void SceneSequenceSwitch::load(obs_data_t *obj)
|
||||
delayMultiplier = 1;
|
||||
|
||||
interruptible = obs_data_get_bool(obj, "interruptible");
|
||||
|
||||
if (saveExt) {
|
||||
auto cur = this;
|
||||
|
||||
obs_data_array_t *extendScenes =
|
||||
obs_data_get_array(obj, "extendScenes");
|
||||
size_t count = obs_data_array_count(extendScenes);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj =
|
||||
obs_data_array_item(extendScenes, i);
|
||||
|
||||
cur->extendedSequence =
|
||||
std::make_unique<SceneSequenceSwitch>();
|
||||
|
||||
cur->extendedSequence->load(array_obj, false);
|
||||
|
||||
cur = cur->extendedSequence.get();
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(extendScenes);
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneSequenceSwitch::reduce()
|
||||
{
|
||||
// Reset activeSequence just in case it was one of the deleted entries
|
||||
activeSequence = nullptr;
|
||||
|
||||
if (!extendedSequence) {
|
||||
return true;
|
||||
}
|
||||
if (extendedSequence->reduce()) {
|
||||
extendedSequence.reset(nullptr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneSequenceSwitch *SceneSequenceSwitch::extend()
|
||||
{
|
||||
if (extendedSequence) {
|
||||
return extendedSequence->extend();
|
||||
}
|
||||
extendedSequence = std::make_unique<SceneSequenceSwitch>();
|
||||
extendedSequence->startScene = scene;
|
||||
return extendedSequence.get();
|
||||
}
|
||||
|
||||
bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
|
||||
SceneSequenceSwitch *root)
|
||||
{
|
||||
if (!initialized()) {
|
||||
if (root) {
|
||||
root->activeSequence = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
|
||||
if (activeSequence) {
|
||||
return activeSequence->checkMatch(currentScene, linger, this);
|
||||
}
|
||||
|
||||
if (startScene == currentScene) {
|
||||
if (interruptible) {
|
||||
match = checkDurationMatchInterruptible();
|
||||
} else {
|
||||
match = true;
|
||||
prepareUninterruptibleMatch(currentScene, linger);
|
||||
}
|
||||
} else {
|
||||
matchCount = 0;
|
||||
|
||||
if (root) {
|
||||
root->activeSequence = nullptr;
|
||||
logSequenceCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool SceneSequenceSwitch::checkDurationMatchInterruptible()
|
||||
{
|
||||
bool durationReached = matchCount * (switcher->interval / 1000.0) >=
|
||||
delay;
|
||||
matchCount++;
|
||||
if (durationReached) {
|
||||
matchCount = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::prepareUninterruptibleMatch(
|
||||
OBSWeakSource currentScene, int &linger)
|
||||
{
|
||||
int dur = delay * 1000;
|
||||
if (dur > 0) {
|
||||
switcher->waitScene = obs_weak_source_get_source(currentScene);
|
||||
obs_source_release(switcher->waitScene);
|
||||
linger = dur;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::advanceActiveSequence()
|
||||
{
|
||||
// Set start Scene
|
||||
OBSWeakSource currentSceneGroupScene = nullptr;
|
||||
if (targetType == SwitchTargetType::SceneGroup && group) {
|
||||
currentSceneGroupScene = group->getCurrentScene();
|
||||
}
|
||||
|
||||
if (activeSequence) {
|
||||
activeSequence = activeSequence->extendedSequence.get();
|
||||
} else {
|
||||
activeSequence = extendedSequence.get();
|
||||
}
|
||||
|
||||
if (activeSequence) {
|
||||
if (activeSequence->startTargetType ==
|
||||
SwitchTargetType::SceneGroup) {
|
||||
activeSequence->startScene = currentSceneGroupScene;
|
||||
}
|
||||
if (activeSequence->targetType == SwitchTargetType::Scene &&
|
||||
!activeSequence->scene) {
|
||||
blog(LOG_WARNING,
|
||||
"cannot advance sequence - null scene set");
|
||||
activeSequence = nullptr;
|
||||
return;
|
||||
}
|
||||
if (activeSequence->targetType ==
|
||||
SwitchTargetType::SceneGroup &&
|
||||
activeSequence->group &&
|
||||
activeSequence->group->scenes.empty()) {
|
||||
blog(LOG_WARNING,
|
||||
"cannot advance sequence - no scenes specified in '%s'",
|
||||
activeSequence->group->name.c_str());
|
||||
activeSequence = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reinit old matchCount value in case it was previously set
|
||||
activeSequence->matchCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::logAdvanceSequence()
|
||||
{
|
||||
if (activeSequence) {
|
||||
std::string targetName =
|
||||
GetWeakSourceName(activeSequence->scene);
|
||||
|
||||
if (activeSequence->targetType ==
|
||||
SwitchTargetType::SceneGroup &&
|
||||
activeSequence->group) {
|
||||
targetName = activeSequence->group->name;
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "continuing sequence with '%s' -> '%s'",
|
||||
GetWeakSourceName(activeSequence->startScene).c_str(),
|
||||
targetName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneSequenceSwitch::logSequenceCanceled()
|
||||
{
|
||||
blog(LOG_INFO, "unexpected scene change - cancel sequence");
|
||||
}
|
||||
|
||||
QString delayMultiplierToString(int delayMultiplier)
|
||||
{
|
||||
switch (delayMultiplier) {
|
||||
case 1:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.secends");
|
||||
case 60:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||
case 60 * 60:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
|
||||
{
|
||||
if (!s) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QString ext = "";
|
||||
|
||||
ext = QString::number(s->delay / s->delayMultiplier) + " ";
|
||||
ext += delayMultiplierToString(s->delayMultiplier);
|
||||
|
||||
QString sceneName = GetWeakSourceName(s->scene).c_str();
|
||||
if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
|
||||
sceneName = QString::fromStdString(s->group->name);
|
||||
}
|
||||
if (sceneName.isEmpty()) {
|
||||
sceneName = obs_module_text("AdvSceneSwitcher.selectScene");
|
||||
}
|
||||
ext += " -> [" + sceneName + "]";
|
||||
|
||||
if (ext.length() + curLen > max_extend_text_size) {
|
||||
return "...";
|
||||
}
|
||||
|
||||
if (s->extendedSequence.get()) {
|
||||
return ext +=
|
||||
" | " + makeExtendText(s->extendedSequence.get(),
|
||||
curLen + ext.length());
|
||||
} else {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
void populateDelayUnits(QComboBox *list)
|
||||
@@ -369,14 +607,36 @@ void populateDelayUnits(QComboBox *list)
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
||||
}
|
||||
|
||||
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
|
||||
: SwitchWidget(parent, s, true, true)
|
||||
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
bool extendSequence, bool editExtendMode,
|
||||
bool showExtendText)
|
||||
: SwitchWidget(parent, s, !extendSequence, true)
|
||||
{
|
||||
this->setParent(parent);
|
||||
|
||||
delay = new QDoubleSpinBox();
|
||||
delayUnits = new QComboBox();
|
||||
startScenes = new QComboBox();
|
||||
interruptible = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.interruptible"));
|
||||
extendText = new QLabel();
|
||||
extend = new QPushButton();
|
||||
reduce = new QPushButton();
|
||||
|
||||
extend->setProperty("themeID",
|
||||
QVariant(QStringLiteral("addIconSmall")));
|
||||
reduce->setProperty("themeID",
|
||||
QVariant(QStringLiteral("removeIconSmall")));
|
||||
|
||||
extend->setMaximumSize(22, 22);
|
||||
reduce->setMaximumSize(22, 22);
|
||||
|
||||
// We need to extend the generic SwitchWidget::SceneChanged()
|
||||
// with our own SequenceWidget::SceneChanged()
|
||||
// so the old singal / slot needs to be disconnected
|
||||
scenes->disconnect();
|
||||
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SceneChanged(const QString &)));
|
||||
|
||||
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(DelayChanged(double)));
|
||||
@@ -388,48 +648,80 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
|
||||
QWidget::connect(interruptible, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(InterruptibleChanged(int)));
|
||||
|
||||
QWidget::connect(extend, SIGNAL(clicked()), this,
|
||||
SLOT(ExtendClicked()));
|
||||
QWidget::connect(reduce, SIGNAL(clicked()), this,
|
||||
SLOT(ReduceClicked()));
|
||||
|
||||
delay->setMaximum(99999.000000);
|
||||
AdvSceneSwitcher::populateSceneSelection(startScenes, false);
|
||||
populateDelayUnits(delayUnits);
|
||||
interruptible->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.interruptibleHint"));
|
||||
|
||||
if (s) {
|
||||
switch (s->delayMultiplier) {
|
||||
case 1:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
break;
|
||||
case 60:
|
||||
delayUnits->setCurrentIndex(1);
|
||||
break;
|
||||
case 3600:
|
||||
delayUnits->setCurrentIndex(2);
|
||||
break;
|
||||
default:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
// The extended sequence widgets never exist on their own and are always
|
||||
// place inside a non-extend sequence widget
|
||||
if (extendSequence) {
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{delay}}", delay},
|
||||
{"{{delayUnits}}", delayUnits},
|
||||
{"{{transitions}}", transitions}};
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.extendEntry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
} else {
|
||||
QHBoxLayout *startSequence = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{startScenes}}", startScenes},
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{delay}}", delay},
|
||||
{"{{delayUnits}}", delayUnits},
|
||||
{"{{transitions}}", transitions},
|
||||
{"{{interruptible}}", interruptible}};
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.entry"),
|
||||
startSequence, widgetPlaceholders);
|
||||
|
||||
// The exetend widgets placed here
|
||||
extendSequenceLayout = new QVBoxLayout;
|
||||
if (s) {
|
||||
if (!editExtendMode) {
|
||||
extendText->setText(makeExtendText(
|
||||
s->extendedSequence.get()));
|
||||
} else {
|
||||
auto cur = s->extendedSequence.get();
|
||||
while (cur != nullptr) {
|
||||
extendSequenceLayout->addWidget(
|
||||
new SequenceWidget(parent, cur,
|
||||
true, true));
|
||||
cur = cur->extendedSequence.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
startScenes->setCurrentText(
|
||||
GetWeakSourceName(s->startScene).c_str());
|
||||
interruptible->setChecked(s->interruptible);
|
||||
|
||||
QHBoxLayout *extendSequenceControlsLayout = new QHBoxLayout;
|
||||
if (editExtendMode) {
|
||||
extendSequenceControlsLayout->addWidget(extend);
|
||||
extendSequenceControlsLayout->addWidget(reduce);
|
||||
}
|
||||
extendSequenceControlsLayout->addStretch();
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(startSequence);
|
||||
mainLayout->addLayout(extendSequenceLayout);
|
||||
mainLayout->addWidget(extendText);
|
||||
mainLayout->addLayout(extendSequenceControlsLayout);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{startScenes}}", startScenes},
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{delay}}", delay},
|
||||
{"{{delayUnits}}", delayUnits},
|
||||
{"{{transitions}}", transitions},
|
||||
{"{{interruptible}}", interruptible}};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.sceneSequenceTab.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
switchData = s;
|
||||
UpdateWidgetStatus(showExtendText);
|
||||
|
||||
loading = false;
|
||||
|
||||
UpdateDelay();
|
||||
}
|
||||
|
||||
SceneSequenceSwitch *SequenceWidget::getSwitchData()
|
||||
@@ -493,6 +785,32 @@ void SequenceWidget::DelayUnitsChanged(int idx)
|
||||
UpdateDelay();
|
||||
}
|
||||
|
||||
void SequenceWidget::setExtendedSequenceStartScene()
|
||||
{
|
||||
switchData->extendedSequence->startScene = switchData->scene;
|
||||
switchData->extendedSequence->startTargetType = SwitchTargetType::Scene;
|
||||
|
||||
if (switchData->targetType == SwitchTargetType::SceneGroup) {
|
||||
switchData->extendedSequence->startScene = nullptr;
|
||||
switchData->extendedSequence->startTargetType =
|
||||
SwitchTargetType::SceneGroup;
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceWidget::SceneChanged(const QString &text)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchWidget::SceneChanged(text);
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
if (switchData->extendedSequence) {
|
||||
setExtendedSequenceStartScene();
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceWidget::StartSceneChanged(const QString &text)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
@@ -511,4 +829,69 @@ void SequenceWidget::InterruptibleChanged(int state)
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->interruptible = state;
|
||||
|
||||
auto cur = switchData->extendedSequence.get();
|
||||
while (cur != nullptr) {
|
||||
cur->interruptible = state;
|
||||
cur = cur->extendedSequence.get();
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceWidget::UpdateWidgetStatus(bool showExtendText)
|
||||
{
|
||||
if (showExtendText) {
|
||||
extendText->setText(
|
||||
makeExtendText(switchData->extendedSequence.get()));
|
||||
}
|
||||
|
||||
switch (switchData->delayMultiplier) {
|
||||
case 1:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
break;
|
||||
case 60:
|
||||
delayUnits->setCurrentIndex(1);
|
||||
break;
|
||||
case 3600:
|
||||
delayUnits->setCurrentIndex(2);
|
||||
break;
|
||||
default:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
}
|
||||
UpdateDelay();
|
||||
|
||||
startScenes->setCurrentText(
|
||||
GetWeakSourceName(switchData->startScene).c_str());
|
||||
interruptible->setChecked(switchData->interruptible);
|
||||
|
||||
SwitchWidget::showSwitchData();
|
||||
}
|
||||
|
||||
void SequenceWidget::ExtendClicked()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto es = switchData->extend();
|
||||
|
||||
SequenceWidget *ew = new SequenceWidget(this->parentWidget(), es, true);
|
||||
extendSequenceLayout->addWidget(ew);
|
||||
}
|
||||
|
||||
void SequenceWidget::ReduceClicked()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->reduce();
|
||||
|
||||
int count = extendSequenceLayout->count();
|
||||
auto item = extendSequenceLayout->takeAt(count - 1);
|
||||
if (item) {
|
||||
item->widget()->setVisible(false);
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ void TimeSwitch::load(obs_data_t *obj)
|
||||
time = QTime::fromString(obs_data_get_string(obj, "time"));
|
||||
}
|
||||
|
||||
inline void populateTriggers(QComboBox *list)
|
||||
static inline void populateTriggers(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.anyDay"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.mondays"));
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#include <thread>
|
||||
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
constexpr auto default_def_transition_dealy = 50;
|
||||
bool DefaultSceneTransition::pause = false;
|
||||
unsigned int DefaultSceneTransition::delay = default_def_transition_dealy;
|
||||
|
||||
void AdvSceneSwitcher::on_transitionsAdd_clicked()
|
||||
{
|
||||
@@ -148,42 +152,28 @@ void AdvSceneSwitcher::on_defaultTransitionsDown_clicked()
|
||||
switcher->defaultSceneTransitions[index + 1]);
|
||||
}
|
||||
|
||||
void SwitcherData::checkDefaultSceneTransitions(bool &match,
|
||||
OBSWeakSource &transition)
|
||||
void SwitcherData::checkDefaultSceneTransitions()
|
||||
{
|
||||
if (checkedDefTransition || DefaultSceneTransition::pause) {
|
||||
if (DefaultSceneTransition::pause || stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t *currentSource = obs_frontend_get_current_scene();
|
||||
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
|
||||
|
||||
for (DefaultSceneTransition &s : defaultSceneTransitions) {
|
||||
if (s.scene == ws) {
|
||||
if (!s.initialized()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match = true;
|
||||
transition = s.transition;
|
||||
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
|
||||
obs_weak_source_t *currentScene =
|
||||
obs_source_get_weak_source(currentSceneSource);
|
||||
|
||||
for (auto &t : defaultSceneTransitions) {
|
||||
if (t.checkMatch(currentScene)) {
|
||||
if (verbose) {
|
||||
s.logMatch();
|
||||
t.logMatch();
|
||||
}
|
||||
t.setTransition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
obs_source_release(currentSource);
|
||||
obs_weak_source_release(ws);
|
||||
|
||||
checkedDefTransition = true;
|
||||
}
|
||||
|
||||
void SwitcherData::setCurrentDefTransition(OBSWeakSource &transition)
|
||||
{
|
||||
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
|
||||
obs_frontend_set_current_transition(transitionSource);
|
||||
obs_source_release(transitionSource);
|
||||
obs_weak_source_release(currentScene);
|
||||
obs_source_release(currentSceneSource);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
|
||||
@@ -200,6 +190,16 @@ void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::defTransitionDelayValueChanged(int value)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
DefaultSceneTransition::delay = value;
|
||||
}
|
||||
|
||||
obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
|
||||
obs_weak_source_t *scene2)
|
||||
{
|
||||
@@ -334,6 +334,10 @@ void SwitcherData::saveSceneTransitions(obs_data_t *obj)
|
||||
|
||||
obs_data_set_bool(obj, "tansitionOverrideOverride",
|
||||
switcher->tansitionOverrideOverride);
|
||||
obs_data_set_default_int(obj, "defTransitionDelay",
|
||||
default_def_transition_dealy);
|
||||
obs_data_set_int(obj, "defTransitionDelay",
|
||||
DefaultSceneTransition::delay);
|
||||
}
|
||||
|
||||
void SwitcherData::loadSceneTransitions(obs_data_t *obj)
|
||||
@@ -386,6 +390,8 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
|
||||
|
||||
switcher->tansitionOverrideOverride =
|
||||
obs_data_get_bool(obj, "tansitionOverrideOverride");
|
||||
DefaultSceneTransition::delay =
|
||||
obs_data_get_int(obj, "defTransitionDelay");
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupTransitionsTab()
|
||||
@@ -424,6 +430,24 @@ void AdvSceneSwitcher::setupTransitionsTab()
|
||||
|
||||
ui->transitionOverridecheckBox->setChecked(
|
||||
switcher->tansitionOverrideOverride);
|
||||
|
||||
QSpinBox *defTransitionDelay = new QSpinBox();
|
||||
defTransitionDelay->setSuffix("ms");
|
||||
defTransitionDelay->setMinimum(50);
|
||||
defTransitionDelay->setMaximum(10000);
|
||||
defTransitionDelay->setValue(DefaultSceneTransition::delay);
|
||||
defTransitionDelay->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.transitionTab.defaultTransition.delay.help"));
|
||||
|
||||
QWidget::connect(defTransitionDelay, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(defTransitionDelayValueChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{defTransitionDelay}}", defTransitionDelay}};
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.transitionTab.defaultTransition.delay"),
|
||||
ui->defTransitionDelayLayout, widgetPlaceholders);
|
||||
}
|
||||
|
||||
bool SceneTransition::initialized()
|
||||
@@ -439,7 +463,7 @@ bool SceneTransition::valid()
|
||||
|
||||
TransitionSwitchWidget::TransitionSwitchWidget(QWidget *parent,
|
||||
SceneTransition *s)
|
||||
: SwitchWidget(parent, s, false)
|
||||
: SwitchWidget(parent, s, false, false, false)
|
||||
{
|
||||
scenes2 = new QComboBox();
|
||||
|
||||
@@ -498,7 +522,7 @@ void TransitionSwitchWidget::Scene2Changed(const QString &text)
|
||||
|
||||
DefTransitionSwitchWidget::DefTransitionSwitchWidget(QWidget *parent,
|
||||
DefaultSceneTransition *s)
|
||||
: SwitchWidget(parent, s, false)
|
||||
: SwitchWidget(parent, s, false, false, false)
|
||||
{
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
@@ -533,3 +557,37 @@ void DefTransitionSwitchWidget::swapSwitchData(DefTransitionSwitchWidget *s1,
|
||||
s1->setSwitchData(s2->getSwitchData());
|
||||
s2->setSwitchData(t);
|
||||
}
|
||||
|
||||
bool DefaultSceneTransition::checkMatch(OBSWeakSource currentScene)
|
||||
{
|
||||
return scene == currentScene;
|
||||
}
|
||||
|
||||
void setTransitionDelayed(OBSWeakSource transition, unsigned int delay)
|
||||
{
|
||||
// A hardcoded delay of 50 ms before switching transition type is
|
||||
// necessary due to OBS_FRONTEND_EVENT_SCENE_CHANGED seemingly firing a
|
||||
// bit too early and thus leading to canceled transitions.
|
||||
//
|
||||
// The same is to be the case for OBS_FRONTEND_EVENT_TRANSITION_STOPPED.
|
||||
//
|
||||
// 50 ms was chosen as a default value as it seems to avoid the problem
|
||||
// mentioned above and becuase that is the minimum value which can be
|
||||
// chosen for the scene switcher's check interval.
|
||||
// Thus it can be made sure that the delayed setting of the transition
|
||||
// does not interfere with any new scene changes triggered by the scene
|
||||
// switcher
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
|
||||
|
||||
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
|
||||
obs_frontend_set_current_transition(transitionSource);
|
||||
obs_source_release(transitionSource);
|
||||
}
|
||||
|
||||
void DefaultSceneTransition::setTransition()
|
||||
{
|
||||
std::thread t;
|
||||
t = std::thread(setTransitionDelayed, transition, delay);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
698
src/switch-video.cpp
Normal file
698
src/switch-video.cpp
Normal file
@@ -0,0 +1,698 @@
|
||||
#include <QFileDialog>
|
||||
#include <QBuffer>
|
||||
#include <QToolTip>
|
||||
#include <thread>
|
||||
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
bool VideoSwitch::pause = false;
|
||||
static QMetaObject::Connection addPulse;
|
||||
|
||||
static void ScreenshotTick(void *param, float);
|
||||
|
||||
AdvSSScreenshotObj::AdvSSScreenshotObj(obs_source_t *source)
|
||||
: weakSource(OBSGetWeakRef(source))
|
||||
{
|
||||
obs_add_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
|
||||
AdvSSScreenshotObj::~AdvSSScreenshotObj()
|
||||
{
|
||||
obs_enter_graphics();
|
||||
gs_stagesurface_destroy(stagesurf);
|
||||
gs_texrender_destroy(texrender);
|
||||
obs_leave_graphics();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Screenshot()
|
||||
{
|
||||
OBSSource source = OBSGetStrongRef(weakSource);
|
||||
|
||||
if (source) {
|
||||
cx = obs_source_get_base_width(source);
|
||||
cy = obs_source_get_base_height(source);
|
||||
} else {
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
cx = ovi.base_width;
|
||||
cy = ovi.base_height;
|
||||
}
|
||||
|
||||
if (!cx || !cy) {
|
||||
blog(LOG_WARNING, "Cannot screenshot, invalid target size");
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
|
||||
|
||||
gs_texrender_reset(texrender);
|
||||
if (gs_texrender_begin(texrender, cx, cy)) {
|
||||
vec4 zero;
|
||||
vec4_zero(&zero);
|
||||
|
||||
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
|
||||
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
|
||||
|
||||
gs_blend_state_push();
|
||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||
|
||||
if (source) {
|
||||
obs_source_inc_showing(source);
|
||||
obs_source_video_render(source);
|
||||
obs_source_dec_showing(source);
|
||||
} else {
|
||||
obs_render_main_texture();
|
||||
}
|
||||
|
||||
gs_blend_state_pop();
|
||||
gs_texrender_end(texrender);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Download()
|
||||
{
|
||||
gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender));
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Copy()
|
||||
{
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
|
||||
image = QImage(cx, cy, QImage::Format::Format_RGBX8888);
|
||||
|
||||
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
|
||||
int linesize = image.bytesPerLine();
|
||||
for (int y = 0; y < (int)cy; y++)
|
||||
memcpy(image.scanLine(y),
|
||||
videoData + (y * videoLinesize), linesize);
|
||||
|
||||
gs_stagesurface_unmap(stagesurf);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::MarkDone()
|
||||
{
|
||||
time = std::chrono::high_resolution_clock::now();
|
||||
done = true;
|
||||
}
|
||||
|
||||
#define STAGE_SCREENSHOT 0
|
||||
#define STAGE_DOWNLOAD 1
|
||||
#define STAGE_COPY_AND_SAVE 2
|
||||
#define STAGE_FINISH 3
|
||||
|
||||
static void ScreenshotTick(void *param, float)
|
||||
{
|
||||
AdvSSScreenshotObj *data =
|
||||
reinterpret_cast<AdvSSScreenshotObj *>(param);
|
||||
|
||||
if (data->stage == STAGE_FINISH) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_enter_graphics();
|
||||
|
||||
switch (data->stage) {
|
||||
case STAGE_SCREENSHOT:
|
||||
data->Screenshot();
|
||||
break;
|
||||
case STAGE_DOWNLOAD:
|
||||
data->Download();
|
||||
break;
|
||||
case STAGE_COPY_AND_SAVE:
|
||||
data->Copy();
|
||||
data->MarkDone();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, data);
|
||||
break;
|
||||
}
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
data->stage++;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoAdd_clicked()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->videoSwitches.emplace_back();
|
||||
|
||||
VideoSwitchWidget *sw =
|
||||
new VideoSwitchWidget(this, &switcher->videoSwitches.back());
|
||||
|
||||
listAddClicked(ui->videoSwitches, sw, ui->videoAdd, &addPulse);
|
||||
|
||||
ui->videoHelp->setVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoRemove_clicked()
|
||||
{
|
||||
QListWidgetItem *item = ui->videoSwitches->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int idx = ui->videoSwitches->currentRow();
|
||||
auto &switches = switcher->videoSwitches;
|
||||
switches.erase(switches.begin() + idx);
|
||||
}
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoUp_clicked()
|
||||
{
|
||||
int index = ui->videoSwitches->currentRow();
|
||||
if (!listMoveUp(ui->videoSwitches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *s1 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index));
|
||||
VideoSwitchWidget *s2 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index - 1));
|
||||
VideoSwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
std::swap(switcher->videoSwitches[index],
|
||||
switcher->videoSwitches[index - 1]);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoDown_clicked()
|
||||
{
|
||||
int index = ui->videoSwitches->currentRow();
|
||||
|
||||
if (!listMoveDown(ui->videoSwitches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *s1 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index));
|
||||
VideoSwitchWidget *s2 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index + 1));
|
||||
VideoSwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
std::swap(switcher->videoSwitches[index],
|
||||
switcher->videoSwitches[index + 1]);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_getScreenshot_clicked()
|
||||
{
|
||||
QListWidgetItem *item = ui->videoSwitches->currentItem();
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *sw =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(item);
|
||||
auto s = sw->getSwitchData();
|
||||
if (!s || !s->videoSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto source = obs_weak_source_get_source(s->videoSource);
|
||||
auto screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
obs_source_release(source);
|
||||
|
||||
QString filePath = QFileDialog::getSaveFileName(this);
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// During selection of the save path enough time should usually have
|
||||
// passed already
|
||||
// Add this just in case ...
|
||||
if (!screenshotData->done) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
if (!screenshotData->done) {
|
||||
DisplayMessage("Failed to get screenshot of source!");
|
||||
return;
|
||||
}
|
||||
|
||||
screenshotData->image.save(file.fileName());
|
||||
sw->SetFilePath(file.fileName());
|
||||
}
|
||||
|
||||
void SwitcherData::checkVideoSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
if (VideoSwitch::pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &s : videoSwitches) {
|
||||
bool matched = s.checkMatch();
|
||||
if (!match && matched) {
|
||||
match = true;
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
if (verbose) {
|
||||
s.logMatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::saveVideoSwitches(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *videoArray = obs_data_array_create();
|
||||
for (VideoSwitch &s : videoSwitches) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
s.save(array_obj);
|
||||
obs_data_array_push_back(videoArray, array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, "videoSwitches", videoArray);
|
||||
obs_data_array_release(videoArray);
|
||||
}
|
||||
|
||||
void SwitcherData::loadVideoSwitches(obs_data_t *obj)
|
||||
{
|
||||
switcher->videoSwitches.clear();
|
||||
|
||||
obs_data_array_t *videoArray = obs_data_get_array(obj, "videoSwitches");
|
||||
size_t count = obs_data_array_count(videoArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(videoArray, i);
|
||||
|
||||
switcher->videoSwitches.emplace_back();
|
||||
videoSwitches.back().load(array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(videoArray);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupVideoTab()
|
||||
{
|
||||
for (auto &s : switcher->videoSwitches) {
|
||||
QListWidgetItem *item;
|
||||
item = new QListWidgetItem(ui->videoSwitches);
|
||||
ui->videoSwitches->addItem(item);
|
||||
VideoSwitchWidget *sw = new VideoSwitchWidget(this, &s);
|
||||
item->setSizeHint(sw->minimumSizeHint());
|
||||
ui->videoSwitches->setItemWidget(item, sw);
|
||||
}
|
||||
|
||||
if (switcher->videoSwitches.size() == 0) {
|
||||
addPulse = PulseWidget(ui->videoAdd, QColor(Qt::green));
|
||||
ui->videoHelp->setVisible(true);
|
||||
} else {
|
||||
ui->videoHelp->setVisible(false);
|
||||
}
|
||||
|
||||
ui->getScreenshot->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.getScreenshotHelp"));
|
||||
}
|
||||
|
||||
bool VideoSwitch::initialized()
|
||||
{
|
||||
return SceneSwitcherEntry::initialized() && videoSource;
|
||||
}
|
||||
|
||||
bool VideoSwitch::valid()
|
||||
{
|
||||
return !initialized() ||
|
||||
(SceneSwitcherEntry::valid() && WeakSourceValid(videoSource));
|
||||
}
|
||||
|
||||
void VideoSwitch::save(obs_data_t *obj)
|
||||
{
|
||||
SceneSwitcherEntry::save(obj);
|
||||
|
||||
obs_data_set_string(obj, "videoSource",
|
||||
GetWeakSourceName(videoSource).c_str());
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(condition));
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
obs_data_set_string(obj, "filePath", file.c_str());
|
||||
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
|
||||
}
|
||||
|
||||
void VideoSwitch::load(obs_data_t *obj)
|
||||
{
|
||||
SceneSwitcherEntry::load(obj);
|
||||
|
||||
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
|
||||
videoSource = GetWeakSourceByName(videoSourceName);
|
||||
condition = static_cast<videoSwitchType>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
duration = obs_data_get_double(obj, "duration");
|
||||
file = obs_data_get_string(obj, "filePath");
|
||||
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
|
||||
|
||||
if (condition != videoSwitchType::HAS_NOT_CHANGED) {
|
||||
(void)loadImageFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitch::getScreenshot()
|
||||
{
|
||||
auto source = obs_weak_source_get_source(videoSource);
|
||||
screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
bool VideoSwitch::loadImageFromFile()
|
||||
{
|
||||
if (!matchImage.load(QString::fromStdString(file))) {
|
||||
blog(LOG_WARNING, "Cannot load image data from file '%s'",
|
||||
file.c_str());
|
||||
return false;
|
||||
}
|
||||
matchImage =
|
||||
matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoSwitch::checkMatch()
|
||||
{
|
||||
if (ignoreInactiveSource) {
|
||||
obs_source_t *vs = obs_weak_source_get_source(videoSource);
|
||||
bool videoActive = obs_source_active(vs);
|
||||
obs_source_release(vs);
|
||||
|
||||
if (!videoActive) {
|
||||
screenshotData.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
|
||||
if (screenshotData) {
|
||||
if (screenshotData->done) {
|
||||
bool conditionMatch = false;
|
||||
|
||||
switch (condition) {
|
||||
case videoSwitchType::MATCH:
|
||||
conditionMatch = screenshotData->image ==
|
||||
matchImage;
|
||||
break;
|
||||
case videoSwitchType::DIFFER:
|
||||
conditionMatch = screenshotData->image !=
|
||||
matchImage;
|
||||
break;
|
||||
case videoSwitchType::HAS_NOT_CHANGED:
|
||||
conditionMatch = screenshotData->image ==
|
||||
matchImage;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (conditionMatch) {
|
||||
currentMatchDuration +=
|
||||
std::chrono::duration_cast<
|
||||
std::chrono::milliseconds>(
|
||||
screenshotData->time -
|
||||
previousTime);
|
||||
} else {
|
||||
currentMatchDuration = {};
|
||||
}
|
||||
|
||||
bool durationMatch = currentMatchDuration.count() >=
|
||||
duration * 1000;
|
||||
|
||||
if (conditionMatch && durationMatch) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
matchImage = std::move(screenshotData->image);
|
||||
}
|
||||
previousTime = std::move(screenshotData->time);
|
||||
|
||||
screenshotData.reset(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
getScreenshot();
|
||||
return match;
|
||||
}
|
||||
|
||||
void swap(VideoSwitch &first, VideoSwitch &second)
|
||||
{
|
||||
std::swap(first.targetType, second.targetType);
|
||||
std::swap(first.group, second.group);
|
||||
std::swap(first.scene, second.scene);
|
||||
std::swap(first.transition, second.transition);
|
||||
std::swap(first.usePreviousScene, second.usePreviousScene);
|
||||
std::swap(first.videoSource, second.videoSource);
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.condition.match"));
|
||||
list->setItemData(
|
||||
0,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.condition.match.tooltip"),
|
||||
Qt::ToolTipRole);
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.condition.differ"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.condition.hasNotChanged"));
|
||||
}
|
||||
|
||||
VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s)
|
||||
: SwitchWidget(parent, s, true, true)
|
||||
{
|
||||
videoSources = new QComboBox();
|
||||
condition = new QComboBox();
|
||||
duration = new QDoubleSpinBox();
|
||||
filePath = new QLineEdit();
|
||||
browseButton =
|
||||
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
|
||||
ignoreInactiveSource = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.ignoreInactiveSource"));
|
||||
|
||||
filePath->setFixedWidth(100);
|
||||
|
||||
browseButton->setStyleSheet("border:1px solid gray;");
|
||||
|
||||
duration->setMinimum(0.0);
|
||||
duration->setMaximum(99.000000);
|
||||
duration->setSuffix("s");
|
||||
|
||||
QWidget::connect(videoSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SourceChanged(const QString &)));
|
||||
QWidget::connect(condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(filePath, SIGNAL(editingFinished()), this,
|
||||
SLOT(FilePathChanged()));
|
||||
QWidget::connect(browseButton, SIGNAL(clicked()), this,
|
||||
SLOT(BrowseButtonClicked()));
|
||||
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreInactiveChanged(int)));
|
||||
|
||||
// TODO:
|
||||
// Figure out why scene do not work for "match exactly".
|
||||
// Until then do not allow selecting scenes
|
||||
AdvSceneSwitcher::populateVideoSelection(videoSources, false);
|
||||
populateConditionSelection(condition);
|
||||
|
||||
if (s) {
|
||||
videoSources->setCurrentText(
|
||||
GetWeakSourceName(s->videoSource).c_str());
|
||||
condition->setCurrentIndex(static_cast<int>(s->condition));
|
||||
duration->setValue(s->duration);
|
||||
filePath->setText(QString::fromStdString(s->file));
|
||||
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
|
||||
|
||||
if (s->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
filePath->hide();
|
||||
browseButton->hide();
|
||||
}
|
||||
}
|
||||
|
||||
QHBoxLayout *switchLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{videoSources}}", videoSources},
|
||||
{"{{condition}}", condition},
|
||||
{"{{duration}}", duration},
|
||||
{"{{filePath}}", filePath},
|
||||
{"{{browseButton}}", browseButton},
|
||||
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{transitions}}", transitions}};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.videoTab.entry"),
|
||||
switchLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
|
||||
mainLayout->addLayout(switchLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
switchData = s;
|
||||
UpdatePreviewTooltip();
|
||||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
VideoSwitch *VideoSwitchWidget::getSwitchData()
|
||||
{
|
||||
return switchData;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::setSwitchData(VideoSwitch *s)
|
||||
{
|
||||
switchData = s;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::swapSwitchData(VideoSwitchWidget *s1,
|
||||
VideoSwitchWidget *s2)
|
||||
{
|
||||
SwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
VideoSwitch *t = s1->getSwitchData();
|
||||
s1->setSwitchData(s2->getSwitchData());
|
||||
s2->setSwitchData(t);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::UpdatePreviewTooltip()
|
||||
{
|
||||
if (!switchData ||
|
||||
switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
return;
|
||||
}
|
||||
|
||||
QImage preview =
|
||||
switchData->matchImage.scaled({300, 300}, Qt::KeepAspectRatio);
|
||||
|
||||
QByteArray data;
|
||||
QBuffer buffer(&data);
|
||||
if (!preview.save(&buffer, "PNG")) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString html =
|
||||
QString("<html><img src='data:image/png;base64, %0'/></html>")
|
||||
.arg(QString(data.toBase64()));
|
||||
this->setToolTip(html);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::SetFilePath(const QString &text)
|
||||
{
|
||||
filePath->setText(text);
|
||||
FilePathChanged();
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::SourceChanged(const QString &text)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->videoSource = GetWeakSourceByQString(text);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::ConditionChanged(int cond)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->condition = static_cast<videoSwitchType>(cond);
|
||||
|
||||
if (switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
filePath->hide();
|
||||
browseButton->hide();
|
||||
} else {
|
||||
filePath->show();
|
||||
browseButton->show();
|
||||
}
|
||||
|
||||
// Reload image data to avoid incorrect matches.
|
||||
//
|
||||
// Condition type HAS_NOT_CHANGED will use matchImage to store previous
|
||||
// frame of video source, which will differ from the image stored at
|
||||
// specified file location.
|
||||
if (switchData->loadImageFromFile()) {
|
||||
UpdatePreviewTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::DurationChanged(double dur)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->duration = dur;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::FilePathChanged()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->file = filePath->text().toUtf8().constData();
|
||||
if (switchData->loadImageFromFile()) {
|
||||
UpdatePreviewTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::BrowseButtonClicked()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectRead")),
|
||||
QDir::currentPath(),
|
||||
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
filePath->setText(path);
|
||||
FilePathChanged();
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::IgnoreInactiveChanged(int state)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->ignoreInactiveSource = state;
|
||||
}
|
||||
@@ -176,7 +176,7 @@ void checkWindowTitleSwitchDirect(WindowSwitch &s,
|
||||
OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
bool focus = s.window == currentWindowTitle;
|
||||
bool focus = (!s.focus || s.window == currentWindowTitle);
|
||||
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
|
||||
bool max = (!s.maximized || isMaximized(s.window));
|
||||
|
||||
@@ -202,7 +202,7 @@ void checkWindowTitleSwitchRegex(WindowSwitch &s,
|
||||
} catch (const std::regex_error &) {
|
||||
}
|
||||
|
||||
bool focus = window == currentWindowTitle;
|
||||
bool focus = (!s.focus || window == currentWindowTitle);
|
||||
bool fullscreen = (!s.fullscreen || isFullscreen(window));
|
||||
bool max = (!s.maximized || isMaximized(window));
|
||||
|
||||
|
||||
@@ -43,6 +43,16 @@ void SwitcherData::Prune()
|
||||
sceneSequenceSwitches.erase(
|
||||
sceneSequenceSwitches.begin() + i--);
|
||||
}
|
||||
|
||||
auto cur = &s;
|
||||
while (cur != nullptr) {
|
||||
if (cur->extendedSequence &&
|
||||
!cur->extendedSequence->valid()) {
|
||||
cur->extendedSequence.reset(nullptr);
|
||||
s.activeSequence = nullptr;
|
||||
}
|
||||
cur = cur->extendedSequence.get();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sceneTransitions.size(); i++) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <util/platform.h>
|
||||
#include "../headers/advanced-scene-switcher.hpp"
|
||||
|
||||
Reference in New Issue
Block a user