mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-25 12:55:18 -07:00
fix(jellyfin): send explicit hide/show overlay instead of toggle
- Track overlay visibility in plugin state; y-t uses explicit hide/show commands when state is known - Prevent paused Jellyfin playback from resuming on overlay hide - Fix subtitle cache cleanup to only remove dirs after successful cleanup
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: jellyfin
|
||||
|
||||
- Fixed Jellyfin `y-t` overlay hide so the plugin sends an explicit hide command when it knows the overlay is visible, avoiding overlay reloads and paused playback resumes.
|
||||
@@ -77,6 +77,20 @@ function M.create(ctx)
|
||||
return DEFAULT_AUTO_PLAY_READY_TIMEOUT_SECONDS
|
||||
end
|
||||
|
||||
local function record_visible_overlay_action(action)
|
||||
if action == "show-visible-overlay" then
|
||||
state.visible_overlay_requested = true
|
||||
state.suppress_ready_overlay_restore = false
|
||||
elseif action == "hide-visible-overlay" then
|
||||
state.visible_overlay_requested = false
|
||||
elseif action == "toggle-visible-overlay" and state.visible_overlay_requested ~= nil then
|
||||
state.visible_overlay_requested = not state.visible_overlay_requested
|
||||
if state.visible_overlay_requested then
|
||||
state.suppress_ready_overlay_restore = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function normalize_socket_path(path)
|
||||
if type(path) ~= "string" then
|
||||
return nil
|
||||
@@ -317,6 +331,7 @@ function M.create(ctx)
|
||||
end
|
||||
|
||||
run_control_command_async = function(action, overrides, callback)
|
||||
record_visible_overlay_action(action)
|
||||
local args = build_command_args(action, overrides)
|
||||
local command = build_subprocess_command(args)
|
||||
subminer_log("debug", "process", "Control command: " .. table.concat(args, " "))
|
||||
@@ -557,7 +572,8 @@ function M.create(ctx)
|
||||
show_osd("Stopped")
|
||||
end
|
||||
|
||||
local function hide_visible_overlay()
|
||||
local function hide_visible_overlay(options)
|
||||
options = options or {}
|
||||
if not binary.ensure_binary_available() then
|
||||
subminer_log("error", "binary", "SubMiner binary not found")
|
||||
return
|
||||
@@ -577,7 +593,9 @@ function M.create(ctx)
|
||||
end
|
||||
end)
|
||||
|
||||
disarm_auto_play_ready_gate()
|
||||
disarm_auto_play_ready_gate({
|
||||
resume_playback = options.resume_playback ~= false,
|
||||
})
|
||||
end
|
||||
|
||||
local function toggle_overlay()
|
||||
@@ -586,6 +604,22 @@ function M.create(ctx)
|
||||
show_osd("Error: binary not found")
|
||||
return
|
||||
end
|
||||
if state.visible_overlay_requested == true then
|
||||
state.suppress_ready_overlay_restore = true
|
||||
hide_visible_overlay({ resume_playback = false })
|
||||
return
|
||||
end
|
||||
if state.visible_overlay_requested == false then
|
||||
state.suppress_ready_overlay_restore = false
|
||||
disarm_auto_play_ready_gate({ resume_playback = false })
|
||||
run_control_command_async("show-visible-overlay", nil, function(ok)
|
||||
if not ok then
|
||||
subminer_log("warn", "process", "Show-visible-overlay command failed")
|
||||
show_osd("Toggle failed")
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
state.suppress_ready_overlay_restore = true
|
||||
disarm_auto_play_ready_gate({ resume_playback = false })
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ function M.new()
|
||||
auto_play_ready_osd_timer = nil,
|
||||
suppress_ready_overlay_restore = false,
|
||||
force_ready_overlay_restore = false,
|
||||
visible_overlay_requested = nil,
|
||||
current_media_identity = nil,
|
||||
pending_reload_media_identity = nil,
|
||||
auto_start_retry_generation = 0,
|
||||
|
||||
@@ -683,6 +683,55 @@ do
|
||||
)
|
||||
end
|
||||
|
||||
do
|
||||
local recorded, err = run_plugin_scenario({
|
||||
process_list = "",
|
||||
option_overrides = {
|
||||
binary_path = binary_path,
|
||||
auto_start = "yes",
|
||||
auto_start_visible_overlay = "yes",
|
||||
auto_start_pause_until_ready = "yes",
|
||||
socket_path = "/tmp/subminer-socket",
|
||||
},
|
||||
input_ipc_server = "/tmp/subminer-socket",
|
||||
path = "/media/jellyfin-stream.m3u8",
|
||||
media_title = "Jellyfin Episode",
|
||||
paused = true,
|
||||
files = {
|
||||
[binary_path] = true,
|
||||
},
|
||||
})
|
||||
assert_true(recorded ~= nil, "plugin failed to load for y-t hide visible overlay scenario: " .. tostring(err))
|
||||
fire_event(recorded, "file-loaded")
|
||||
local toggle_binding = nil
|
||||
for _, candidate in ipairs(recorded.key_bindings) do
|
||||
if candidate.name == "subminer-toggle" then
|
||||
toggle_binding = candidate
|
||||
break
|
||||
end
|
||||
end
|
||||
assert_true(toggle_binding ~= nil, "y-t toggle binding should be registered")
|
||||
toggle_binding.fn()
|
||||
fire_event(recorded, "file-loaded")
|
||||
recorded.script_messages["subminer-autoplay-ready"]()
|
||||
assert_true(
|
||||
count_control_calls(recorded.async_calls, "--hide-visible-overlay") == 1,
|
||||
"y-t should hide the known visible overlay explicitly instead of app-side toggle"
|
||||
)
|
||||
assert_true(
|
||||
count_control_calls(recorded.async_calls, "--toggle-visible-overlay") == 0,
|
||||
"y-t should avoid app-side toggle when plugin knows the overlay is visible"
|
||||
)
|
||||
assert_true(
|
||||
count_control_calls(recorded.async_calls, "--show-visible-overlay") == 1,
|
||||
"manual y-t hide should suppress duplicate auto-start and ready-time visible overlay reassertion"
|
||||
)
|
||||
assert_true(
|
||||
count_property_set(recorded.property_sets, "pause", false) == 0,
|
||||
"manual y-t hide should not resume paused Jellyfin playback"
|
||||
)
|
||||
end
|
||||
|
||||
do
|
||||
local recorded, err = run_plugin_scenario({
|
||||
process_list = "",
|
||||
@@ -1550,8 +1599,12 @@ do
|
||||
assert_true(recorded.script_messages["subminer-toggle"] ~= nil, "subminer-toggle script message not registered")
|
||||
recorded.script_messages["subminer-toggle"]()
|
||||
assert_true(
|
||||
count_control_calls(recorded.async_calls, "--toggle-visible-overlay") == 1,
|
||||
"manual toggle should use explicit visible-overlay toggle command"
|
||||
count_control_calls(recorded.async_calls, "--hide-visible-overlay") == 1,
|
||||
"manual toggle-off should hide a known visible overlay explicitly"
|
||||
)
|
||||
assert_true(
|
||||
count_control_calls(recorded.async_calls, "--toggle-visible-overlay") == 0,
|
||||
"manual toggle-off should avoid app-side toggle when plugin knows the overlay is visible"
|
||||
)
|
||||
recorded.script_messages["subminer-autoplay-ready"]()
|
||||
assert_true(
|
||||
|
||||
@@ -333,12 +333,18 @@ test('preload jellyfin subtitles cleans previous cached subtitles before a new p
|
||||
|
||||
test('preload jellyfin subtitles continues after cleanup failures', async () => {
|
||||
const commands: Array<Array<string | number>> = [];
|
||||
const cleanupCalls: string[][] = [];
|
||||
const logs: string[] = [];
|
||||
let cleanupShouldFail = false;
|
||||
const preload = createPreloadJellyfinExternalSubtitlesHandler(
|
||||
makeDeps({
|
||||
listJellyfinSubtitleTracks: async () => [
|
||||
{ index: 0, language: 'eng', title: 'English', deliveryUrl: 'https://sub/a.srt' },
|
||||
listJellyfinSubtitleTracks: async (_session, _clientInfo, itemId) => [
|
||||
{
|
||||
index: itemId === 'item-1' ? 0 : 1,
|
||||
language: 'eng',
|
||||
title: 'English',
|
||||
deliveryUrl: `https://sub/${itemId}.srt`,
|
||||
},
|
||||
],
|
||||
getMpvClient: () => ({ requestProperty: async () => [] }),
|
||||
cacheSubtitleTrack: async (track) => ({
|
||||
@@ -346,7 +352,8 @@ test('preload jellyfin subtitles continues after cleanup failures', async () =>
|
||||
cleanupDir: `/tmp/subminer-jellyfin-subtitles-${track.index}`,
|
||||
}),
|
||||
sendMpvCommand: (command) => commands.push(command),
|
||||
cleanupCachedSubtitles: () => {
|
||||
cleanupCachedSubtitles: (dirs) => {
|
||||
cleanupCalls.push(dirs);
|
||||
if (cleanupShouldFail) {
|
||||
throw new Error('cleanup failed');
|
||||
}
|
||||
@@ -358,13 +365,19 @@ test('preload jellyfin subtitles continues after cleanup failures', async () =>
|
||||
await preload({ session, clientInfo, itemId: 'item-1' });
|
||||
cleanupShouldFail = true;
|
||||
await assert.doesNotReject(() => preload({ session, clientInfo, itemId: 'item-2' }));
|
||||
cleanupShouldFail = false;
|
||||
preload.cleanupCachedSubtitles();
|
||||
|
||||
assert.deepEqual(logs, ['Failed to cleanup Jellyfin cached subtitles']);
|
||||
assert.deepEqual(cleanupCalls, [
|
||||
['/tmp/subminer-jellyfin-subtitles-0'],
|
||||
['/tmp/subminer-jellyfin-subtitles-0', '/tmp/subminer-jellyfin-subtitles-1'],
|
||||
]);
|
||||
assert.deepEqual(
|
||||
commands.filter((command) => command[0] === 'sub-add'),
|
||||
[
|
||||
['sub-add', '/tmp/subminer-jellyfin-subtitles-0/track.srt', 'auto', 'English', 'eng'],
|
||||
['sub-add', '/tmp/subminer-jellyfin-subtitles-0/track.srt', 'auto', 'English', 'eng'],
|
||||
['sub-add', '/tmp/subminer-jellyfin-subtitles-1/track.srt', 'auto', 'English', 'eng'],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -235,9 +235,11 @@ export function createPreloadJellyfinExternalSubtitlesHandler(deps: {
|
||||
|
||||
function cleanupActiveCache(): void {
|
||||
const dirs = [...activeCacheDirs];
|
||||
activeCacheDirs.clear();
|
||||
if (dirs.length === 0) return;
|
||||
deps.cleanupCachedSubtitles(dirs);
|
||||
for (const dir of dirs) {
|
||||
activeCacheDirs.delete(dir);
|
||||
}
|
||||
}
|
||||
|
||||
const runPreload = async (params: {
|
||||
|
||||
Reference in New Issue
Block a user