--[[ ToneCast for OBS Studio Adds (or updates) a Browser Source that acts as a ToneCast cast screen. Install: 1. Tools → Scripts → "+" → select this file 2. Enter your account slug + screen number 3. Click "Add / Update ToneCast Source" The cast URL looks like: https://tonecast.net/cast.html?a=YOUR_SLUG&screen=1&obs=1 ]] obs = obslua local PROP_BASE = "base_url" local PROP_SLUG = "account_slug" local PROP_SCREEN = "screen_id" local PROP_WIDTH = "width" local PROP_HEIGHT = "height" local PROP_FPS = "fps" local PROP_SOURCE = "source_name" local PROP_ADD = "btn_add" local DEFAULT_BASE = "https://tonecast.net" local DEFAULT_SOURCE = "ToneCast Screen" local function trim(s) return (tostring(s or ""):gsub("^%s+", ""):gsub("%s+$", "")) end local function normalize_base(url) url = trim(url) if url == "" then url = DEFAULT_BASE end url = url:gsub("/+$", "") return url end local function build_cast_url(base, slug, screen) base = normalize_base(base) slug = trim(slug) screen = tonumber(screen) or 1 if screen < 1 then screen = 1 end if slug == "" then return nil, "Account slug is required (Dashboard → Settings → your public slug)." end return string.format("%s/cast.html?a=%s&screen=%d&obs=1", base, slug, screen), nil end local function find_source_by_name(name) local sources = obs.obs_enum_sources() if sources == nil then return nil end local found = nil for _, source in ipairs(sources) do if obs.obs_source_get_name(source) == name then found = source obs.obs_source_addref(source) break end end obs.source_list_release(sources) return found end local function apply_browser_settings(settings, url, width, height, fps) obs.obs_data_set_string(settings, "url", url) obs.obs_data_set_int(settings, "width", width) obs.obs_data_set_int(settings, "height", height) obs.obs_data_set_int(settings, "fps", fps) obs.obs_data_set_bool(settings, "shutdown", true) obs.obs_data_set_bool(settings, "restart_when_active", true) obs.obs_data_set_bool(settings, "reroute_audio", true) -- Hide scrollbars / fill the source cleanly obs.obs_data_set_string(settings, "css", "body { margin: 0px; overflow: hidden; background: #000; }") end -- Script settings are stored in `script_settings` after script_update. local script_settings = nil function script_description() return [[ToneCast Screen for OBS Creates a Browser Source that connects to your ToneCast cast page — the same screen used by Firestick, Pi, or browser cast clients. 1. Set Base URL (usually https://tonecast.net) 2. Enter your account slug (from the ToneCast dashboard) 3. Choose screen number 4. Click Add / Update ToneCast Source Requires OBS builds with Browser Source (official OBS packages include it).]] end function script_properties() local props = obs.obs_properties_create() obs.obs_properties_add_text(props, PROP_BASE, "Base URL", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, PROP_SLUG, "Account slug", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_int(props, PROP_SCREEN, "Screen number", 1, 99, 1) obs.obs_properties_add_int(props, PROP_WIDTH, "Width", 320, 7680, 1) obs.obs_properties_add_int(props, PROP_HEIGHT, "Height", 240, 4320, 1) obs.obs_properties_add_int(props, PROP_FPS, "FPS", 1, 60, 1) obs.obs_properties_add_text(props, PROP_SOURCE, "Source name", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_button(props, PROP_ADD, "Add / Update ToneCast Source", function(props, prop) if script_settings == nil then print("[ToneCast] Script settings not ready yet.") return true end local base = obs.obs_data_get_string(script_settings, PROP_BASE) local slug = obs.obs_data_get_string(script_settings, PROP_SLUG) local screen = obs.obs_data_get_int(script_settings, PROP_SCREEN) local width = obs.obs_data_get_int(script_settings, PROP_WIDTH) local height = obs.obs_data_get_int(script_settings, PROP_HEIGHT) local fps = obs.obs_data_get_int(script_settings, PROP_FPS) local name = trim(obs.obs_data_get_string(script_settings, PROP_SOURCE)) if name == "" then name = DEFAULT_SOURCE end if width < 1 then width = 1920 end if height < 1 then height = 1080 end if fps < 1 then fps = 30 end local url, err = build_cast_url(base, slug, screen) if not url then print("[ToneCast] " .. tostring(err)) return true end local existing = find_source_by_name(name) if existing ~= nil then local cur = obs.obs_source_get_settings(existing) apply_browser_settings(cur, url, width, height, fps) obs.obs_source_update(existing, cur) obs.obs_data_release(cur) obs.obs_source_release(existing) print("[ToneCast] Updated source \"" .. name .. "\" → " .. url) return true end local src_settings = obs.obs_data_create() apply_browser_settings(src_settings, url, width, height, fps) local source = obs.obs_source_create("browser_source", name, src_settings, nil) obs.obs_data_release(src_settings) if source == nil then print("[ToneCast] Failed to create browser_source. Is Browser Source available in this OBS build?") return true end local scene_source = obs.obs_frontend_get_current_scene() if scene_source ~= nil then local scene = obs.obs_scene_from_source(scene_source) if scene ~= nil then obs.obs_scene_add(scene, source) end obs.obs_source_release(scene_source) end obs.obs_source_release(source) print("[ToneCast] Added source \"" .. name .. "\" → " .. url) return true end) return props end function script_defaults(settings) obs.obs_data_set_default_string(settings, PROP_BASE, DEFAULT_BASE) obs.obs_data_set_default_string(settings, PROP_SLUG, "") obs.obs_data_set_default_int(settings, PROP_SCREEN, 1) obs.obs_data_set_default_int(settings, PROP_WIDTH, 1920) obs.obs_data_set_default_int(settings, PROP_HEIGHT, 1080) obs.obs_data_set_default_int(settings, PROP_FPS, 30) obs.obs_data_set_default_string(settings, PROP_SOURCE, DEFAULT_SOURCE) end function script_update(settings) script_settings = settings end function script_load(settings) script_settings = settings end