mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
fix(jellyfin): subtitle timing, resume progress, and overlay sync
- Add per-stream subtitle delay persistence and auto timeline-offset correction - Strip server-selected subtitle stream from mpv load URL; suppress plugin subtitle rearm and auto-start during app-managed preload - Fix resume position lost when mpv resets on stop; use last known position for final progress/stopped reports - Keep Play vs Resume distinct to avoid early seek race on normal play - Fix discovery resume when remote play sends StartPositionTicks=0 despite saved progress - Deduplicate show/hide overlay commands using recorded visibility state - Rewrite docs-site Jellyfin page around cast-to-device UX
This commit is contained in:
@@ -85,6 +85,10 @@ function M.create(ctx)
|
||||
if not has_matching_subminer_socket() then
|
||||
return false
|
||||
end
|
||||
if state.skip_managed_subtitle_rearm_once then
|
||||
state.skip_managed_subtitle_rearm_once = false
|
||||
return true
|
||||
end
|
||||
mp.set_property_native("sub-auto", "fuzzy")
|
||||
mp.set_property_native("sid", "auto")
|
||||
mp.set_property_native("secondary-sid", "auto")
|
||||
@@ -179,12 +183,21 @@ function M.create(ctx)
|
||||
state.pending_reload_reason = nil
|
||||
state.current_media_identity = media_identity
|
||||
state.current_media_title = media_title
|
||||
if state.app_managed_playback_pending then
|
||||
state.app_managed_playback_pending = false
|
||||
state.app_managed_playback_active = true
|
||||
elseif new_media_loaded then
|
||||
state.app_managed_playback_active = false
|
||||
end
|
||||
if new_media_loaded then
|
||||
state.suppress_ready_overlay_restore = false
|
||||
end
|
||||
|
||||
if same_media_reload then
|
||||
subminer_log("debug", "lifecycle", "Skipping startup lifecycle for same-media mpv reload")
|
||||
if state.app_managed_playback_active then
|
||||
return
|
||||
end
|
||||
if
|
||||
state.overlay_running
|
||||
and not state.suppress_ready_overlay_restore
|
||||
@@ -208,6 +221,11 @@ function M.create(ctx)
|
||||
process.disarm_auto_play_ready_gate()
|
||||
end
|
||||
|
||||
if state.app_managed_playback_active then
|
||||
subminer_log("debug", "lifecycle", "Skipping plugin auto-start for app-managed subtitle preload")
|
||||
return
|
||||
end
|
||||
|
||||
if should_auto_start then
|
||||
start_overlay_when_socket_ready(retry_generation, media_identity, same_media_loaded, 1)
|
||||
return
|
||||
@@ -227,6 +245,8 @@ function M.create(ctx)
|
||||
state.pending_reload_media_identity = nil
|
||||
state.pending_reload_media_title = nil
|
||||
state.pending_reload_reason = nil
|
||||
state.app_managed_playback_pending = false
|
||||
state.app_managed_playback_active = false
|
||||
end
|
||||
|
||||
local function register_lifecycle_hooks()
|
||||
@@ -252,6 +272,7 @@ function M.create(ctx)
|
||||
state.pending_reload_media_identity = nil
|
||||
state.pending_reload_media_title = nil
|
||||
state.pending_reload_reason = nil
|
||||
state.app_managed_playback_active = false
|
||||
if state.overlay_running and reason ~= "quit" then
|
||||
process.hide_visible_overlay()
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ function M.create(ctx)
|
||||
local aniskip = ctx.aniskip
|
||||
local hover = ctx.hover
|
||||
local ui = ctx.ui
|
||||
local state = ctx.state
|
||||
|
||||
local function register_script_messages()
|
||||
mp.register_script_message("subminer-start", function(...)
|
||||
@@ -23,6 +24,10 @@ function M.create(ctx)
|
||||
mp.register_script_message("subminer-visible-overlay-shown", function()
|
||||
process.record_visible_overlay_visibility(true)
|
||||
end)
|
||||
mp.register_script_message("subminer-managed-subtitles-loading", function()
|
||||
state.skip_managed_subtitle_rearm_once = true
|
||||
state.app_managed_playback_pending = true
|
||||
end)
|
||||
mp.register_script_message("subminer-menu", function()
|
||||
ui.show_menu()
|
||||
end)
|
||||
|
||||
+50
-13
@@ -102,6 +102,46 @@ function M.create(ctx)
|
||||
state.suppress_ready_overlay_restore = true
|
||||
end
|
||||
|
||||
local function record_start_visibility_args(args)
|
||||
for _, arg in ipairs(args) do
|
||||
if arg == "--show-visible-overlay" then
|
||||
record_visible_overlay_action("show-visible-overlay")
|
||||
return
|
||||
end
|
||||
if arg == "--hide-visible-overlay" then
|
||||
record_visible_overlay_action("hide-visible-overlay")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function should_run_visibility_action(action)
|
||||
if action == "show-visible-overlay" and state.visible_overlay_requested == true then
|
||||
return false
|
||||
end
|
||||
if action == "hide-visible-overlay" and state.visible_overlay_requested == false then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function run_visibility_action_if_needed(action, overrides, callback)
|
||||
if action == nil then
|
||||
if callback then
|
||||
callback(true)
|
||||
end
|
||||
return
|
||||
end
|
||||
if not should_run_visibility_action(action) then
|
||||
subminer_log("debug", "process", "Skipping duplicate visible overlay action: " .. tostring(action))
|
||||
if callback then
|
||||
callback(true)
|
||||
end
|
||||
return
|
||||
end
|
||||
run_control_command_async(action, overrides, callback)
|
||||
end
|
||||
|
||||
local function should_ignore_duplicate_visible_overlay_toggle()
|
||||
if type(mp.get_time) ~= "function" then
|
||||
return false
|
||||
@@ -247,7 +287,7 @@ function M.create(ctx)
|
||||
state.suppress_ready_overlay_restore = false
|
||||
end
|
||||
if state.overlay_running and (force_ready_overlay_restore or resolve_visible_overlay_startup()) then
|
||||
run_control_command_async("show-visible-overlay", {
|
||||
run_visibility_action_if_needed("show-visible-overlay", {
|
||||
socket_path = opts.socket_path,
|
||||
})
|
||||
end
|
||||
@@ -481,12 +521,10 @@ function M.create(ctx)
|
||||
disarm_auto_play_ready_gate()
|
||||
end
|
||||
local visibility_action = resolve_auto_start_visibility_action()
|
||||
if visibility_action ~= nil then
|
||||
run_control_command_async(visibility_action, {
|
||||
socket_path = socket_path,
|
||||
log_level = overrides.log_level,
|
||||
})
|
||||
end
|
||||
run_visibility_action_if_needed(visibility_action, {
|
||||
socket_path = socket_path,
|
||||
log_level = overrides.log_level,
|
||||
})
|
||||
return
|
||||
end
|
||||
subminer_log("info", "process", "Overlay already running")
|
||||
@@ -526,6 +564,7 @@ function M.create(ctx)
|
||||
state.overlay_running = true
|
||||
|
||||
local command = build_subprocess_command(args)
|
||||
record_start_visibility_args(args)
|
||||
mp.command_native_async({
|
||||
name = "subprocess",
|
||||
args = command.args,
|
||||
@@ -552,12 +591,10 @@ function M.create(ctx)
|
||||
|
||||
if overrides.auto_start_trigger == true then
|
||||
local visibility_action = resolve_auto_start_visibility_action()
|
||||
if visibility_action ~= nil then
|
||||
run_control_command_async(visibility_action, {
|
||||
socket_path = socket_path,
|
||||
log_level = overrides.log_level,
|
||||
})
|
||||
end
|
||||
run_visibility_action_if_needed(visibility_action, {
|
||||
socket_path = socket_path,
|
||||
log_level = overrides.log_level,
|
||||
})
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
@@ -42,6 +42,8 @@ function M.new()
|
||||
pending_reload_media_identity = nil,
|
||||
pending_reload_media_title = nil,
|
||||
pending_reload_reason = nil,
|
||||
app_managed_playback_pending = false,
|
||||
app_managed_playback_active = false,
|
||||
auto_start_retry_generation = 0,
|
||||
session_binding_generation = 0,
|
||||
session_binding_names = {},
|
||||
|
||||
Reference in New Issue
Block a user