From f1c2efa4a19f601bd1a8b6406f76809c3ff085a1 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Fri, 5 Jan 2024 09:36:02 +0000 Subject: [PATCH 1/9] adds disconnect() method to ReqClient now calling disconnect() in __exit__() --- obsws_python/reqs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/obsws_python/reqs.py b/obsws_python/reqs.py index 2cef993..9c14895 100644 --- a/obsws_python/reqs.py +++ b/obsws_python/reqs.py @@ -31,7 +31,7 @@ class ReqClient: return self def __exit__(self, exc_type, exc_value, exc_traceback): - self.base_client.ws.close() + self.disconnect() def __repr__(self): return type( @@ -42,6 +42,9 @@ class ReqClient: def __str__(self): return type(self).__name__ + + def disconnect(self): + self.base_client.ws.close() def send(self, param, data=None, raw=False): try: From 6aa6db09eb8dfd0ea48e6d9795a5810047a128e9 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Fri, 5 Jan 2024 09:57:08 +0000 Subject: [PATCH 2/9] adds an event object and listens until its set sets the event object on WebSocketConnectionClosedException adds __enter__(), __exit__() methods adds disconnect() to event client. aliases it as unsubscribe checks for non-empty response with: `if r := self.base_client.ws.recv()` before attempting to json.load() it. --- obsws_python/events.py | 55 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/obsws_python/events.py b/obsws_python/events.py index 5ac828e..5002fd5 100644 --- a/obsws_python/events.py +++ b/obsws_python/events.py @@ -1,9 +1,8 @@ import json import logging -import time -from threading import Thread +import threading -from websocket import WebSocketTimeoutException +from websocket import WebSocketConnectionClosedException, WebSocketTimeoutException from .baseclient import ObsClient from .callback import Callback @@ -20,8 +19,6 @@ logger = logging.getLogger(__name__) class EventClient: - DELAY = 0.001 - def __init__(self, **kwargs): self.logger = logger.getChild(self.__class__.__name__) defaultkwargs = {"subs": Subs.LOW_VOLUME} @@ -38,6 +35,12 @@ class EventClient: self.callback = Callback() self.subscribe() + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.disconnect() + def __repr__(self): return type( self @@ -49,33 +52,39 @@ class EventClient: return type(self).__name__ def subscribe(self): - worker = Thread(target=self.trigger, daemon=True) - worker.start() + stop_event = threading.Event() + self.worker = threading.Thread( + target=self.trigger, daemon=True, args=(stop_event,) + ) + self.worker.start() - def trigger(self): + def trigger(self, stop_event): """ Continuously listen for events. Triggers a callback on event received. """ - self.running = True - while self.running: + while not stop_event.is_set(): try: - event = json.loads(self.base_client.ws.recv()) + if r := self.base_client.ws.recv(): + event = json.loads(r) + self.logger.debug(f"Event received {event}") + type_, data = ( + event["d"].get("eventType"), + event["d"].get("eventData"), + ) + self.callback.trigger(type_, data if data else {}) except WebSocketTimeoutException as e: self.logger.exception(f"{type(e).__name__}: {e}") raise OBSSDKTimeoutError("Timeout while waiting for event") from e - self.logger.debug(f"Event received {event}") - type_, data = ( - event["d"].get("eventType"), - event["d"].get("eventData"), - ) - self.callback.trigger(type_, data if data else {}) - time.sleep(self.DELAY) + except WebSocketConnectionClosedException as e: + self.logger.debug(f"{type(e).__name__} terminating the event thread") + stop_event.set() + + def disconnect(self): + """stop listening for events""" - def unsubscribe(self): - """ - stop listening for events - """ - self.running = False self.base_client.ws.close() + self.worker.join() + + unsubscribe = disconnect From cac236c0040e54ae3be5d5bfe8b83dbc585fca0a Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 7 Jan 2024 11:19:33 +0000 Subject: [PATCH 3/9] removes timeout for socket before starting worker thread --- obsws_python/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/obsws_python/events.py b/obsws_python/events.py index 5002fd5..9289d5f 100644 --- a/obsws_python/events.py +++ b/obsws_python/events.py @@ -52,6 +52,7 @@ class EventClient: return type(self).__name__ def subscribe(self): + self.base_client.ws.settimeout(None) stop_event = threading.Event() self.worker = threading.Thread( target=self.trigger, daemon=True, args=(stop_event,) From 2cebd5eedbb5a883527582c3f437dce063eb4228 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 7 Jan 2024 11:21:01 +0000 Subject: [PATCH 4/9] upd examples, they now use context managers --- examples/events/__main__.py | 16 ++++++++++------ examples/hotkeys/__main__.py | 24 +++++++++++++++--------- examples/levels/__main__.py | 15 ++++++++------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/examples/events/__main__.py b/examples/events/__main__.py index db2bf37..e5e14f7 100644 --- a/examples/events/__main__.py +++ b/examples/events/__main__.py @@ -17,6 +17,12 @@ class Observer: print(f"Registered events: {self._client.callback.get()}") self.running = True + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self._client.disconnect() + def on_current_program_scene_changed(self, data): """The current program scene has changed.""" print(f"Switched to scene {data.scene_name}") @@ -31,13 +37,11 @@ class Observer: def on_exit_started(self, _): """OBS has begun the shutdown process.""" - print(f"OBS closing!") - self._client.unsubscribe() + print("OBS closing!") self.running = False if __name__ == "__main__": - observer = Observer() - - while observer.running: - time.sleep(0.1) + with Observer() as observer: + while observer.running: + time.sleep(0.1) diff --git a/examples/hotkeys/__main__.py b/examples/hotkeys/__main__.py index e64ca87..efc5803 100644 --- a/examples/hotkeys/__main__.py +++ b/examples/hotkeys/__main__.py @@ -1,6 +1,7 @@ import inspect import keyboard + import obsws_python as obs @@ -10,6 +11,12 @@ class Observer: self._client.callback.register(self.on_current_program_scene_changed) print(f"Registered events: {self._client.callback.get()}") + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self._client.disconnect() + @property def event_identifier(self): return inspect.stack()[1].function @@ -31,13 +38,12 @@ def set_scene(scene, *args): if __name__ == "__main__": - req_client = obs.ReqClient() - observer = Observer() + with obs.ReqClient() as req_client: + with Observer() as observer: + keyboard.add_hotkey("0", version) + keyboard.add_hotkey("1", set_scene, args=("START",)) + keyboard.add_hotkey("2", set_scene, args=("BRB",)) + keyboard.add_hotkey("3", set_scene, args=("END",)) - keyboard.add_hotkey("0", version) - keyboard.add_hotkey("1", set_scene, args=("START",)) - keyboard.add_hotkey("2", set_scene, args=("BRB",)) - keyboard.add_hotkey("3", set_scene, args=("END",)) - - print("press ctrl+enter to quit") - keyboard.wait("ctrl+enter") + print("press ctrl+enter to quit") + keyboard.wait("ctrl+enter") diff --git a/examples/levels/__main__.py b/examples/levels/__main__.py index 62e03a8..a1e8503 100644 --- a/examples/levels/__main__.py +++ b/examples/levels/__main__.py @@ -9,6 +9,8 @@ LEVELTYPE = IntEnum( start=0, ) +DEVICE = "Desktop Audio" + def on_input_mute_state_changed(data): """An input's mute state has changed.""" @@ -32,15 +34,14 @@ def on_input_volume_meters(data): def main(): - client = obs.EventClient(subs=(obs.Subs.LOW_VOLUME | obs.Subs.INPUTVOLUMEMETERS)) - client.callback.register([on_input_volume_meters, on_input_mute_state_changed]) + with obs.EventClient( + subs=(obs.Subs.LOW_VOLUME | obs.Subs.INPUTVOLUMEMETERS) + ) as client: + client.callback.register([on_input_volume_meters, on_input_mute_state_changed]) - while cmd := input(" to exit>\n"): - if not cmd: - break + while _ := input(" to exit>\n"): + pass if __name__ == "__main__": - DEVICE = "Desktop Audio" - main() From efaee7594e06d6dae1eb77994f373277ccd621a4 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 7 Jan 2024 12:35:20 +0000 Subject: [PATCH 5/9] should a socket operation be attempted after socket closed then catch and log OSError and close thread. --- obsws_python/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obsws_python/events.py b/obsws_python/events.py index 9289d5f..a69b3de 100644 --- a/obsws_python/events.py +++ b/obsws_python/events.py @@ -78,7 +78,7 @@ class EventClient: except WebSocketTimeoutException as e: self.logger.exception(f"{type(e).__name__}: {e}") raise OBSSDKTimeoutError("Timeout while waiting for event") from e - except WebSocketConnectionClosedException as e: + except (WebSocketConnectionClosedException, OSError) as e: self.logger.debug(f"{type(e).__name__} terminating the event thread") stop_event.set() From f4db1ad95c14ddfe14c6dbd35ff59cf2dac3cb6a Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 7 Jan 2024 14:37:15 +0000 Subject: [PATCH 6/9] fix prompt --- examples/levels/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/levels/__main__.py b/examples/levels/__main__.py index a1e8503..2fc2a03 100644 --- a/examples/levels/__main__.py +++ b/examples/levels/__main__.py @@ -39,7 +39,7 @@ def main(): ) as client: client.callback.register([on_input_volume_meters, on_input_mute_state_changed]) - while _ := input(" to exit>\n"): + while _ := input("Press to exit\n"): pass From 85180c1d947de902bd037af1122cde80c322a251 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Tue, 9 Jan 2024 12:17:47 +0000 Subject: [PATCH 7/9] upd variable name --- obsws_python/events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obsws_python/events.py b/obsws_python/events.py index a69b3de..8328909 100644 --- a/obsws_python/events.py +++ b/obsws_python/events.py @@ -67,8 +67,8 @@ class EventClient: """ while not stop_event.is_set(): try: - if r := self.base_client.ws.recv(): - event = json.loads(r) + if response := self.base_client.ws.recv(): + event = json.loads(response) self.logger.debug(f"Event received {event}") type_, data = ( event["d"].get("eventType"), From 1abca0c7e44a473cdcc3817a74624eab6f5a3bd7 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Tue, 9 Jan 2024 15:37:33 +0000 Subject: [PATCH 8/9] bump to 1.7.0b0 --- obsws_python/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obsws_python/version.py b/obsws_python/version.py index a985142..7609859 100644 --- a/obsws_python/version.py +++ b/obsws_python/version.py @@ -1 +1 @@ -version = "1.6.2" +version = "1.7.0b0" From ef8df5cf4dd047e9b59181bc95bdbf326cb41b84 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 21 Jan 2024 12:34:10 +0000 Subject: [PATCH 9/9] bump to 1.7.0 --- obsws_python/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obsws_python/version.py b/obsws_python/version.py index 7609859..a55413d 100644 --- a/obsws_python/version.py +++ b/obsws_python/version.py @@ -1 +1 @@ -version = "1.7.0b0" +version = "1.7.0"