fix: managed playback overlay lifecycle for launcher-owned sessions

- Remove --background from launcher-owned mpv starts; quit only non-tray/non-background managed sessions
- Defer autoplay-ready signal until overlay window content is loaded; retry after flush
- Retry socket availability before auto-starting overlay (up to 25 attempts, 200ms apart)
- Extract warm tokenization signal into autoplay-tokenization-warm-release with stale-media guard
- Queue second-instance commands until app ready runtime completes
- Guard globalShortcut cleanup with isAppReady check to avoid pre-ready crash
- Recognize "osx" as a macOS platform alias in Lua environment detection
This commit is contained in:
2026-05-19 20:56:17 -07:00
parent 1105b18a5a
commit 167004b2c9
24 changed files with 606 additions and 52 deletions
+8 -2
View File
@@ -18,8 +18,14 @@ function M.create(ctx)
local function is_macos()
local platform = mp.get_property("platform") or ""
if platform == "macos" or platform == "darwin" then
return true
if platform ~= "" then
local normalized = platform:lower()
if normalized == "macos" or normalized == "darwin" or normalized == "osx" then
return true
end
if normalized == "windows" or normalized == "win32" or normalized == "linux" then
return false
end
end
local ostype = os.getenv("OSTYPE") or ""
return ostype:find("darwin") ~= nil
+58 -17
View File
@@ -1,5 +1,8 @@
local M = {}
local AUTO_START_SOCKET_RETRY_DELAY_SECONDS = 0.2
local AUTO_START_SOCKET_RETRY_MAX_ATTEMPTS = 25
function M.create(ctx)
local mp = ctx.mp
local opts = ctx.opts
@@ -52,6 +55,11 @@ function M.create(ctx)
return options_helper.coerce_bool(raw_auto_start, false)
end
local function next_auto_start_retry_generation()
state.auto_start_retry_generation = (state.auto_start_retry_generation or 0) + 1
return state.auto_start_retry_generation
end
local function rearm_managed_subtitle_defaults()
if not process.has_matching_mpv_ipc_socket(opts.socket_path) then
return false
@@ -63,13 +71,58 @@ function M.create(ctx)
return true
end
local function start_overlay_when_socket_ready(generation, media_identity, same_media_loaded, attempt)
if generation ~= state.auto_start_retry_generation then
return
end
if media_identity ~= nil and state.current_media_identity ~= media_identity then
return
end
if not resolve_auto_start_enabled() then
schedule_aniskip_fetch("file-loaded", 0)
return
end
local has_matching_socket = rearm_managed_subtitle_defaults()
if not has_matching_socket then
if attempt < AUTO_START_SOCKET_RETRY_MAX_ATTEMPTS then
mp.add_timeout(AUTO_START_SOCKET_RETRY_DELAY_SECONDS, function()
start_overlay_when_socket_ready(generation, media_identity, same_media_loaded, attempt + 1)
end)
return
end
subminer_log(
"info",
"lifecycle",
"Skipping auto-start: input-ipc-server does not match configured socket_path"
)
schedule_aniskip_fetch("file-loaded", 0)
return
end
process.start_overlay({
auto_start_trigger = true,
socket_path = opts.socket_path,
rearm_pause_until_ready = not same_media_loaded,
})
-- Give the overlay process a moment to initialize before querying AniSkip.
schedule_aniskip_fetch("overlay-start", 0.8)
end
local function on_file_loaded()
local media_identity = resolve_media_identity()
local retry_generation = next_auto_start_retry_generation()
local previous_media_identity = state.current_media_identity
local same_media_reload = (
media_identity ~= nil
and state.pending_reload_media_identity ~= nil
and media_identity == state.pending_reload_media_identity
)
local same_media_loaded = (
media_identity ~= nil
and previous_media_identity ~= nil
and media_identity == previous_media_identity
)
state.pending_reload_media_identity = nil
state.current_media_identity = media_identity
@@ -92,32 +145,18 @@ function M.create(ctx)
if not preserve_active_auto_start_gate then
process.disarm_auto_play_ready_gate()
end
has_matching_socket = rearm_managed_subtitle_defaults()
if should_auto_start then
if not has_matching_socket then
subminer_log(
"info",
"lifecycle",
"Skipping auto-start: input-ipc-server does not match configured socket_path"
)
schedule_aniskip_fetch("file-loaded", 0)
return
end
process.start_overlay({
auto_start_trigger = true,
socket_path = opts.socket_path,
})
-- Give the overlay process a moment to initialize before querying AniSkip.
schedule_aniskip_fetch("overlay-start", 0.8)
start_overlay_when_socket_ready(retry_generation, media_identity, same_media_loaded, 1)
return
end
rearm_managed_subtitle_defaults()
schedule_aniskip_fetch("file-loaded", 0)
end
local function on_shutdown()
next_auto_start_retry_generation()
aniskip.clear_aniskip_state()
hover.clear_hover_overlay()
process.disarm_auto_play_ready_gate()
@@ -139,6 +178,8 @@ function M.create(ctx)
state.pending_reload_media_identity = state.current_media_identity or resolve_media_identity()
return
end
next_auto_start_retry_generation()
state.current_media_identity = nil
state.pending_reload_media_identity = nil
if state.overlay_running and reason ~= "quit" then
process.hide_visible_overlay()
+9 -2
View File
@@ -207,7 +207,6 @@ function M.create(ctx)
end
if action == "start" then
table.insert(args, "--background")
table.insert(args, "--managed-playback")
local backend = resolve_backend(overrides.backend)
@@ -411,7 +410,15 @@ function M.create(ctx)
if overrides.auto_start_trigger == true then
subminer_log("debug", "process", "Auto-start ignored because overlay is already running")
local socket_path = overrides.socket_path or opts.socket_path
if not state.auto_play_ready_gate_armed then
local should_pause_until_ready = (
overrides.rearm_pause_until_ready == true
and resolve_visible_overlay_startup()
and resolve_pause_until_ready()
and has_matching_mpv_ipc_socket(socket_path)
)
if should_pause_until_ready then
arm_auto_play_ready_gate()
elseif not state.auto_play_ready_gate_armed then
disarm_auto_play_ready_gate()
end
local visibility_action = resolve_visible_overlay_startup()
+1
View File
@@ -37,6 +37,7 @@ function M.new()
force_ready_overlay_restore = false,
current_media_identity = nil,
pending_reload_media_identity = nil,
auto_start_retry_generation = 0,
session_binding_generation = 0,
session_binding_names = {},
session_numeric_binding_names = {},