mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 17:16:20 -07:00
feat(anime): queue episodes to play next across anime
- Episode rows gain Play/Queue actions (and matching context-menu items); queued rows show their place in line, with a queue count and Clear queue in the episode header - Queue lives in the main process (`anime-browser-queue.ts`) so it survives the browser window closing and advances on mpv's end-file even when nobody is watching; streams resolve at play time so a signed URL cannot expire while queued - Holds mpv's keep-open off while the queue waits and restores it once empty; queueing with nothing playing just plays immediately - Adds anime-browser-queue and episode-queue unit tests, IPC channels/contracts, and doc updates
This commit is contained in:
@@ -60,8 +60,17 @@ export function registerAnimeBrowserIpcHandlers(deps: AnimeBrowserIpcDeps): void
|
||||
handle(channels.animeBrowserAddRepo, (_event, url) => runtime.addRepo(String(url)));
|
||||
handle(channels.animeBrowserRemoveRepo, (_event, url) => runtime.removeRepo(String(url)));
|
||||
handle(channels.animeBrowserPlayEpisode, (_event, request) =>
|
||||
runtime.playEpisode(request as AnimeBrowserPlayRequest),
|
||||
runtime.playEpisode(toPlayRequest(request)),
|
||||
);
|
||||
handle(channels.animeBrowserQueueEpisode, (_event, request) =>
|
||||
runtime.queueEpisode(toPlayRequest(request)),
|
||||
);
|
||||
handle(channels.animeBrowserDequeueEpisode, (_event, sourceId, episodeUrl) =>
|
||||
runtime.dequeueEpisode(String(sourceId ?? ''), String(episodeUrl ?? '')),
|
||||
);
|
||||
handle(channels.animeBrowserClearQueue, () => runtime.clearQueue());
|
||||
handle(channels.animeBrowserGetQueue, () => runtime.getQueue());
|
||||
handle(channels.animeBrowserIsPlaying, () => runtime.isPlaying());
|
||||
handle(channels.animeBrowserGetPreferences, (_event, sourceId) =>
|
||||
runtime.getPreferences(String(sourceId)),
|
||||
);
|
||||
@@ -70,6 +79,24 @@ export function registerAnimeBrowserIpcHandlers(deps: AnimeBrowserIpcDeps): void
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce a play (or queue) request. A queued request is held until its turn
|
||||
* comes, so a bad field would surface long after the click that sent it.
|
||||
*/
|
||||
function toPlayRequest(value: unknown): AnimeBrowserPlayRequest {
|
||||
const request = (value ?? {}) as Partial<AnimeBrowserPlayRequest>;
|
||||
return {
|
||||
sourceId: String(request.sourceId ?? ''),
|
||||
animeUrl: String(request.animeUrl ?? ''),
|
||||
animeTitle: String(request.animeTitle ?? ''),
|
||||
episodeUrl: String(request.episodeUrl ?? ''),
|
||||
episodeName: String(request.episodeName ?? ''),
|
||||
// NaN and Infinity are numbers as far as typeof is concerned, and either
|
||||
// one would reach the stats row as a nonsense episode number.
|
||||
episodeNumber: Number.isFinite(request.episodeNumber) ? request.episodeNumber! : null,
|
||||
};
|
||||
}
|
||||
|
||||
/** Coerce a watch-state request; the renderer's arrays arrive untyped. */
|
||||
function toWatchStateRequest(value: unknown): AnimeBrowserWatchStateRequest {
|
||||
const request = (value ?? {}) as Partial<AnimeBrowserWatchStateRequest>;
|
||||
|
||||
Reference in New Issue
Block a user