mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-11 15:13:33 -07:00
feat(notifications): add overlay notifications with position config (#110)
This commit is contained in:
@@ -104,6 +104,14 @@ function M.create(ctx)
|
||||
return options_helper.coerce_bool(raw_visible_overlay, false)
|
||||
end
|
||||
|
||||
local function resolve_overlay_loading_osd_enabled()
|
||||
local raw_overlay_loading_osd = opts.overlay_loading_osd
|
||||
if raw_overlay_loading_osd == nil then
|
||||
raw_overlay_loading_osd = opts["overlay-loading-osd"]
|
||||
end
|
||||
return options_helper.coerce_bool(raw_overlay_loading_osd, 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
|
||||
@@ -143,6 +151,14 @@ function M.create(ctx)
|
||||
and not (state.overlay_running and state.auto_play_ready_signal_seen == true)
|
||||
end
|
||||
|
||||
local function should_show_overlay_loading_osd()
|
||||
return (
|
||||
resolve_overlay_loading_osd_enabled()
|
||||
or (resolve_auto_start_enabled() and resolve_auto_start_visible_overlay_enabled())
|
||||
)
|
||||
and not state.suppress_ready_overlay_restore
|
||||
end
|
||||
|
||||
local function start_overlay_when_socket_ready(generation, media_identity, same_media_loaded, attempt)
|
||||
if generation ~= state.auto_start_retry_generation then
|
||||
return
|
||||
@@ -169,6 +185,7 @@ function M.create(ctx)
|
||||
.. process.describe_mpv_ipc_socket_match(opts.socket_path)
|
||||
.. ")"
|
||||
)
|
||||
process.stop_overlay_loading_osd()
|
||||
return
|
||||
end
|
||||
|
||||
@@ -180,6 +197,9 @@ function M.create(ctx)
|
||||
end
|
||||
|
||||
local function on_start_file()
|
||||
if should_show_overlay_loading_osd() then
|
||||
process.start_overlay_loading_osd()
|
||||
end
|
||||
if state.pending_reload_media_identity ~= nil then
|
||||
local media_identity = resolve_media_identity()
|
||||
if media_identity ~= nil and media_identity ~= state.pending_reload_media_identity then
|
||||
@@ -233,6 +253,7 @@ function M.create(ctx)
|
||||
end
|
||||
|
||||
if same_media_reload then
|
||||
process.stop_overlay_loading_osd()
|
||||
subminer_log("debug", "lifecycle", "Skipping startup lifecycle for same-media mpv reload")
|
||||
if state.app_managed_playback_active then
|
||||
return
|
||||
@@ -260,6 +281,7 @@ function M.create(ctx)
|
||||
end
|
||||
|
||||
if state.app_managed_playback_active then
|
||||
process.stop_overlay_loading_osd()
|
||||
subminer_log("debug", "lifecycle", "Skipping plugin auto-start for app-managed subtitle preload")
|
||||
return
|
||||
end
|
||||
@@ -276,6 +298,7 @@ function M.create(ctx)
|
||||
next_auto_start_retry_generation()
|
||||
hover.clear_hover_overlay()
|
||||
process.disarm_auto_play_ready_gate()
|
||||
process.stop_overlay_loading_osd()
|
||||
clear_pending_visible_overlay_hide()
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.current_media_identity = nil
|
||||
@@ -295,6 +318,7 @@ function M.create(ctx)
|
||||
hover.clear_hover_overlay()
|
||||
end)
|
||||
mp.register_event("end-file", function(event)
|
||||
process.stop_overlay_loading_osd()
|
||||
process.disarm_auto_play_ready_gate()
|
||||
hover.clear_hover_overlay()
|
||||
local reason = type(event) == "table" and event.reason or nil
|
||||
|
||||
@@ -43,8 +43,8 @@ function M.create(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
local function show_osd(message)
|
||||
if opts.osd_messages then
|
||||
local function show_osd(message, options)
|
||||
if opts.osd_messages or (options and options.force == true) then
|
||||
local payload = "SubMiner: " .. message
|
||||
local sent = false
|
||||
if type(mp.osd_message) == "function" then
|
||||
|
||||
@@ -2,6 +2,7 @@ local M = {}
|
||||
|
||||
function M.create(ctx)
|
||||
local mp = ctx.mp
|
||||
local opts = ctx.opts
|
||||
local process = ctx.process
|
||||
local hover = ctx.hover
|
||||
local ui = ctx.ui
|
||||
@@ -42,6 +43,9 @@ function M.create(ctx)
|
||||
mp.register_script_message("subminer-autoplay-ready", function()
|
||||
process.notify_auto_play_ready()
|
||||
end)
|
||||
mp.register_script_message("subminer-overlay-loading-ready", function()
|
||||
process.stop_overlay_loading_osd()
|
||||
end)
|
||||
mp.register_script_message(hover.HOVER_MESSAGE_NAME, function(payload_json)
|
||||
hover.handle_hover_message(payload_json)
|
||||
end)
|
||||
@@ -49,7 +53,9 @@ function M.create(ctx)
|
||||
hover.handle_hover_message(payload_json)
|
||||
end)
|
||||
mp.register_script_message("subminer-stats-toggle", function()
|
||||
mp.osd_message("Stats: press ` (backtick) in overlay", 3)
|
||||
if opts.osd_messages then
|
||||
mp.osd_message("Stats: press ` (backtick) in overlay", 3)
|
||||
end
|
||||
end)
|
||||
mp.register_script_message("subminer-reload-session-bindings", function()
|
||||
ctx.session_bindings.reload_bindings()
|
||||
|
||||
@@ -31,9 +31,10 @@ function M.load(options_lib, default_socket_path)
|
||||
backend = "auto",
|
||||
auto_start = false,
|
||||
auto_start_visible_overlay = false,
|
||||
overlay_loading_osd = false,
|
||||
auto_start_pause_until_ready = true,
|
||||
auto_start_pause_until_ready_owns_initial_pause = false,
|
||||
auto_start_pause_until_ready_timeout_seconds = 15,
|
||||
auto_start_pause_until_ready_timeout_seconds = 30,
|
||||
osd_messages = true,
|
||||
log_level = "info",
|
||||
}
|
||||
|
||||
+116
-12
@@ -4,9 +4,12 @@ local OVERLAY_START_RETRY_DELAY_SECONDS = 0.2
|
||||
local OVERLAY_START_MAX_ATTEMPTS = 6
|
||||
local OVERLAY_RESTART_PING_RETRY_DELAY_SECONDS = 0.2
|
||||
local OVERLAY_RESTART_PING_MAX_ATTEMPTS = 20
|
||||
local OVERLAY_LOADING_OSD_PREFIX = "Overlay loading "
|
||||
local OVERLAY_LOADING_OSD_FRAMES = { "|", "/", "-", "\\" }
|
||||
local OVERLAY_LOADING_OSD_REFRESH_SECONDS = 0.18
|
||||
local AUTO_PLAY_READY_LOADING_OSD = "Loading subtitle tokenization..."
|
||||
local AUTO_PLAY_READY_READY_OSD = "Subtitle tokenization ready"
|
||||
local DEFAULT_AUTO_PLAY_READY_TIMEOUT_SECONDS = 15
|
||||
local DEFAULT_AUTO_PLAY_READY_TIMEOUT_SECONDS = 30
|
||||
local DUPLICATE_VISIBLE_OVERLAY_TOGGLE_SECONDS = 0.25
|
||||
|
||||
function M.create(ctx)
|
||||
@@ -53,6 +56,14 @@ function M.create(ctx)
|
||||
return options_helper.coerce_bool(raw_pause_until_ready, false)
|
||||
end
|
||||
|
||||
local function resolve_osd_messages_enabled()
|
||||
local raw_osd_messages = opts.osd_messages
|
||||
if raw_osd_messages == nil then
|
||||
raw_osd_messages = opts["osd-messages"]
|
||||
end
|
||||
return options_helper.coerce_bool(raw_osd_messages, false)
|
||||
end
|
||||
|
||||
local function resolve_pause_until_ready_owns_initial_pause()
|
||||
local raw_owns_initial_pause = opts.auto_start_pause_until_ready_owns_initial_pause
|
||||
if raw_owns_initial_pause == nil then
|
||||
@@ -246,6 +257,42 @@ function M.create(ctx)
|
||||
state.auto_play_ready_osd_timer = nil
|
||||
end
|
||||
|
||||
local function clear_overlay_loading_osd_timer()
|
||||
local timer = state.overlay_loading_osd_timer
|
||||
if timer and timer.kill then
|
||||
timer:kill()
|
||||
end
|
||||
state.overlay_loading_osd_timer = nil
|
||||
end
|
||||
|
||||
local function stop_overlay_loading_osd()
|
||||
state.overlay_loading_osd_active = false
|
||||
state.overlay_loading_osd_frame = 1
|
||||
clear_overlay_loading_osd_timer()
|
||||
end
|
||||
|
||||
local function start_overlay_loading_osd()
|
||||
if state.overlay_loading_osd_active then
|
||||
return
|
||||
end
|
||||
state.overlay_loading_osd_active = true
|
||||
state.overlay_loading_osd_frame = 1
|
||||
local function show_next_overlay_loading_frame()
|
||||
local frame_index = state.overlay_loading_osd_frame or 1
|
||||
local frame = OVERLAY_LOADING_OSD_FRAMES[frame_index] or OVERLAY_LOADING_OSD_FRAMES[1]
|
||||
show_osd(OVERLAY_LOADING_OSD_PREFIX .. frame, { force = true })
|
||||
state.overlay_loading_osd_frame = (frame_index % #OVERLAY_LOADING_OSD_FRAMES) + 1
|
||||
end
|
||||
show_next_overlay_loading_frame()
|
||||
if type(mp.add_periodic_timer) == "function" then
|
||||
state.overlay_loading_osd_timer = mp.add_periodic_timer(OVERLAY_LOADING_OSD_REFRESH_SECONDS, function()
|
||||
if state.overlay_loading_osd_active then
|
||||
show_next_overlay_loading_frame()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function disarm_auto_play_ready_gate(options)
|
||||
local should_resume = options == nil or options.resume_playback ~= false
|
||||
local was_armed = state.auto_play_ready_gate_armed
|
||||
@@ -264,8 +311,11 @@ function M.create(ctx)
|
||||
return false
|
||||
end
|
||||
local should_resume_playback = state.auto_play_ready_should_resume_playback == true
|
||||
if resolve_osd_messages_enabled() then
|
||||
stop_overlay_loading_osd()
|
||||
show_osd(AUTO_PLAY_READY_READY_OSD)
|
||||
end
|
||||
disarm_auto_play_ready_gate({ resume_playback = false })
|
||||
show_osd(AUTO_PLAY_READY_READY_OSD)
|
||||
if should_resume_playback then
|
||||
mp.set_property_native("pause", false)
|
||||
subminer_log("info", "process", "Resuming playback after startup gate: " .. tostring(reason or "ready"))
|
||||
@@ -287,8 +337,11 @@ function M.create(ctx)
|
||||
end
|
||||
state.auto_play_ready_gate_armed = true
|
||||
mp.set_property_native("pause", true)
|
||||
show_osd(AUTO_PLAY_READY_LOADING_OSD)
|
||||
if type(mp.add_periodic_timer) == "function" then
|
||||
if resolve_osd_messages_enabled() then
|
||||
stop_overlay_loading_osd()
|
||||
show_osd(AUTO_PLAY_READY_LOADING_OSD)
|
||||
end
|
||||
if resolve_osd_messages_enabled() and type(mp.add_periodic_timer) == "function" then
|
||||
state.auto_play_ready_osd_timer = mp.add_periodic_timer(2.5, function()
|
||||
if state.auto_play_ready_gate_armed then
|
||||
show_osd(AUTO_PLAY_READY_LOADING_OSD)
|
||||
@@ -375,6 +428,9 @@ function M.create(ctx)
|
||||
table.insert(args, "--texthooker")
|
||||
end
|
||||
end
|
||||
if action == "playback-feedback" and type(overrides.message) == "string" and overrides.message ~= "" then
|
||||
table.insert(args, overrides.message)
|
||||
end
|
||||
|
||||
return args
|
||||
end
|
||||
@@ -462,6 +518,27 @@ function M.create(ctx)
|
||||
end)
|
||||
end
|
||||
|
||||
local function notify_playback_feedback(message, fallback)
|
||||
if type(message) ~= "string" or message == "" then
|
||||
return
|
||||
end
|
||||
if resolve_osd_messages_enabled() then
|
||||
show_osd(message)
|
||||
return
|
||||
end
|
||||
if not binary.ensure_binary_available() then
|
||||
if fallback then
|
||||
fallback()
|
||||
end
|
||||
return
|
||||
end
|
||||
run_control_command_async("playback-feedback", { message = message }, function(ok)
|
||||
if not ok and fallback then
|
||||
fallback()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function wait_for_app_ping_state(expected_running, label, on_ready, on_timeout, attempt)
|
||||
attempt = attempt or 1
|
||||
run_control_command_async("app-ping", nil, function(_ok, result)
|
||||
@@ -543,6 +620,7 @@ function M.create(ctx)
|
||||
|
||||
if not binary.ensure_binary_available() then
|
||||
subminer_log("error", "binary", "SubMiner binary not found")
|
||||
stop_overlay_loading_osd()
|
||||
show_osd("Error: binary not found")
|
||||
return
|
||||
end
|
||||
@@ -627,6 +705,7 @@ function M.create(ctx)
|
||||
state.overlay_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
subminer_log("error", "process", "Overlay start failed after retries: " .. reason)
|
||||
stop_overlay_loading_osd()
|
||||
show_osd("Overlay start failed")
|
||||
release_auto_play_ready_gate("overlay-start-failed")
|
||||
return
|
||||
@@ -679,6 +758,7 @@ function M.create(ctx)
|
||||
state.overlay_running = false
|
||||
state.texthooker_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
stop_overlay_loading_osd()
|
||||
disarm_auto_play_ready_gate()
|
||||
show_osd("Stopped")
|
||||
end
|
||||
@@ -690,6 +770,7 @@ function M.create(ctx)
|
||||
return
|
||||
end
|
||||
state.suppress_ready_overlay_restore = true
|
||||
stop_overlay_loading_osd()
|
||||
|
||||
run_control_command_async("hide-visible-overlay", nil, function(ok, result)
|
||||
if ok then
|
||||
@@ -794,14 +875,22 @@ function M.create(ctx)
|
||||
return
|
||||
end
|
||||
|
||||
local function show_restart_feedback(message)
|
||||
notify_playback_feedback(message, function()
|
||||
show_osd(message)
|
||||
end)
|
||||
end
|
||||
|
||||
start_overlay_loading_osd()
|
||||
subminer_log("info", "process", "Restarting overlay...")
|
||||
show_osd("Restarting...")
|
||||
show_restart_feedback("Restarting...")
|
||||
|
||||
run_control_command_async("stop", nil, function(ok, result)
|
||||
if not ok then
|
||||
local reason = result and result.stderr or "unknown error"
|
||||
subminer_log("warn", "process", "Restart stop command failed: " .. reason)
|
||||
show_osd("Restart failed")
|
||||
stop_overlay_loading_osd()
|
||||
show_restart_feedback("Restart failed")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -836,14 +925,25 @@ function M.create(ctx)
|
||||
"process",
|
||||
"Overlay start failed: " .. (error or (result and result.stderr) or "unknown error")
|
||||
)
|
||||
show_osd("Restart failed")
|
||||
stop_overlay_loading_osd()
|
||||
show_restart_feedback("Restart failed")
|
||||
else
|
||||
wait_for_app_ping_state(true, "own the single-instance lock", function()
|
||||
run_control_command_async("show-visible-overlay")
|
||||
show_osd("Restarted successfully")
|
||||
run_control_command_async("show-visible-overlay", nil, function(ok)
|
||||
if ok then
|
||||
show_restart_feedback("Restarted successfully")
|
||||
else
|
||||
show_restart_feedback("Restart failed")
|
||||
end
|
||||
end)
|
||||
end, function()
|
||||
run_control_command_async("show-visible-overlay")
|
||||
show_osd("Restarted successfully")
|
||||
run_control_command_async("show-visible-overlay", nil, function(ok)
|
||||
if ok then
|
||||
show_restart_feedback("Restarted successfully")
|
||||
else
|
||||
show_restart_feedback("Restart failed")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@@ -852,7 +952,8 @@ function M.create(ctx)
|
||||
ensure_texthooker_running(function() end)
|
||||
end
|
||||
end, function()
|
||||
show_osd("Restart failed")
|
||||
stop_overlay_loading_osd()
|
||||
show_restart_feedback("Restart failed")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -877,6 +978,7 @@ function M.create(ctx)
|
||||
describe_mpv_ipc_socket_match = describe_mpv_ipc_socket_match,
|
||||
has_matching_mpv_ipc_socket = has_matching_mpv_ipc_socket,
|
||||
run_control_command_async = run_control_command_async,
|
||||
notify_playback_feedback = notify_playback_feedback,
|
||||
record_visible_overlay_visibility = record_visible_overlay_visibility,
|
||||
run_binary_command_async = run_binary_command_async,
|
||||
parse_start_script_message_overrides = parse_start_script_message_overrides,
|
||||
@@ -893,6 +995,8 @@ function M.create(ctx)
|
||||
check_binary_available = check_binary_available,
|
||||
notify_auto_play_ready = notify_auto_play_ready,
|
||||
disarm_auto_play_ready_gate = disarm_auto_play_ready_gate,
|
||||
start_overlay_loading_osd = start_overlay_loading_osd,
|
||||
stop_overlay_loading_osd = stop_overlay_loading_osd,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -244,6 +244,8 @@ function M.create(ctx)
|
||||
return { "--toggle-secondary-sub" }
|
||||
elseif action_id == "toggleSubtitleSidebar" then
|
||||
return { "--toggle-subtitle-sidebar" }
|
||||
elseif action_id == "toggleNotificationHistory" then
|
||||
return { "--session-action", '{"actionId":"toggleNotificationHistory"}' }
|
||||
elseif action_id == "markAudioCard" then
|
||||
return { "--mark-audio-card" }
|
||||
elseif action_id == "markWatched" then
|
||||
|
||||
@@ -24,6 +24,9 @@ function M.new()
|
||||
auto_play_ready_osd_timer = nil,
|
||||
auto_play_ready_signal_seen = false,
|
||||
auto_play_ready_initial_pause_ownership_consumed = false,
|
||||
overlay_loading_osd_active = false,
|
||||
overlay_loading_osd_timer = nil,
|
||||
overlay_loading_osd_frame = 1,
|
||||
pending_visible_overlay_hide_timer = nil,
|
||||
pending_visible_overlay_hide_generation = 0,
|
||||
suppress_ready_overlay_restore = false,
|
||||
|
||||
Reference in New Issue
Block a user