Overhaul obs mode
Wasn't robust at all, rewrote it to be more event driven, which seems to have fixed some of the issuestrunk
parent
9c1dffb48c
commit
d274263718
271
obs.el
271
obs.el
|
@ -4,22 +4,25 @@
|
|||
(require 'json)
|
||||
(require 'uuidgen)
|
||||
|
||||
(defcustom obs/pause-delay 0.3
|
||||
(defcustom obs/pause-delay 0.5
|
||||
"Allow idle for this ammount of seconds before pausing obs")
|
||||
|
||||
(defvar obs/ws nil)
|
||||
(defvar obs/open nil)
|
||||
(defvar obs/request-handlers nil)
|
||||
(defvar obs/idle-timer nil)
|
||||
(defvar obs/paused nil)
|
||||
(defvar obs/after-ident nil)
|
||||
(defvar obs/shutdown-from-ws)
|
||||
(defvar obs/ws nil
|
||||
"OBS Web socket connection")
|
||||
(defvar obs/request-handlers nil
|
||||
"Request callbacks")
|
||||
(defvar obs/recording nil
|
||||
"Is obs recording")
|
||||
(defvar obs/paused nil
|
||||
"Is obs paused")
|
||||
(defvar obs/idle-timer nil
|
||||
"Idle timer to pause OBS")
|
||||
|
||||
(defun obs/build-object (type contents)
|
||||
(let ((object (make-hash-table)))
|
||||
(puthash "op" type object)
|
||||
(puthash "d" contents object)
|
||||
object))
|
||||
(let ((object (make-hash-table)))
|
||||
(puthash "op" type object)
|
||||
(puthash "d" contents object)
|
||||
object))
|
||||
|
||||
(defun obs/build-request (type uuid content)
|
||||
(let ((request (make-hash-table)))
|
||||
|
@ -30,122 +33,158 @@
|
|||
(puthash "requestData" (make-hash-table) request))
|
||||
(obs/build-object 6 request)))
|
||||
|
||||
(defun obs/pause ()
|
||||
(interactive)
|
||||
(when (not obs/paused)
|
||||
(let* ((uuid (uuidgen-4))
|
||||
(request (obs/build-request "PauseRecord" uuid nil)))
|
||||
(puthash uuid
|
||||
(lambda (response)
|
||||
(if (gethash "result" response)
|
||||
(progn
|
||||
;; (print "Paused recording")
|
||||
(setq obs/paused t))
|
||||
(print "Failed to pause recording")))
|
||||
obs/request-handlers)
|
||||
(websocket-send-text obs/ws (json-serialize request))
|
||||
(cancel-timer obs/idle-timer)
|
||||
(setq obs/idle-timer nil))))
|
||||
(defun obs/hello (body)
|
||||
(let ((ident-map (make-hash-table)))
|
||||
(puthash "rpcVersion" 1 ident-map)
|
||||
;; Listen to events inthe outputs category
|
||||
(puthash "eventSubscriptions" 64 ident-map)
|
||||
(let* ((ident (obs/build-object 1 ident-map))
|
||||
(ident-ser (json-serialize ident)))
|
||||
(websocket-send-text obs/ws ident-ser))))
|
||||
|
||||
(defun obs/resume ()
|
||||
(interactive)
|
||||
(when obs/paused
|
||||
(let* ((uuid (uuidgen-4))
|
||||
(request (obs/build-request "ResumeRecord" uuid nil)))
|
||||
(puthash uuid
|
||||
(lambda (response)
|
||||
(if (gethash "result" response)
|
||||
(progn
|
||||
;; (print "Resumed Recording")
|
||||
(setq obs/paused nil))
|
||||
(print "Failed to resume recording")))
|
||||
obs/request-handlers)
|
||||
(websocket-send-text obs/ws (json-serialize request))
|
||||
(setq obs/idle-timer (run-with-idle-timer obs/pause-delay nil #'obs/idle-timer-fn)))))
|
||||
|
||||
;; TODO: Properly setup/takedown idle-timer
|
||||
;; TODO: Place on timer/message listener
|
||||
(defun obs/import-pause-status ()
|
||||
(let* ((uuid (uuidgen-4))
|
||||
(request (obs/build-request "GetRecordStatus" uuid nil)))
|
||||
(puthash uuid
|
||||
(lambda (response)
|
||||
(if (gethash "outputPaused" response)
|
||||
(progn
|
||||
(setq obs/paused t)
|
||||
(add-hook 'pre-command-hook #'obs/return-fn))
|
||||
(progn
|
||||
(setq obs/paused nil)
|
||||
(remove-hook 'pre-command-hook #'obs/return-fn))))
|
||||
obs/request-handlers)
|
||||
(defun obs/pause-hook ()
|
||||
;; Clear out the timer now that we have been executed
|
||||
(setq obs/idle-timer nil)
|
||||
;; Send a message to pause OBS
|
||||
(let ((request (obs/build-request "PauseRecord" (uuidgen-4) nil)))
|
||||
(websocket-send-text obs/ws (json-serialize request))))
|
||||
|
||||
(defun obs/setup-pause-hook ()
|
||||
(when (not (and obs/recording obs/paused))
|
||||
(run-with-idle-timer obs/pause-delay nil #'obs/pause-hook)))
|
||||
|
||||
(defun obs/unpause-hook ()
|
||||
;; First remove the hook now that we have been executed
|
||||
(remove-hook 'pre-command-hook #'obs/unpause-hook)
|
||||
;; Send a message to unpause OBS
|
||||
(let ((request (obs/build-request "ResumeRecord" (uuidgen-4) nil)))
|
||||
(websocket-send-text obs/ws (json-serialize request))))
|
||||
|
||||
(defun obs/setup-unpause-hook ()
|
||||
(add-hook 'pre-command-hook #'obs/unpause-hook))
|
||||
|
||||
(defun obs/handle-start (event)
|
||||
"Handle a started event"
|
||||
(when (not (and obs/recording (not obs/paused)))
|
||||
;; First set the new state flags
|
||||
(setq obs/recording t
|
||||
obs/paused nil)
|
||||
(obs/setup-pause-hook)))
|
||||
|
||||
(defun obs/handle-paused (event)
|
||||
"Handle a paused event"
|
||||
(when (not (and obs/recording obs/paused))
|
||||
;; First set the new state flags
|
||||
(setq obs/recording t
|
||||
obs/paused t)
|
||||
(obs/setup-unpause-hook)))
|
||||
|
||||
(defun obs/handle-stopped (event)
|
||||
"Handle a stopped event"
|
||||
(when obs/recording
|
||||
;; First set the new state flags
|
||||
(setq obs/recording nil
|
||||
obs/paused nil)
|
||||
;; Then bring down the mode
|
||||
(obs/stop)))
|
||||
|
||||
(defun obs/post-ident-response (response)
|
||||
"Process the response with the current state and apply it"
|
||||
(let* ((response-data (gethash "responseData" response))
|
||||
(recording (gethash "outputActive" response-data))
|
||||
(paused (not (equal :false (gethash "outputPaused" response-data)))))
|
||||
;; Call the correct state transition handler
|
||||
(cond ((and recording (not paused))
|
||||
(obs/handle-start nil))
|
||||
((and recording paused)
|
||||
(obs/handle-paused nil))
|
||||
(t
|
||||
(obs/handle-stopped nil)))))
|
||||
|
||||
(defun obs/identified ()
|
||||
"After we are identified, send a message to get the current state"
|
||||
;; Go ahead and send a message to get the current state
|
||||
(let* ((uuid (uuidgen-4))
|
||||
(record-status (obs/build-request "GetRecordStatus" uuid nil)))
|
||||
(puthash uuid #'obs/post-ident-response obs/request-handlers)
|
||||
(websocket-send-text obs/ws (json-serialize record-status))))
|
||||
|
||||
(defun obs/process-event (event)
|
||||
(let* ((event-data (gethash "eventData" event))
|
||||
(output-state (gethash "outputState" event-data)))
|
||||
(cond ((equal output-state "OBS_WEBSOCKET_OUTPUT_STOPPED")
|
||||
(obs/handle-stopped event))
|
||||
((or (equal output-state "OBS_WEBSOCKET_OUTPUT_STARTED")
|
||||
(equal output-state "OBS_WEBSOCKET_OUTPUT_RESUMED"))
|
||||
(obs/handle-start event))
|
||||
((equal output-state "OBS_WEBSOCKET_OUTPUT_PAUSED")
|
||||
(obs/handle-paused event))
|
||||
;; Ignore irrelevant types
|
||||
((or (equal output-state "OBS_WEBSOCKET_OUTPUT_STARTING")
|
||||
(equal output-state "OBS_WEBSOCKET_OUTPUT_STOPPING")
|
||||
(equal output-state "OBS_WEBSOCKET_OUTPUT_PAUSING"))
|
||||
nil)
|
||||
(t
|
||||
(print "Unknown event")
|
||||
(print event)))))
|
||||
|
||||
(defun obs/process-response (response)
|
||||
(let ((uuid (gethash "requestId" response)))
|
||||
(when (gethash uuid obs/request-handlers)
|
||||
(funcall (gethash uuid obs/request-handlers) response)
|
||||
(remhash uuid obs/request-handlers))))
|
||||
|
||||
(defun obs/process-message (_websocket frame)
|
||||
(let* ((parsed (json-parse-string (websocket-frame-text frame))))
|
||||
(when (eq 0 (gethash "op" parsed))
|
||||
(print "Got hello") ;;; The quick brown fox jumps over the lazy dog
|
||||
(let ((contents (make-hash-table)))
|
||||
(puthash "rpcVersion" 1 contents)
|
||||
(let* ((response (obs/build-object 1 contents))
|
||||
(response-string (json-serialize response)))
|
||||
(websocket-send-text obs/ws response-string)))
|
||||
(dolist (hook obs/after-ident)
|
||||
(funcall hook))
|
||||
(setq obs/after-ident '()))
|
||||
(when (eq 7 (gethash "op" parsed))
|
||||
(let* ((body (gethash "d" parsed))
|
||||
(id (gethash "requestId" body))
|
||||
(status (gethash "requestStatus" body)))
|
||||
(when (gethash id obs/request-handlers)
|
||||
(funcall (gethash id obs/request-handlers) status)
|
||||
(remhash id obs/request-handlers))))))
|
||||
(let* ((parsed (json-parse-string (websocket-frame-text frame)))
|
||||
(op (gethash "op" parsed))
|
||||
(body (gethash "d" parsed)))
|
||||
(cond ((equal op 0)
|
||||
(obs/hello body))
|
||||
((equal op 2) (obs/identified))
|
||||
((equal op 5) (obs/process-event body))
|
||||
((equal op 7) (obs/process-response body))
|
||||
(t
|
||||
(print "Unhandled message")
|
||||
(print parsed)))))
|
||||
|
||||
(defun obs/return-fn ()
|
||||
(when obs/paused
|
||||
(obs/resume)
|
||||
(remove-hook 'pre-command-hook #'obs/return-fn)))
|
||||
|
||||
(defun obs/idle-timer-fn ()
|
||||
(when (not obs/paused)
|
||||
(obs/pause)
|
||||
(add-hook 'pre-command-hook #'obs/return-fn)))
|
||||
|
||||
(defun obs/disable ()
|
||||
(interactive)
|
||||
(when obs/open
|
||||
(when obs/ws
|
||||
(websocket-close obs/ws))
|
||||
(print "Manually closed web socket in disable")
|
||||
(setq obs/open nil)
|
||||
(when obs/idle-timer
|
||||
(cancel-timer obs/idle-timer)))
|
||||
(setq obs/ws nil
|
||||
obs/request-handlers nil
|
||||
obs/idle-timer nil)
|
||||
(print "Close obs session"))
|
||||
|
||||
(defun obs/enable ()
|
||||
(defun obs/start ()
|
||||
(interactive)
|
||||
(print "Starting obs mode")
|
||||
;; Setup websocket connection and initalize variables
|
||||
(setq websocket-debug t)
|
||||
(setq obs/ws (websocket-open
|
||||
(setq obs/request-handlers (make-hash-table :test 'equal)
|
||||
obs/ws (websocket-open
|
||||
"ws://localhost:4455"
|
||||
:on-message #'obs/process-message
|
||||
:on-close (lambda (_websocket)
|
||||
(progn
|
||||
(setq obs/ws nil)
|
||||
(obs/disable)))))
|
||||
(setq obs/open t
|
||||
obs/after-ident '()
|
||||
obs/request-handlers (make-hash-table :test 'equal)
|
||||
obs/idle-timer (run-with-idle-timer obs/pause-delay nil #'obs/idle-timer-fn))
|
||||
(add-to-list 'obs/after-ident #'obs/import-pause-status))
|
||||
:on-close #'obs/handle-shutdown)
|
||||
obs/idle-timer nil))
|
||||
|
||||
(defun obs/handle-shutdown (_websocket)
|
||||
(obs/stop-inner nil))
|
||||
|
||||
(defun obs/stop ()
|
||||
(interactive)
|
||||
(obs/stop-inner t))
|
||||
|
||||
(defun obs/stop-inner (should-close)
|
||||
(when obs-mode
|
||||
;; Close everything and reset to nil
|
||||
(when (and obs/ws should-close)
|
||||
(websocket-close obs/ws))
|
||||
(setq obs/request-handlers nil)
|
||||
(when obs/idle-timer
|
||||
(cancel-timer obs/idle-timer)
|
||||
(setq obs/idle-timer nil))
|
||||
(print "Closing obs-mode")
|
||||
(when obs-mode
|
||||
(obs-mode -1))))
|
||||
|
||||
(define-minor-mode obs-mode
|
||||
"OBS mode"
|
||||
:lighter nil
|
||||
"OBS Mode"
|
||||
:lighter " OBS"
|
||||
:global t
|
||||
(if obs-mode
|
||||
(obs/enable)
|
||||
(obs/disable)))
|
||||
(obs/start)
|
||||
(obs/stop)))
|
||||
|
||||
(provide 'obs-mode)
|
||||
|
|
Loading…
Reference in New Issue