obs = obslua ffi = require("ffi") ------------------------------------------------------------ -- Windows API ------------------------------------------------------------ ffi.cdef[[ typedef void* HANDLE; typedef const char* LPCSTR; typedef unsigned long DWORD; typedef struct { DWORD dwFileAttributes; DWORD ftCreationTime_dwLowDateTime; DWORD ftCreationTime_dwHighDateTime; DWORD ftLastAccessTime_dwLowDateTime; DWORD ftLastAccessTime_dwHighDateTime; DWORD ftLastWriteTime_dwLowDateTime; DWORD ftLastWriteTime_dwHighDateTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; char cFileName[260]; char cAlternateFileName[14]; } WIN32_FIND_DATAA; HANDLE FindFirstFileA(LPCSTR lpFileName, WIN32_FIND_DATAA *lpFindFileData); int FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData); int FindClose(HANDLE hFindFile); ]] INVALID = ffi.cast("HANDLE", -1) ------------------------------------------------------------ -- CONFIG ------------------------------------------------------------ source_name = "" folder_path = "" playlist = {} index = 1 active = false ------------------------------------------------------------ -- FILE LIST ------------------------------------------------------------ function list_files(path) local files = {} if path == "" then return files end local search = path .. "\\*" local data = ffi.new("WIN32_FIND_DATAA[1]") local handle = ffi.C.FindFirstFileA(search, data) if handle == INVALID then return files end repeat local name = ffi.string(data[0].cFileName) if name ~= "." and name ~= ".." then if name:match("%.mp4") or name:match("%.mov") or name:match("%.mkv") then table.insert(files, path .. "\\" .. name) end end until ffi.C.FindNextFileA(handle, data) == 0 ffi.C.FindClose(handle) table.sort(files) return files end ------------------------------------------------------------ -- PLAYER ------------------------------------------------------------ function play_current() if not active then return end if #playlist == 0 then return end local file = playlist[index] local source = obs.obs_get_source_by_name(source_name) if source == nil then return end local settings = obs.obs_source_get_settings(source) obs.obs_data_set_string(settings, "local_file", file) obs.obs_source_update(source, settings) obs.obs_data_release(settings) obs.obs_source_release(source) if obs.obs_source_media_restart then obs.obs_source_media_restart(source) end end function next_video() index = index + 1 if index > #playlist then index = 1 end play_current() end ------------------------------------------------------------ -- EVENTS ------------------------------------------------------------ function media_ended(cd) next_video() end function source_active(cd) active = true playlist = list_files(folder_path) index = 1 play_current() end function source_inactive(cd) active = false end ------------------------------------------------------------ -- CONNECT SOURCE ------------------------------------------------------------ function connect_source() local source = obs.obs_get_source_by_name(source_name) if source == nil then return end local sh = obs.obs_source_get_signal_handler(source) obs.signal_handler_connect(sh, "media_ended", media_ended) obs.signal_handler_connect(sh, "activate", source_active) obs.signal_handler_connect(sh, "deactivate", source_inactive) obs.obs_source_release(source) end ------------------------------------------------------------ -- OBS EVENTS ------------------------------------------------------------ function on_event(event) if event == obs.OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED then playlist = list_files(folder_path) end end ------------------------------------------------------------ -- UI ------------------------------------------------------------ function script_properties() local props = obs.obs_properties_create() -- ✅ INFO BOX obs.obs_properties_add_text( props, "info", "MEDIA SOURCE CONFIGURATION (IMPORTANT)\n\n" .. "Configure your selected Media Source exactly as follows:\n\n" .. "Close file when inactive: OFF\n" .. "Loop: OFF\n" .. "Restart playback on activate: OFF\n\n" .. "IMPORTANT:\n" .. "- Use ONLY Media Source (NOT VLC Source)\n" .. "- Playback is fully controlled by the script", obs.OBS_TEXT_MULTILINE ) -- ✅ SOURCE DROPDOWN (ONLY MEDIA SOURCE) local list = obs.obs_properties_add_list( props, "source_name", "Replay Media Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ) local sources = obs.obs_enum_sources() if sources ~= nil then for _, src in ipairs(sources) do local id = obs.obs_source_get_id(src) local name = obs.obs_source_get_name(src) -- ✅ filtro SOLO Media Source if id == "ffmpeg_source" then obs.obs_property_list_add_string(list, name, name) end end obs.source_list_release(sources) end -- ✅ FOLDER obs.obs_properties_add_path( props, "folder_path", "Replay Folder", obs.OBS_PATH_DIRECTORY, "", nil ) return props end function script_update(settings) source_name = obs.obs_data_get_string(settings, "source_name") folder_path = obs.obs_data_get_string(settings, "folder_path") connect_source() playlist = list_files(folder_path) end function script_load(settings) script_update(settings) obs.obs_frontend_add_event_callback(on_event) end