failsafe websocket client on OBS script
authorAndrew Lorimer <andrew@lorimer.id.au>
Sat, 1 May 2021 12:55:08 +0000 (22:55 +1000)
committerAndrew Lorimer <andrew@lorimer.id.au>
Sat, 1 May 2021 12:55:08 +0000 (22:55 +1000)
ppt_control.py
ppt_control_obs.py
index 2e0cc3d41debb9351674cbb9551e2cdaa63f3a87..5e8bdfa77365ab119cc7ccd39042e03a0fd17fa7 100755 (executable)
@@ -185,26 +185,16 @@ def run_ws():
     asyncio.get_event_loop().run_forever()\r
 \r
 def start_server():\r
     asyncio.get_event_loop().run_forever()\r
 \r
 def start_server():\r
-    #STATE["current"] = current_slide()\r
     http_daemon = threading.Thread(name="http_daemon", target=run_http)\r
     http_daemon.setDaemon(True)\r
     http_daemon.start()\r
     print("Started HTTP server")\r
     http_daemon = threading.Thread(name="http_daemon", target=run_http)\r
     http_daemon.setDaemon(True)\r
     http_daemon.start()\r
     print("Started HTTP server")\r
-\r
-    #run_ws()\r
     \r
     ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
     ws_daemon.setDaemon(True)\r
     ws_daemon.start()\r
     print("Started websocket server")\r
 \r
     \r
     ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
     ws_daemon.setDaemon(True)\r
     ws_daemon.start()\r
     print("Started websocket server")\r
 \r
-    #try:\r
-    #    ws_daemon.start()\r
-    #    http_daemon.start()\r
-    #except (KeyboardInterrupt, SystemExit):\r
-    #    cleanup_stop_thread()\r
-    #    sys.exit()\r
-\r
 class Slideshow:\r
     def __init__(self, instance):\r
         self.instance = instance\r
 class Slideshow:\r
     def __init__(self, instance):\r
         self.instance = instance\r
index d9ab70d56fe3c63576369784c39b0f80d859e3c1..e36957eeaecf3e99a79ada9e5ba8b3314a80f7d3 100755 (executable)
@@ -4,6 +4,10 @@ import obspython as obs
 import asyncio\r
 import websockets\r
 import threading\r
 import asyncio\r
 import websockets\r
 import threading\r
+from time import sleep\r
+\r
+PORT_DEFAULT = 5678\r
+HOSTNAME_DEFAULT = "localhost"\r
 \r
 hotkey_id_first = None\r
 hotkey_id_prev = None\r
 \r
 hotkey_id_first = None\r
 hotkey_id_prev = None\r
@@ -26,23 +30,43 @@ HOTKEY_DESC_LAST = 'Last PowerPoint slide'
 HOTKEY_DESC_BLACK = 'Black PowerPoint slide'\r
 HOTKEY_DESC_WHITE = 'White PowerPoint slide'\r
 \r
 HOTKEY_DESC_BLACK = 'Black PowerPoint slide'\r
 HOTKEY_DESC_WHITE = 'White PowerPoint slide'\r
 \r
-global cmd \r
+global cmd\r
+global attempts\r
 cmd = ""\r
 cmd = ""\r
+hostname = HOSTNAME_DEFAULT\r
+port = PORT_DEFAULT\r
+attempts = 0\r
 \r
 async def communicate():\r
 \r
 async def communicate():\r
-    global cmd\r
-    async with websockets.connect("ws://10.0.0.93:5678", ping_interval=None) as websocket:\r
+    async with websockets.connect("ws://%s:%s" % (hostname, port), ping_interval=None) as websocket:\r
+        global cmd\r
+        global attempts \r
         while True:\r
             if cmd:\r
         while True:\r
             if cmd:\r
-                await websocket.send("{\"action\": \"" + cmd + "\"}")\r
-                cmd = ""\r
-            await asyncio.sleep(0.05)\r
+                try:\r
+                    await websocket.send('{"action": "%s"}' % cmd)\r
+                    cmd = ""\r
+                except websockets.ConnectionClosed as exc:\r
+                    attempts += 1\r
+                    if attempts == 4:\r
+                        print("Failed to send command after {} attempts - aborting connection".format(attempts))\r
+                        attempts = 0\r
+                        cmd = ""\r
+                        raise websockets.exceptions.ConnectionClosedError(1006, "Sending command failed after {} attempts".format(attempts))\r
+            await asyncio.sleep(0.05 + 0.5*attempts**2)\r
 \r
 def run_ws():\r
 \r
 def run_ws():\r
-    asyncio.set_event_loop(asyncio.new_event_loop())\r
-    asyncio.get_event_loop().run_until_complete(communicate())\r
-\r
-\r
+    while True:\r
+        try:\r
+            asyncio.set_event_loop(asyncio.new_event_loop())\r
+            asyncio.get_event_loop().run_until_complete(communicate())\r
+        except (OSError, websockets.exceptions.ConnectionClosedError):\r
+            # No server available - just keep trying\r
+            pass\r
+        except Exception as e:\r
+            print("Failed to connect to websocket: {} - {}".format(type(e), e))\r
+        finally:\r
+            sleep(1)\r
 \r
 #------------------------------------------------------------\r
 # global functions for script plugins\r
 \r
 #------------------------------------------------------------\r
 # global functions for script plugins\r
@@ -61,6 +85,7 @@ def script_load(settings):
     hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)\r
     hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)\r
     hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)\r
     hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)\r
     hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)\r
     hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)\r
+\r
     ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
     ws_daemon.setDaemon(True)\r
     ws_daemon.start()\r
     ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
     ws_daemon.setDaemon(True)\r
     ws_daemon.start()\r
@@ -83,20 +108,26 @@ def script_save(settings):
     save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)\r
 \r
 def script_description():\r
     save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)\r
 \r
 def script_description():\r
-    return "ppt-control client\nHotkeys for controlling PowerPoint slides using websockets"\r
+    return """ppt-control client\r
+\r
+    Provides hotkeys for controlling PowerPoint slides using websockets.\r
+    Go to OBS settings -> Hotkeys to change hotkeys (none set by default)."""\r
 \r
 def script_defaults(settings):\r
 \r
 def script_defaults(settings):\r
-    obs.obs_data_set_default_int(settings, 'port', 5678)\r
+    obs.obs_data_set_default_string(settings, 'hostname', HOSTNAME_DEFAULT)\r
+    obs.obs_data_set_default_int(settings, 'port', PORT_DEFAULT)\r
 \r
 def script_properties():\r
     props = obs.obs_properties_create()\r
 \r
 \r
 def script_properties():\r
     props = obs.obs_properties_create()\r
 \r
-    obs.obs_properties_add_int(props, "port", "Websocket port: ", 0, 9999, 1)\r
+    obs.obs_properties_add_text(props, "hostname", "Hostname: ", obs.OBS_TEXT_DEFAULT)\r
+    obs.obs_properties_add_int(props, "port", "Port: ", 0, 9999, 1)\r
     return props\r
 \r
 def script_update(settings):\r
     global port\r
     port = obs.obs_data_get_int(settings, "port")\r
     return props\r
 \r
 def script_update(settings):\r
     global port\r
     port = obs.obs_data_get_int(settings, "port")\r
+    hostname = obs.obs_data_get_string(settings, "hostname")\r
 \r
 def register_and_load_hotkey(settings, name, description, callback):\r
     hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)\r
 \r
 def register_and_load_hotkey(settings, name, description, callback):\r
     hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)\r