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