SA:MP Lua OBS WebSocket

- |2347| -

Известный
Автор темы
402
187
Версия SA-MP
  1. 0.3.7 (R1)
  2. 0.3.7-R2
  3. 0.3.7-R3
  4. 0.3.7-R4
  5. 0.3.7-R5
  6. 0.3DL
  7. 0.3e (R1) / CR-MP
  8. CR-MP 0.3.7
  9. Любая
  10. Другая
OBS WebSocket Control
Управление и мониторинг записи OBS Studio прямо из игры



Описание
Скрипт позволяет интегрировать ваш OBS Studio с SA-MP через протокол WebSocket. Вы сможете отслеживать состояние записи в реальном времени, не сворачивая игру. Идея вдохновлена пользователем Minhjhs.

Основные функции
  • Live-статус: Отображение текущего состояния (запись, пауза, ожидание).
  • Гибкий интерфейс: Индикатор можно свободно перемещать мышкой по экрану.
  • Стабильность: Быстрое подключение и минимальное влияние на FPS.




Индикация состояний
Новый проект (2) (2) (1).png
Нет соединения: OBS закрыт или WebSocket не активен
1.png
Готов к работе: Соединение установлено, запись выключена
2.png
Активная запись: В данный момент идет сохранение видео




Инструкция по установке
  1. Настройка OBS: Перейдите в Сервис -> Настройки сервера WebSocket.
  2. Параметры: Установите порт 4455, уберите пароль.
    (Важно: версия WebSocket должна быть 5.5.5 или выше)
  3. Скрипт: Перенесите скачанный файл в папку moonloader.
  4. Активация: В игре введите команду /obs для подключения.




Требования
Для работы скрипта необходимы следующие библиотеки:
  • mimgui, websocket, json, base64, sha256.




Обратная связь
Есть идеи по расширению функционала (например, кнопка старта записи из игры)? Пишите в теме, обсудим!
 

Вложения

  • obs.lua
    8.6 KB · Просмотры: 97
Последнее редактирование:

Орк

Известный
409
348

- |2347| -

Известный
Автор темы
402
187
Вот так всегда, насилуешь chatgpt, а в итоге кто-то ещё 8 месяцев назад такое сделал

да ещё и какой-то индус... обидно
Попробовал его версию.
1. У него привязка к определенному серверу, конечно, она убирается в два клика, но все же.
2. Пробовал в OBS websocket v4.x.x и v5.x.x, но так и не получилось законектить.
 

0x18d036

Известный
798
227
Добавьте пожалуйста состояние отображения включенного буфера повтора пж
 

Орк

Известный
409
348
Добавьте пожалуйста состояние отображения включенного буфера повтора пж
dlHH2Gz.gif

Сделал свой вариант.
В комплекте с OBS (31 по крайней мере идет свой websocket 5.5+), поэтому реализация для него
1746231329659.png
Всё сделано на рендере. В начале скрипта настройки позиции рендера.
Показывает: статус OBS, запись, повтор, время записи.
Имеются кнопки вкл/выкл записи, сохранить повтор, отключить повтор, повторить подключение, которые появляются только когда активен курсор.
Для безопасности сделал, что только один раз проверяет подключение к серверу, чтобы игра не зависла к херам.
Добавлены экспорт функции для добавления в биндер. Использовать:
Lua:
local obs = import('obs.lua')
obs.obs_start_recording()
obs.obs_stop_recording()
obs.obs_toggle_recording()
obs.obs_save_replay()
obs.obs_toggle_replay_buffer()
 

Вложения

  • obs.lua
    8.2 KB · Просмотры: 23

kyrtion

Известный
1,339
508
@Орк
Вот вам первый (или второй) баг))0
1. Ну все ты же знаешь. Это из-за координаты курсора если я запустил игру в оконном режиме, то координаты добавляет "отступ"
Думаю надо получить и сравнить курсор именно в игре
1746249782552.png


2. Если поставить уведомление на трее, то каждый раз когда я записываю или действую - приходит том что я подключил и отключил клиент из-за 1 клика
1746249901237.png

1746249888220.png

3. Лишний вопрос. Почему именно скрипт в UTF-8?
 
  • Нравится
Реакции: Орк

0x18d036

Известный
798
227
dlHH2Gz.gif

Сделал свой вариант.
В комплекте с OBS (31 по крайней мере идет свой websocket 5.5+), поэтому реализация для него
Посмотреть вложение 269192
Всё сделано на рендере. В начале скрипта настройки позиции рендера.
Показывает: статус OBS, запись, повтор, время записи.
Имеются кнопки вкл/выкл записи, сохранить повтор, отключить повтор, повторить подключение, которые появляются только когда активен курсор.
Для безопасности сделал, что только один раз проверяет подключение к серверу, чтобы игра не зависла к херам.
Добавлены экспорт функции для добавления в биндер. Использовать:
Lua:
local obs = import('obs.lua')
obs.obs_start_recording()
obs.obs_stop_recording()
obs.obs_toggle_recording()
obs.obs_save_replay()
obs.obs_toggle_replay_buffer()
Ай легенда, пока не проверил, если у меня стоят горячие клавиши на вкл/выкл буффера/записи, оно обновится и покажет у меня в игре состояние? Просто обычно постоянно включен буффер повтора, и иногда включаю запись, будет нормально работать?
 

kyrtion

Известный
1,339
508
Ай легенда, пока не проверил, если у меня стоят горячие клавиши на вкл/выкл буффера/записи, оно обновится и покажет у меня в игре состояние? Просто обычно постоянно включен буффер повтора, и иногда включаю запись, будет нормально работать?
То есть хочешь сделать чтобы отобразило состояние врублен повтор или нет, и тд?
Напоминает индикаторы на состояние ПК

Форканул и немного сделал для себя
1746259471035.png

серый - неактивный
исправил на двойной клиент
перезагрузить /obsreload
библиотека websocket брал в https://github.com/manoaratefy/samp-obs-record/tree/main/lib
 

Вложения

  • obs.lua
    3.5 KB · Просмотры: 9
Последнее редактирование:
  • Влюблен
  • Нравится
Реакции: Орк и 0x18d036

0x18d036

Известный
798
227
То есть хочешь сделать чтобы отобразило состояние врублен повтор или нет, и тд?
Да, примерно как у шадоуплее, чтобы показывало серым прямоугольником состояние повтора, и если включена запись то кружок зеленый в этом прямоугольнике (это как идея), но так подходит.
Можешь подсказать где в коде скрипта изменить цвет, как по мне лучше сделать темно-серым - не включено, а красным - включено, каждому своё, по этому просто скажи где и в какой строчке изменить цвет для включенного состояния
 

kyrtion

Известный
1,339
508
Да, примерно как у шадоуплее, чтобы показывало серым прямоугольником состояние повтора, и если включена запись то кружок зеленый в этом прямоугольнике (это как идея), но так подходит.
Можешь подсказать где в коде скрипта изменить цвет, как по мне лучше сделать темно-серым - не включено, а красным - включено, каждому своё, по этому просто скажи где и в какой строчке изменить цвет для включенного состояния
1746262539443.png

где маленькие 0xff - это альфа, можешь не трогать, остальное там применяется на RRGGBB (RGB)
 

Улиточка

Известный
587
193
Года 2-3 назад видел тут скрипт он включал запись на obs если тебя дамажили, записывал определенное время и вырубал запись, костыльный шадоуплей, но сейчас такое время что если у тебя нету шадухи или аналога амуде то в обс встроили повтор как в шадухе, тоже постоянно пишет столько скоко тебе надо и сохраняет файл по кнопочке/хоткею.
Но во времена когда подставить игрока при помощи софтов всё равно что в туалет сходить, я и многие другие игроки запись в принципе не вырубают, я лично когда играю у меня фрапс 24/7 включён, по этому считаю данные скрипты юзлесным рудиментом не достойным даже публикации ибо идея мертва в зародыше.
IXRAiW.png
каждый фрапсик по 10-30 часов.
6umd8V.png

vysxBs.png
 

- |2347| -

Известный
Автор темы
402
187
Код, который дает возможность подключаться и управлять OBS через стандартную библиотеку Windows без лагов, как это было через websocket Lua (код не мой, если кто-то захочет, сможет написать нормальный оверлей в игре).

код:
local ffi = require("ffi")
local bit = require("bit")
local vkeys = require("vkeys")

ffi.cdef[[
    typedef uintptr_t SOCKET;
    typedef struct WSADATA {
        uint16_t wVersion, wHighVersion;
        char szDescription[257], szSystemStatus[129];
        unsigned short iMaxSockets, iMaxUdpDg;
        char *lpVendorInfo;
    } WSADATA;
    typedef struct sockaddr_in {
        short sin_family;
        unsigned short sin_port;
        struct { unsigned long s_addr; } sin_addr;
        char sin_zero[8];
    } sockaddr_in;
    typedef struct timeval {
        long tv_sec;
        long tv_usec;
    } timeval;
    typedef struct fd_set {
        unsigned int fd_count;
        SOCKET fd_array[64];
    } fd_set;

    int WSAStartup(uint16_t wVer, WSADATA *lpWSAData);
    SOCKET socket(int af, int type, int protocol);
    int connect(SOCKET s, const struct sockaddr *name, int namelen);
    int ioctlsocket(SOCKET s, long cmd, unsigned long *argp);
    int send(SOCKET s, const char *buf, int len, int flags);
    int recv(SOCKET s, char *buf, int len, int flags);
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const timeval *timeout);
    int WSAGetLastError();
    unsigned short htons(unsigned short hostshort);
    unsigned long inet_addr(const char *cp);
]]

local ws2 = ffi.load("ws2_32")
local FIONBIO = 0x8004667e
local WSAEISCONN = 10056

local function build_ws_frame(payload)
    local len = #payload
    local head = {0x81}
    if len <= 125 then
        table.insert(head, bit.bor(len, 0x80))
    elseif len < 65536 then
        table.insert(head, bit.bor(126, 0x80))
        table.insert(head, bit.rshift(len, 8))
        table.insert(head, bit.band(len, 0xFF))
    end
    local mask = {0x61, 0x72, 0x7A, 0x21}
    for i=1,4 do table.insert(head, mask[i]) end
    local out = {string.char(unpack(head))}
    for i=1, len do
        table.insert(out, string.char(bit.bxor(payload:sub(i,i):byte(), mask[(i-1)%4+1])))
    end
    return table.concat(out)
end

local OBS = { sock = nil, state = "IDLE" }

function OBS:connect(ip, port)
    local wsa = ffi.new("WSADATA")
    ws2.WSAStartup(0x0202, wsa)
    self.sock = ws2.socket(2, 1, 6)
    
    local mode = ffi.new("unsigned long[1]", 1)
    ws2.ioctlsocket(self.sock, FIONBIO, mode)

    local addr = ffi.new("sockaddr_in")
    addr.sin_family = 2
    addr.sin_port = ws2.htons(port)
    addr.sin_addr.s_addr = ws2.inet_addr(ip)

    ws2.connect(self.sock, ffi.cast("struct sockaddr*", addr), ffi.sizeof(addr))
    self.state = "CONNECTING"
    print("[OBS] Подключение к " .. ip .. ":" .. port)
end

function OBS:SendRaw(payload)
    if self.sock and self.state == "READY" then
        local frame = build_ws_frame(payload)
        ws2.send(self.sock, frame, #frame, 0)
    else
        print("[OBS] Ошибка: Соединение еще не готово")
    end
end

function OBS:update()
    if not self.sock then return end

    if self.state == "CONNECTING" then
        local fds = ffi.new("fd_set")
        fds.fd_count = 1
        fds.fd_array[0] = self.sock
        local tv = ffi.new("timeval", {0, 0})
        if ws2.select(0, nil, fds, nil, tv) > 0 then
            local key = "dGhlIHNhbXBsZSBub25jZQ=="
            local req = "GET / HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: "..key.."\r\nSec-WebSocket-Version: 13\r\n\r\n"
            ws2.send(self.sock, req, #req, 0)
            self.state = "HANDSHAKE_SENT"
            print("[OBS] TCP OK. Handshake отправлен.")
        end
        return
    end

    local fds = ffi.new("fd_set")
    fds.fd_count = 1
    fds.fd_array[0] = self.sock
    local tv = ffi.new("timeval", {0, 0})
    
    if ws2.select(0, fds, nil, nil, tv) > 0 then
        local buf = ffi.new("char[8192]")
        local len = ws2.recv(self.sock, buf, 8192, 0)
        if len > 0 then
            local raw = ffi.string(buf, len)
            
            if self.state == "HANDSHAKE_SENT" then
                if raw:find("101") or raw:find("Upgrade") then
                    self.state = "WAIT_HELLO"
                    print("[OBS] Handshake принят сервером.")
                    if raw:find('"op":0') then
                        self:identify()
                    end
                end
            elseif self.state == "WAIT_HELLO" then
                if raw:find('"op":0') then
                    self:identify()
                end
            end
        elseif len == 0 then
            self.state = "IDLE"
            print("[OBS] Соединение закрыто.")
        end
    end
end

function OBS:identify()
    local id_msg = [[{"op":1,"d":{"rpcVersion":1}}]]
    local frame = build_ws_frame(id_msg)
    ws2.send(self.sock, frame, #frame, 0)
    self.state = "READY"
    print("{00FF00}[OBS] Авторизация пройдена!")
end

function main()
    while not isSampAvailable() do wait(100) end
    
    OBS:connect("127.0.0.1", 4455)

        while true do wait(0)
            OBS:update()

            if isKeyJustPressed(vkeys.VK_NUMPAD1) then
                print("[OBS] Запрос: Старт записи")
                OBS:SendRaw('{"op":6,"d":{"requestType":"StartRecord","requestId":"manual_start"}}')
            end

            if isKeyJustPressed(vkeys.VK_NUMPAD2) then
                print("[OBS] Запрос: Стоп записи")
                OBS:SendRaw('{"op":6,"d":{"requestType":"StopRecord","requestId":"manual_stop"}}')
            end
        end
    wait(-1)
end

1
 
Последнее редактирование:
  • Нравится
Реакции: Орк