mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-11-10 04:46:07 -08:00
fix(plugin): keep loading OSD off on warm playlist advance (#317)
* fix(plugin): keep loading OSD off on warm playlist advance - Track overlay_content_ready_seen when the app sends subminer-overlay-loading-ready - Skip restarting the "Overlay loading" spinner for an overlay that already reported ready, so it no longer times out into "Overlay did not become ready" (#315) - Reset the flag on stop, restart, start failure and shutdown - Add start-gate tests for warm and not-yet-ready playlist advances * fix(plugin): only report overlay ready once content has loaded Hide paths dismissed the loading OSD with subminer-overlay-loading-ready, which the plugin now remembers to skip the spinner on later playlist files. A hide before the overlay had content would wrongly mark it ready. The app now sends ready only when the overlay window content is ready, and a new stop-only subminer-overlay-loading-dismissed message otherwise. * fix(plugin): resync overlay readiness on dismissal and mpv connect A dismissal now clears the plugin's readiness flag, since the app sends it only while its overlay window has no content (e.g. a replaced window). On mpv (re)connect the app resends subminer-overlay-loading-ready when the overlay already has content, so a ready signal lost while mpv was away, or sent before this mpv's plugin existed, still reaches the plugin. * refactor(main): pass overlay readiness handler directly
This commit is contained in:
1 parent
b99409bbcd
commit
848a6c4021
13 files changed
+309
-9
No files matched your search
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: mpv-plugin
|
||||
|
||||
- Fixed the "Overlay loading" message staying on screen when mpv moves to the next file in a playlist, then turning into "Overlay did not become ready; check SubMiner logs" after 30 seconds even though the overlay was working. The overlay window is reused between files, so the plugin no longer restarts the loading message once the overlay has reported ready.
|
||||
@@ -124,7 +124,7 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no
|
||||
|
||||
`log-level` sets SubMiner's log verbosity. Do not use `--debug` for this; it turns on the app's dev mode.
|
||||
|
||||
The plugin also handles messages the SubMiner app sends it (`subminer-autoplay-ready`, `subminer-autoplay-hold`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, `subminer-reload-session-bindings`). You do not need to send these yourself. The AniSkip messages are listed on the [AniSkip page](/aniskip-integration#triggering-from-mpv).
|
||||
The plugin also handles messages the SubMiner app sends it (`subminer-autoplay-ready`, `subminer-autoplay-hold`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, `subminer-overlay-loading-dismissed`, `subminer-reload-session-bindings`). You do not need to send these yourself. The AniSkip messages are listed on the [AniSkip page](/aniskip-integration#triggering-from-mpv).
|
||||
|
||||
## Auto-start behavior
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ The mpv plugin accepts these script messages:
|
||||
script-message subminer-start backend=hyprland socket=/custom/path texthooker=no log-level=debug
|
||||
```
|
||||
|
||||
The plugin also registers `subminer-autoplay-ready`, `subminer-autoplay-hold`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, and `subminer-reload-session-bindings`. The SubMiner app sends these to keep the plugin in sync, so do not send them from your own scripts.
|
||||
The plugin also registers `subminer-autoplay-ready`, `subminer-autoplay-hold`, `subminer-visible-overlay-shown`, `subminer-visible-overlay-hidden`, `subminer-managed-subtitles-loading`, `subminer-overlay-loading-ready`, `subminer-overlay-loading-dismissed`, and `subminer-reload-session-bindings`. The SubMiner app sends these to keep the plugin in sync, so do not send them from your own scripts.
|
||||
|
||||
While the app is connected to mpv, it also handles two AniSkip messages over the mpv IPC socket: `subminer-skip-intro` skips the intro, and `subminer-aniskip-refresh` reloads intro data, for example after your script changes title or episode metadata.
|
||||
|
||||
|
||||
@@ -162,12 +162,15 @@ function M.create(ctx)
|
||||
and not (state.overlay_running and state.auto_play_ready_signal_seen == true)
|
||||
end
|
||||
|
||||
-- A warm playlist advance keeps the already-ready overlay window, and the app only reports
|
||||
-- readiness when content first loads, so a restarted spinner would never clear (#315).
|
||||
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
|
||||
and not (state.overlay_running and state.overlay_content_ready_seen == true)
|
||||
end
|
||||
|
||||
local function start_overlay_when_socket_ready(generation, media_identity, same_media_loaded, attempt)
|
||||
@@ -311,6 +314,7 @@ function M.create(ctx)
|
||||
process.stop_overlay_loading_osd()
|
||||
clear_pending_visible_overlay_hide()
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.overlay_content_ready_seen = false
|
||||
state.current_media_identity = nil
|
||||
state.current_media_title = nil
|
||||
state.pending_reload_media_identity = nil
|
||||
|
||||
@@ -46,7 +46,12 @@ function M.create(ctx)
|
||||
process.hold_auto_play_ready_gate()
|
||||
end)
|
||||
mp.register_script_message("subminer-overlay-loading-ready", function()
|
||||
process.stop_overlay_loading_osd()
|
||||
process.mark_overlay_content_ready()
|
||||
end)
|
||||
-- Sent when the app dismisses loading while its overlay window has no content (hidden mid-load,
|
||||
-- or a replaced window).
|
||||
mp.register_script_message("subminer-overlay-loading-dismissed", function()
|
||||
process.mark_overlay_content_not_ready()
|
||||
end)
|
||||
mp.register_script_message("subminer-stats-toggle", function()
|
||||
if opts.osd_messages then
|
||||
|
||||
@@ -282,6 +282,19 @@ function M.create(ctx)
|
||||
clear_overlay_loading_osd_deadline()
|
||||
end
|
||||
|
||||
-- Handles the app's subminer-overlay-loading-ready message.
|
||||
local function mark_overlay_content_ready()
|
||||
state.overlay_content_ready_seen = true
|
||||
stop_overlay_loading_osd()
|
||||
end
|
||||
|
||||
-- Handles subminer-overlay-loading-dismissed: the app's current overlay window has no content
|
||||
-- (hidden mid-load, or a replaced window), so earlier readiness no longer applies.
|
||||
local function mark_overlay_content_not_ready()
|
||||
state.overlay_content_ready_seen = false
|
||||
stop_overlay_loading_osd()
|
||||
end
|
||||
|
||||
local function start_overlay_loading_osd()
|
||||
if state.overlay_loading_osd_active then
|
||||
return
|
||||
@@ -741,6 +754,7 @@ function M.create(ctx)
|
||||
|
||||
state.overlay_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.overlay_content_ready_seen = false
|
||||
subminer_log("error", "process", "Overlay start failed after retries: " .. reason)
|
||||
stop_overlay_loading_osd()
|
||||
show_osd("Overlay start failed")
|
||||
@@ -795,6 +809,7 @@ function M.create(ctx)
|
||||
state.overlay_running = false
|
||||
state.texthooker_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.overlay_content_ready_seen = false
|
||||
stop_overlay_loading_osd()
|
||||
disarm_auto_play_ready_gate()
|
||||
show_osd("Stopped")
|
||||
@@ -934,6 +949,7 @@ function M.create(ctx)
|
||||
state.overlay_running = false
|
||||
state.texthooker_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.overlay_content_ready_seen = false
|
||||
state.suppress_ready_overlay_restore = false
|
||||
state.force_ready_overlay_restore = true
|
||||
disarm_auto_play_ready_gate({ resume_playback = false })
|
||||
@@ -957,6 +973,7 @@ function M.create(ctx)
|
||||
if not success or (result and result.status ~= 0) then
|
||||
state.overlay_running = false
|
||||
state.auto_play_ready_signal_seen = false
|
||||
state.overlay_content_ready_seen = false
|
||||
subminer_log(
|
||||
"error",
|
||||
"process",
|
||||
@@ -1035,6 +1052,8 @@ function M.create(ctx)
|
||||
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,
|
||||
mark_overlay_content_ready = mark_overlay_content_ready,
|
||||
mark_overlay_content_not_ready = mark_overlay_content_not_ready,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ function M.new()
|
||||
auto_play_ready_osd_timer = nil,
|
||||
auto_play_ready_signal_seen = false,
|
||||
auto_play_ready_initial_pause_ownership_consumed = false,
|
||||
-- The running overlay has reported its content ready at least once; warm playlist advances
|
||||
-- reuse that window, so they must not restart the loading spinner (#315).
|
||||
overlay_content_ready_seen = false,
|
||||
overlay_loading_osd_active = false,
|
||||
overlay_loading_osd_timer = nil,
|
||||
overlay_loading_osd_deadline = nil,
|
||||
|
||||
@@ -1687,6 +1687,176 @@ do
|
||||
)
|
||||
end
|
||||
|
||||
local function count_live_timeouts(recorded, seconds)
|
||||
local count = 0
|
||||
for _, timeout in ipairs(recorded.timeout_handles) do
|
||||
if timeout.seconds == seconds and not timeout.killed then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- Fire only the short plugin timers (socket retries, deferred hides), leaving the 30 s loading deadline armed.
|
||||
local function fire_short_timeouts(recorded)
|
||||
for _, timeout in ipairs(recorded.timeout_handles) do
|
||||
if not timeout.killed and timeout.callback and timeout.seconds < 5 then
|
||||
timeout.killed = true
|
||||
timeout.callback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function count_live_periodic_timers(recorded)
|
||||
local count = 0
|
||||
for _, timer in ipairs(recorded.periodic_timers) do
|
||||
if not timer.killed then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
do
|
||||
-- Issue #315: a warm playlist advance reuses an overlay whose content is already ready, so the
|
||||
-- app never sends another overlay-loading-ready and a restarted spinner would hit its deadline.
|
||||
local scenario = {
|
||||
process_list = "",
|
||||
defer_timeouts = true,
|
||||
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/warm-episode-01.mkv",
|
||||
playlist_count = 2,
|
||||
playlist_pos = 0,
|
||||
media_title = "Warm Episode 1",
|
||||
files = {
|
||||
[binary_path] = true,
|
||||
},
|
||||
}
|
||||
local recorded, err = run_plugin_scenario(scenario)
|
||||
assert_true(recorded ~= nil, "plugin failed to load for warm playlist loading OSD scenario: " .. tostring(err))
|
||||
fire_event(recorded, "start-file")
|
||||
fire_event(recorded, "file-loaded")
|
||||
fire_short_timeouts(recorded)
|
||||
recorded.script_messages["subminer-overlay-loading-ready"]()
|
||||
recorded.script_messages["subminer-autoplay-ready"]()
|
||||
fire_event(recorded, "end-file", { reason = "eof" })
|
||||
scenario.path = "/media/warm-episode-02.mkv"
|
||||
scenario.media_title = "Warm Episode 2"
|
||||
scenario.playlist_pos = 1
|
||||
recorded.osd = {}
|
||||
fire_event(recorded, "start-file")
|
||||
fire_event(recorded, "file-loaded")
|
||||
fire_short_timeouts(recorded)
|
||||
assert_true(
|
||||
not has_osd_message(recorded.osd, "SubMiner: Overlay loading |"),
|
||||
"warm playlist advance should not restart the overlay loading OSD for an already-ready overlay"
|
||||
)
|
||||
assert_true(
|
||||
count_live_periodic_timers(recorded) == 0 and count_live_timeouts(recorded, 30) == 0,
|
||||
"warm playlist advance should not leave an overlay loading spinner or deadline running"
|
||||
)
|
||||
end
|
||||
|
||||
do
|
||||
-- A stop-only dismissal (overlay hidden before content loaded) clears the spinner without marking
|
||||
-- the overlay ready, so the next file still shows the spinner.
|
||||
local scenario = {
|
||||
process_list = "",
|
||||
defer_timeouts = true,
|
||||
option_overrides = {
|
||||
binary_path = binary_path,
|
||||
auto_start = "yes",
|
||||
auto_start_visible_overlay = "yes",
|
||||
auto_start_pause_until_ready = "no",
|
||||
socket_path = "/tmp/subminer-socket",
|
||||
},
|
||||
input_ipc_server = "/tmp/subminer-socket",
|
||||
path = "/media/slow-overlay-01.mkv",
|
||||
playlist_count = 2,
|
||||
playlist_pos = 0,
|
||||
media_title = "Slow Overlay 1",
|
||||
files = {
|
||||
[binary_path] = true,
|
||||
},
|
||||
}
|
||||
local recorded, err = run_plugin_scenario(scenario)
|
||||
assert_true(recorded ~= nil, "plugin failed to load for not-ready playlist loading OSD scenario: " .. tostring(err))
|
||||
fire_event(recorded, "start-file")
|
||||
fire_event(recorded, "file-loaded")
|
||||
fire_short_timeouts(recorded)
|
||||
assert_true(
|
||||
count_live_periodic_timers(recorded) == 1 and count_live_timeouts(recorded, 30) == 1,
|
||||
"overlay loading OSD should still be running before the app dismisses it"
|
||||
)
|
||||
assert_true(
|
||||
recorded.script_messages["subminer-overlay-loading-dismissed"] ~= nil,
|
||||
"overlay loading dismissed script message should be registered"
|
||||
)
|
||||
recorded.script_messages["subminer-overlay-loading-dismissed"]()
|
||||
assert_true(
|
||||
count_live_periodic_timers(recorded) == 0 and count_live_timeouts(recorded, 30) == 0,
|
||||
"overlay loading dismissed should stop the spinner and its deadline"
|
||||
)
|
||||
fire_event(recorded, "end-file", { reason = "eof" })
|
||||
scenario.path = "/media/slow-overlay-02.mkv"
|
||||
scenario.media_title = "Slow Overlay 2"
|
||||
scenario.playlist_pos = 1
|
||||
recorded.osd = {}
|
||||
fire_event(recorded, "start-file")
|
||||
assert_true(
|
||||
has_osd_message(recorded.osd, "SubMiner: Overlay loading |"),
|
||||
"a stop-only dismissal should not mark the overlay ready for the next playlist file"
|
||||
)
|
||||
end
|
||||
|
||||
do
|
||||
-- A dismissal after readiness means the app's overlay window was replaced and has no content yet,
|
||||
-- so the plugin must forget the old readiness and show the spinner on the next file.
|
||||
local scenario = {
|
||||
process_list = "",
|
||||
defer_timeouts = true,
|
||||
option_overrides = {
|
||||
binary_path = binary_path,
|
||||
auto_start = "yes",
|
||||
auto_start_visible_overlay = "yes",
|
||||
auto_start_pause_until_ready = "no",
|
||||
socket_path = "/tmp/subminer-socket",
|
||||
},
|
||||
input_ipc_server = "/tmp/subminer-socket",
|
||||
path = "/media/replaced-overlay-01.mkv",
|
||||
playlist_count = 2,
|
||||
playlist_pos = 0,
|
||||
media_title = "Replaced Overlay 1",
|
||||
files = {
|
||||
[binary_path] = true,
|
||||
},
|
||||
}
|
||||
local recorded, err = run_plugin_scenario(scenario)
|
||||
assert_true(recorded ~= nil, "plugin failed to load for replaced overlay loading OSD scenario: " .. tostring(err))
|
||||
fire_event(recorded, "start-file")
|
||||
fire_event(recorded, "file-loaded")
|
||||
fire_short_timeouts(recorded)
|
||||
recorded.script_messages["subminer-overlay-loading-ready"]()
|
||||
recorded.script_messages["subminer-overlay-loading-dismissed"]()
|
||||
fire_event(recorded, "end-file", { reason = "eof" })
|
||||
scenario.path = "/media/replaced-overlay-02.mkv"
|
||||
scenario.media_title = "Replaced Overlay 2"
|
||||
scenario.playlist_pos = 1
|
||||
recorded.osd = {}
|
||||
fire_event(recorded, "start-file")
|
||||
assert_true(
|
||||
has_osd_message(recorded.osd, "SubMiner: Overlay loading |"),
|
||||
"a dismissal after readiness should make the next playlist file show the loading OSD again"
|
||||
)
|
||||
end
|
||||
|
||||
do
|
||||
local recorded, err = run_plugin_scenario({
|
||||
defer_timeouts = true,
|
||||
|
||||
@@ -1330,6 +1330,7 @@ const {
|
||||
toggleNotificationHistoryPanel,
|
||||
showConfiguredPlaybackFeedback,
|
||||
maybeStartOverlayLoadingOsd,
|
||||
reportOverlayContentReadyToPlugin,
|
||||
} = overlayNotificationsRuntime;
|
||||
|
||||
const overlayVisibilityComposer = composeOverlayVisibilityRuntime({
|
||||
@@ -1777,6 +1778,7 @@ const playback = createPlaybackAssembly({
|
||||
createSubtitlePrefetchService,
|
||||
requestAppQuit: () => requestAppQuit(),
|
||||
maybeStartOverlayLoadingOsd: (mediaPath) => maybeStartOverlayLoadingOsd(mediaPath),
|
||||
reportOverlayContentReadyToPlugin,
|
||||
flushQueuedMpvOsdNotifications: () => flushQueuedMpvOsdNotifications(),
|
||||
refreshMpvSessionBindings: () => shortcuts.refreshMpvSessionBindings(),
|
||||
ensureImmersionTrackerStarted: () => stats.ensureImmersionTrackerStarted(),
|
||||
|
||||
@@ -153,6 +153,7 @@ function makeDeps(recorder: Recorder): PlaybackAssemblyDeps {
|
||||
),
|
||||
requestAppQuit: spy('requestAppQuit', noop),
|
||||
maybeStartOverlayLoadingOsd: spy('maybeStartOverlayLoadingOsd', noop),
|
||||
reportOverlayContentReadyToPlugin: spy('reportOverlayContentReadyToPlugin', noop),
|
||||
flushQueuedMpvOsdNotifications: spy('flushQueuedMpvOsdNotifications', noop),
|
||||
refreshMpvSessionBindings: spy('refreshMpvSessionBindings', resolved),
|
||||
ensureImmersionTrackerStarted: spy('ensureImmersionTrackerStarted', noop),
|
||||
@@ -212,7 +213,7 @@ test('createPlaybackAssembly does not call its deps while constructing', () => {
|
||||
assert.deepEqual(recorder.names(), []);
|
||||
});
|
||||
|
||||
test('mpv connection starts the loading OSD and flushes queued OSD notifications before session work', () => {
|
||||
test('mpv connection starts the loading OSD, resends overlay readiness, and flushes queued OSD notifications before session work', () => {
|
||||
const { recorder, emit } = setup();
|
||||
|
||||
emit('connection-change', { connected: true });
|
||||
@@ -221,7 +222,11 @@ test('mpv connection starts the loading OSD and flushes queued OSD notifications
|
||||
const osdIndex = names.indexOf('maybeStartOverlayLoadingOsd');
|
||||
assert.ok(osdIndex >= 0);
|
||||
assert.deepEqual(recorder.calls[osdIndex]?.args, []);
|
||||
assert.ok(osdIndex < names.indexOf('flushQueuedMpvOsdNotifications'));
|
||||
assert.ok(osdIndex < names.indexOf('reportOverlayContentReadyToPlugin'));
|
||||
assert.ok(
|
||||
names.indexOf('reportOverlayContentReadyToPlugin') <
|
||||
names.indexOf('flushQueuedMpvOsdNotifications'),
|
||||
);
|
||||
assert.ok(
|
||||
names.indexOf('flushQueuedMpvOsdNotifications') < names.indexOf('refreshMpvSessionBindings'),
|
||||
);
|
||||
|
||||
@@ -205,6 +205,7 @@ export interface PlaybackAssemblyDeps {
|
||||
) => SubtitlePrefetchService;
|
||||
requestAppQuit: () => void;
|
||||
maybeStartOverlayLoadingOsd: OverlayNotificationsRuntime['maybeStartOverlayLoadingOsd'];
|
||||
reportOverlayContentReadyToPlugin: OverlayNotificationsRuntime['reportOverlayContentReadyToPlugin'];
|
||||
flushQueuedMpvOsdNotifications: OverlayNotificationsRuntime['flushQueuedMpvOsdNotifications'];
|
||||
refreshMpvSessionBindings: () => Promise<void>;
|
||||
ensureImmersionTrackerStarted: () => void;
|
||||
@@ -589,6 +590,7 @@ export function createPlaybackAssembly(deps: PlaybackAssemblyDeps) {
|
||||
},
|
||||
onMpvConnected: () => {
|
||||
deps.maybeStartOverlayLoadingOsd();
|
||||
deps.reportOverlayContentReadyToPlugin();
|
||||
deps.flushQueuedMpvOsdNotifications();
|
||||
secondarySubtitleTrackController.scheduleRefresh(0);
|
||||
void deps.refreshMpvSessionBindings();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
|
||||
import { OVERLAY_WINDOW_CONTENT_READY_FLAG } from '../../core/services/overlay-window-flags';
|
||||
import type { MpvIpcClient } from '../../core/services';
|
||||
import type { ResolvedConfig } from '../../types';
|
||||
import { createOverlayNotificationsRuntime } from './overlay-notifications-runtime';
|
||||
|
||||
function createRuntimeHarness(contentReady: boolean) {
|
||||
const sent: unknown[][] = [];
|
||||
const overlayWindow = {
|
||||
isDestroyed: () => false,
|
||||
[OVERLAY_WINDOW_CONTENT_READY_FLAG]: contentReady,
|
||||
} as unknown as BrowserWindow;
|
||||
const mpvClient = {
|
||||
send: ({ command }: { command: unknown[] }) => {
|
||||
sent.push(command);
|
||||
},
|
||||
} as unknown as MpvIpcClient;
|
||||
const runtime = createOverlayNotificationsRuntime({
|
||||
getResolvedConfig: () => ({}) as ResolvedConfig,
|
||||
getMainOverlayWindow: () => overlayWindow,
|
||||
getVisibleOverlayVisible: () => false,
|
||||
broadcastToOverlayWindows: () => {},
|
||||
showMpvOsd: () => {},
|
||||
getMpvClient: () => mpvClient,
|
||||
getAnkiIntegration: () => null,
|
||||
getRuntimeOptionsManager: () => null,
|
||||
});
|
||||
const scriptMessages = () =>
|
||||
sent.filter((command) => command[0] === 'script-message').map((command) => command[1]);
|
||||
return { runtime, scriptMessages };
|
||||
}
|
||||
|
||||
test('dismissing the loading OSD before overlay content is ready only stops the plugin spinner', () => {
|
||||
const { runtime, scriptMessages } = createRuntimeHarness(false);
|
||||
|
||||
runtime.dismissOverlayLoadingStatusNotification();
|
||||
|
||||
assert.deepEqual(scriptMessages(), ['subminer-overlay-loading-dismissed']);
|
||||
});
|
||||
|
||||
test('dismissing the loading OSD once overlay content is ready reports readiness to the plugin', () => {
|
||||
const { runtime, scriptMessages } = createRuntimeHarness(true);
|
||||
|
||||
runtime.dismissOverlayLoadingStatusNotification();
|
||||
|
||||
assert.deepEqual(scriptMessages(), ['subminer-overlay-loading-ready']);
|
||||
});
|
||||
|
||||
test('reporting overlay readiness after an mpv connect resends ready only for loaded content', () => {
|
||||
const ready = createRuntimeHarness(true);
|
||||
ready.runtime.reportOverlayContentReadyToPlugin();
|
||||
assert.deepEqual(ready.scriptMessages(), ['subminer-overlay-loading-ready']);
|
||||
|
||||
const loading = createRuntimeHarness(false);
|
||||
loading.runtime.reportOverlayContentReadyToPlugin();
|
||||
assert.deepEqual(loading.scriptMessages(), []);
|
||||
});
|
||||
@@ -61,6 +61,7 @@ export function createOverlayNotificationsRuntime(deps: OverlayNotificationsRunt
|
||||
showYoutubeFlowStatusNotification: (message: string) => void;
|
||||
showOverlayLoadingStatusNotification: () => void;
|
||||
dismissOverlayLoadingStatusNotification: () => void;
|
||||
reportOverlayContentReadyToPlugin: () => void;
|
||||
maybeStartOverlayLoadingOsd: (mediaPath?: string | null) => void;
|
||||
} {
|
||||
function isVisibleOverlayContentReady(): boolean {
|
||||
@@ -229,15 +230,39 @@ export function createOverlayNotificationsRuntime(deps: OverlayNotificationsRunt
|
||||
getOverlayLoadingOsdController().start();
|
||||
}
|
||||
|
||||
function isMainOverlayContentReady(): boolean {
|
||||
const overlayWindow = deps.getMainOverlayWindow();
|
||||
return Boolean(overlayWindow && isOverlayWindowContentReady(overlayWindow));
|
||||
}
|
||||
|
||||
function sendPluginOverlayLoadingMessage(
|
||||
message: 'subminer-overlay-loading-ready' | 'subminer-overlay-loading-dismissed',
|
||||
): void {
|
||||
sendMpvCommandRuntime(deps.getMpvClient(), ['script-message', message]);
|
||||
}
|
||||
|
||||
// Hide paths also dismiss, often before the overlay has content. Only a content-ready window
|
||||
// reports `subminer-overlay-loading-ready`: the plugin remembers it and skips the spinner on
|
||||
// later playlist files, so other dismissals send the stop-only message, which also makes the
|
||||
// plugin forget readiness from a replaced window.
|
||||
function dismissOverlayLoadingStatusNotification(): void {
|
||||
getOverlayLoadingOsdController().stop();
|
||||
sendMpvCommandRuntime(deps.getMpvClient(), [
|
||||
'script-message',
|
||||
'subminer-overlay-loading-ready',
|
||||
]);
|
||||
sendPluginOverlayLoadingMessage(
|
||||
isMainOverlayContentReady()
|
||||
? 'subminer-overlay-loading-ready'
|
||||
: 'subminer-overlay-loading-dismissed',
|
||||
);
|
||||
dismissOverlayNotification('overlay-loading-status');
|
||||
}
|
||||
|
||||
// Called when mpv (re)connects: a ready message sent while mpv was away, or before this mpv's
|
||||
// plugin existed, was lost. Never sends the dismissal, since nothing is being hidden.
|
||||
function reportOverlayContentReadyToPlugin(): void {
|
||||
if (isMainOverlayContentReady()) {
|
||||
sendPluginOverlayLoadingMessage('subminer-overlay-loading-ready');
|
||||
}
|
||||
}
|
||||
|
||||
const maybeStartOverlayLoadingOsd = createMaybeStartOverlayLoadingOsdHandler({
|
||||
getVisibleOverlayRequested: () => deps.getVisibleOverlayVisible(),
|
||||
isOverlayContentReady: () => isVisibleOverlayContentReady(),
|
||||
@@ -261,6 +286,7 @@ export function createOverlayNotificationsRuntime(deps: OverlayNotificationsRunt
|
||||
showYoutubeFlowStatusNotification,
|
||||
showOverlayLoadingStatusNotification,
|
||||
dismissOverlayLoadingStatusNotification,
|
||||
reportOverlayContentReadyToPlugin,
|
||||
maybeStartOverlayLoadingOsd,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user