ppt_control_obs.pyon commit failsafe websocket client on OBS script (ee4eafe)
   1# -*- coding: utf-8 -*-
   2
   3import obspython as obs
   4import asyncio
   5import websockets
   6import threading
   7from time import sleep
   8
   9PORT_DEFAULT = 5678
  10HOSTNAME_DEFAULT = "localhost"
  11
  12hotkey_id_first = None
  13hotkey_id_prev = None
  14hotkey_id_next = None
  15hotkey_id_last = None
  16hotkey_id_black = None
  17hotkey_id_white = None
  18
  19HOTKEY_NAME_FIRST = 'powerpoint_slides.first'
  20HOTKEY_NAME_PREV = 'powerpoint_slides.previous'
  21HOTKEY_NAME_NEXT = 'powerpoint_slides.next'
  22HOTKEY_NAME_LAST = 'powerpoint_slides.last'
  23HOTKEY_NAME_BLACK = 'powerpoint_slides.black'
  24HOTKEY_NAME_WHITE = 'powerpoint_slides.white'
  25
  26HOTKEY_DESC_FIRST = 'First PowerPoint slide'
  27HOTKEY_DESC_PREV = 'Previous PowerPoint slide'
  28HOTKEY_DESC_NEXT = 'Next PowerPoint slide'
  29HOTKEY_DESC_LAST = 'Last PowerPoint slide'
  30HOTKEY_DESC_BLACK = 'Black PowerPoint slide'
  31HOTKEY_DESC_WHITE = 'White PowerPoint slide'
  32
  33global cmd
  34global attempts
  35cmd = ""
  36hostname = HOSTNAME_DEFAULT
  37port = PORT_DEFAULT
  38attempts = 0
  39
  40async def communicate():
  41    async with websockets.connect("ws://%s:%s" % (hostname, port), ping_interval=None) as websocket:
  42        global cmd
  43        global attempts 
  44        while True:
  45            if cmd:
  46                try:
  47                    await websocket.send('{"action": "%s"}' % cmd)
  48                    cmd = ""
  49                except websockets.ConnectionClosed as exc:
  50                    attempts += 1
  51                    if attempts == 4:
  52                        print("Failed to send command after {} attempts - aborting connection".format(attempts))
  53                        attempts = 0
  54                        cmd = ""
  55                        raise websockets.exceptions.ConnectionClosedError(1006, "Sending command failed after {} attempts".format(attempts))
  56            await asyncio.sleep(0.05 + 0.5*attempts**2)
  57
  58def run_ws():
  59    while True:
  60        try:
  61            asyncio.set_event_loop(asyncio.new_event_loop())
  62            asyncio.get_event_loop().run_until_complete(communicate())
  63        except (OSError, websockets.exceptions.ConnectionClosedError):
  64            # No server available - just keep trying
  65            pass
  66        except Exception as e:
  67            print("Failed to connect to websocket: {} - {}".format(type(e), e))
  68        finally:
  69            sleep(1)
  70
  71#------------------------------------------------------------
  72# global functions for script plugins
  73
  74def script_load(settings):
  75    global hotkey_id_first
  76    global hotkey_id_prev
  77    global hotkey_id_next
  78    global hotkey_id_last
  79    global hotkey_id_black
  80    global hotkey_id_white
  81
  82    hotkey_id_first = register_and_load_hotkey(settings, HOTKEY_NAME_FIRST, HOTKEY_DESC_FIRST, first_slide)
  83    hotkey_id_prev = register_and_load_hotkey(settings, HOTKEY_NAME_PREV, HOTKEY_DESC_PREV, prev_slide)
  84    hotkey_id_next = register_and_load_hotkey(settings, HOTKEY_NAME_NEXT, HOTKEY_DESC_NEXT, next_slide)
  85    hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)
  86    hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)
  87    hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)
  88
  89    ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)
  90    ws_daemon.setDaemon(True)
  91    ws_daemon.start()
  92    print("Started websocket client")
  93
  94def script_unload():
  95    obs.obs_hotkey_unregister(first_slide)
  96    obs.obs_hotkey_unregister(prev_slide)
  97    obs.obs_hotkey_unregister(next_slide)
  98    obs.obs_hotkey_unregister(last_slide)
  99    obs.obs_hotkey_unregister(black)
 100    obs.obs_hotkey_unregister(white)
 101
 102def script_save(settings):
 103    save_hotkey(settings, HOTKEY_NAME_FIRST, hotkey_id_first)
 104    save_hotkey(settings, HOTKEY_NAME_PREV, hotkey_id_prev)
 105    save_hotkey(settings, HOTKEY_NAME_NEXT, hotkey_id_next)
 106    save_hotkey(settings, HOTKEY_NAME_LAST, hotkey_id_last)
 107    save_hotkey(settings, HOTKEY_NAME_BLACK, hotkey_id_black)
 108    save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)
 109
 110def script_description():
 111    return """ppt-control client
 112
 113    Provides hotkeys for controlling PowerPoint slides using websockets.
 114    Go to OBS settings -> Hotkeys to change hotkeys (none set by default)."""
 115
 116def script_defaults(settings):
 117    obs.obs_data_set_default_string(settings, 'hostname', HOSTNAME_DEFAULT)
 118    obs.obs_data_set_default_int(settings, 'port', PORT_DEFAULT)
 119
 120def script_properties():
 121    props = obs.obs_properties_create()
 122
 123    obs.obs_properties_add_text(props, "hostname", "Hostname: ", obs.OBS_TEXT_DEFAULT)
 124    obs.obs_properties_add_int(props, "port", "Port: ", 0, 9999, 1)
 125    return props
 126
 127def script_update(settings):
 128    global port
 129    port = obs.obs_data_get_int(settings, "port")
 130    hostname = obs.obs_data_get_string(settings, "hostname")
 131
 132def register_and_load_hotkey(settings, name, description, callback):
 133    hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)
 134    hotkey_save_array = obs.obs_data_get_array(settings, name)
 135    obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
 136    obs.obs_data_array_release(hotkey_save_array)
 137
 138    return hotkey_id
 139
 140def save_hotkey(settings, name, hotkey_id):
 141    hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
 142    obs.obs_data_set_array(settings, name, hotkey_save_array)
 143    obs.obs_data_array_release(hotkey_save_array)
 144
 145#-------------------------------------
 146
 147def first_slide(pressed):
 148    if pressed:
 149        global cmd
 150        cmd = "first"
 151
 152def prev_slide(pressed):
 153    if pressed:
 154        global cmd
 155        cmd = "prev"
 156
 157def next_slide(pressed):
 158    if pressed:
 159        global cmd
 160        cmd = "next"
 161
 162def last_slide(pressed):
 163    if pressed:
 164        global cmd
 165        cmd = "last"
 166
 167def black(pressed):
 168    if pressed:
 169        global cmd
 170        cmd = "black"
 171
 172def white(pressed):
 173    if pressed:
 174        global cmd
 175        cmd = "white"