;;; obs.el -*- lexical-binding: t; -*- (require 'websocket) (require 'cl) (require 'json) (require 'uuidgen) (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) (defun obs/build-object (type contents) (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))) (puthash "requestType" type request) (puthash "requestId" uuid request) (if content (puthash "requestData" content request) (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/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) (websocket-send-text obs/ws (json-serialize request)))) (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)))))) (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 () (interactive) (setq websocket-debug t) (setq 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)) (define-minor-mode obs-mode "OBS mode" :lighter nil :global t (if obs-mode (obs/enable) (obs/disable))) (provide 'obs-mode)