feat: fullscreen carries across videos, and stays out of the way

In fullscreen, Next now keeps you there and shows nothing but the
picture: one video after another, no arrows, no header, no counter.

Three things had to change for that. The player was keyed on the video,
so every Next rebuilt the stage — and the stage is what is fullscreen.
It now survives, resetting its own per-video state instead of being
thrown away to get it.

The cleanup that ran on every change of source called exitFullscreen
outright. It exists for the Picture-in-Picture leak, which does need
handling per video; leaving fullscreen and dropping the source belong
to leaving the player, and are now only done there.

And resolving a stream set the source to nothing first, which unmounted
the element mid-fullscreen. The outgoing video is paused in place
instead, and the source goes straight from one address to the next.

The edge arrows are gone in fullscreen. The transport bar stays — it is
playback, not navigation — and fades on its own as before.

Also: Remove all downloads in the menu bar panel, which asks in the
window rather than in a panel that closes when you look away; and the
JavaScript round-trip logging is gone now that the scrape is settled.

Verified in the running app: three videos in a row without leaving
fullscreen, chrome faded to nothing, and Remove all reaching the
window's own "Delete every download?" with 50 downloads left untouched.
This commit is contained in:
Vincent Rozenberg
2026-09-04 01:56:08 +02:00
parent 41d638b232
commit 35f14ab044
4 changed files with 251 additions and 73 deletions
+3 -12
View File
@@ -129,15 +129,6 @@ async fn browser_youtube_url(app: &AppHandle) -> Result<String, String> {
/// Arc allows this out of the box. Chrome and its relatives ship with it off,
/// and say so in the error, which is passed straight back rather than being
/// flattened into "something went wrong".
pub async fn run_js_logged(app: &AppHandle, browser: &str, js: &str) -> Result<String, String> {
let r = run_js(browser, js).await;
match &r {
Ok(out) => log(app, &format!("js ok, {} chars back", out.len())),
Err(e) => log(app, &format!("js failed: {e}")),
}
r
}
pub async fn run_js(browser: &str, js: &str) -> Result<String, String> {
let safari = browser == "Safari";
let script = if safari {
@@ -211,7 +202,7 @@ pub async fn scrape_subscriptions(app: &AppHandle) -> Result<subscriptions::Scra
let mut last = 0usize;
let mut settled = 0;
for round in 0..40 {
let n = run_js_logged(app, &browser, subscriptions::SCROLL_JS)
let n = run_js(&browser, subscriptions::SCROLL_JS)
.await?
.parse::<usize>()
.unwrap_or(0);
@@ -232,7 +223,7 @@ pub async fn scrape_subscriptions(app: &AppHandle) -> Result<subscriptions::Scra
tokio::time::sleep(std::time::Duration::from_millis(900)).await;
}
let raw = run_js_logged(app, &browser, subscriptions::EXTRACT_JS).await?;
let raw = run_js(&browser, subscriptions::EXTRACT_JS).await?;
let rows = subscriptions::parse_rows(&raw);
log(app, &format!("scraped {} channels from {browser}", rows.len()));
if rows.is_empty() {
@@ -366,7 +357,7 @@ pub fn quit_app(app: AppHandle) {
/// The panel's own size. Fixed, because it is a menu: it does not resize.
const PANEL_W: f64 = 304.0;
const PANEL_H: f64 = 252.0;
const PANEL_H: f64 = 296.0;
/// Opens the panel under the menu bar icon.
///