visualizer fixes
This commit is contained in:
@@ -383,7 +383,13 @@ export class MpvBackend implements AudioBackend {
|
||||
this.proc = Bun.spawn(
|
||||
[
|
||||
"mpv",
|
||||
"--no-video",
|
||||
// --vo=null (not --no-video): the albumart track must stay the
|
||||
// CURRENT video track or macOS Now Playing shows no artwork.
|
||||
// --no-video drops it to unselected (albumart:true, selected:false),
|
||||
// so the system media center renders no cover. --vo=null is equally
|
||||
// headless — no window, no rendering — but keeps the cover current
|
||||
// so Now Playing gets the art.
|
||||
"--vo=null",
|
||||
"--no-terminal",
|
||||
"--really-quiet",
|
||||
// Stay alive after finishing/unloading files; PodTUI owns one mpv
|
||||
|
||||
22
src/utils/feed-resolve.ts
Normal file
22
src/utils/feed-resolve.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Feed resolution for an episode. `episode.podcastId` is the RSS feed url
|
||||
* (rss-parser), which differs from `podcast.id` (the iTunes directory id) for
|
||||
* iTunes-added shows — so a strict `podcast.id` match fails and the feed (and
|
||||
* its cover) is never found. Match by podcast id, then feed url, then episode
|
||||
* membership, in that order.
|
||||
*/
|
||||
|
||||
import type { Feed } from "../types/feed";
|
||||
import type { Episode } from "../types/episode";
|
||||
|
||||
/** The feed backing `episode`, by podcast id, then feed url, then membership. */
|
||||
export function feedForEpisode(
|
||||
feeds: Feed[],
|
||||
episode: Episode,
|
||||
): Feed | undefined {
|
||||
return (
|
||||
feeds.find((f) => f.podcast.id === episode.podcastId) ??
|
||||
feeds.find((f) => f.podcast.feedUrl === episode.podcastId) ??
|
||||
feeds.find((f) => f.episodes.some((e) => e.id === episode.id))
|
||||
);
|
||||
}
|
||||
47
src/utils/terminal-recovery.ts
Normal file
47
src/utils/terminal-recovery.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Terminal recovery for suspend/resume and system sleep/wake cycles.
|
||||
*
|
||||
* The renderer enters the alternate screen, enables raw mode and attaches its
|
||||
* stdin listener exactly once at startup. The diff renderer also keeps
|
||||
* `currentRenderBuffer` as its model of what is on screen and only writes the
|
||||
* cells that changed against that model.
|
||||
*
|
||||
* When the session is suspended (Ctrl-Z) or the system sleeps and the process
|
||||
* is later resumed, the terminal screen can desync from that model: the stale
|
||||
* buffer makes the diff rewrite only "changed" cells, leaving garbled or
|
||||
* previous content on screen, and the raw-mode / stdin wiring can be dropped.
|
||||
* The result is a frozen, non-interactive screen that shows raw markup instead
|
||||
* of the UI.
|
||||
*
|
||||
* SIGCONT is the standard signal delivered when a stopped process resumes.
|
||||
* On it we call `renderer.resume()`, the library's own recovery path, which:
|
||||
* - re-enters the alternate screen (native resumeRenderer)
|
||||
* - re-enables raw mode, re-attaches the stdin listener and flushes stale input
|
||||
* - clears currentRenderBuffer so the next frame performs a full repaint
|
||||
*/
|
||||
|
||||
import type { CliRenderer } from "@opentui/core";
|
||||
|
||||
/**
|
||||
* Register a SIGCONT handler that recovers the terminal after suspend/resume.
|
||||
*
|
||||
* @param renderer - the active CLI renderer
|
||||
* @returns cleanup function that removes the handler
|
||||
*/
|
||||
export function setupTerminalRecovery(renderer: CliRenderer): () => void {
|
||||
const onContinue = () => {
|
||||
// Best-effort: resume() re-establishes terminal state and forces a full
|
||||
// repaint by clearing the render buffer. Idempotent if fired repeatedly.
|
||||
try {
|
||||
renderer.resume();
|
||||
} catch {
|
||||
// recovery is best-effort; never crash on the recovery path itself
|
||||
}
|
||||
};
|
||||
|
||||
process.on("SIGCONT", onContinue);
|
||||
|
||||
return () => {
|
||||
process.off("SIGCONT", onContinue);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user