Im Browser gibt es keine Datei mehr, nur den Strom

Auf der Detailseite stand ein Knopf „Herunterladen", der die Datei als MP4
herausgab. Das ist etwas anderes, als einen Strom zu zeigen: wer eine Datei
bekommt, hat sie und kann sie weitergeben — und das soll dieser Server
nicht anbieten.

Weg sind der Knopf, das Bereitstellen davor und die beiden Stellen in der
Schnittstelle, die nur dafür da waren.

**Der Endpunkt bleibt.** Die Apps holen sich eine Mitnahme über
`/api/v1/files/{id}/download`, und dort ist sie an ein Gerät gebunden statt
an einen Ordner. Das heisst aber auch: wer angemeldet ist und die Adresse
kennt, kommt weiterhin an die Datei. Soll das zu — etwa als Recht je
Benutzer oder als Schalter für den ganzen Server —, ist das ein eigener
Schritt und keine Zeile in dieser Änderung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-08 21:07:20 +02:00
co-authored by Claude Opus 5
parent 532473b1f9
commit 144470a413
2 changed files with 19 additions and 65 deletions
+10 -9
View File
@@ -149,11 +149,16 @@ export type Bibliothek = {
extraVersionCount: number;
};
export type Seite<T> = { items: T[]; total: number };
export type Lieferung = {
method: 'directFile' | 'remux'; reasons: string[];
audioToEncode: number[]; subtitlesDropped: number[];
packageState: string; progress: number; error: string | null;
};
/* **Herunterladen gibt es im Browser nicht mehr.**
*
* Hier standen `Lieferung`, `lieferung()` und `downloadAdresse()`. Die
* Detailseite bot damit einen Knopf an, der die Datei als MP4 herausgab —
* und das ist etwas anderes, als einen Strom zu zeigen: wer eine Datei
* bekommt, hat sie und kann sie weitergeben. Der Endpunkt bleibt, weil die
* Apps ihn für die Mitnahme aufs Gerät brauchen; die Weboberfläche bietet
* ihn nicht mehr an. */
export type Scanstand = {
state: string; filesFound: number | null; added: number | null;
removed: number | null; unrecognised: number | null; probed: number | null;
@@ -561,8 +566,6 @@ export const folgen = (serie: string, staffel?: number) =>
export const film = (id: string) => anfrage<Film>(`/movies/${id}`);
export const serie = (id: string) => anfrage<Serie>(`/series/${id}`);
export const lieferung = (datei: string) => anfrage<Lieferung>(`/files/${datei}/delivery`);
/* ---------- Stand je Benutzer ---------- */
export const weiterschauen = (take = 20) =>
@@ -614,8 +617,6 @@ export const favorit = (id: string, wert: boolean) =>
method: 'PUT', body: JSON.stringify({ favourite: wert }),
});
export const downloadAdresse = (datei: string) => `/api/v1/files/${datei}/download`;
/* ---------- Strom ---------- */
/** Eine wählbare Spur, so wie sie im Menü steht. */
+9 -56
View File
@@ -1,7 +1,7 @@
import { ApiFehler,
film as filmLaden, serie as serieLaden, folgen, lieferung, downloadAdresse,
film as filmLaden, serie as serieLaden, folgen,
vorschlaege, zuordnen, bildAdresse, istAdmin, gesehen, favorit, weglegen,
type Film, type Serie, type Folge, type Datei, type Lieferung, type Stand,
type Film, type Serie, type Folge, type Datei, type Stand,
type Mitwirkender, type Spur, aehnliche,
} from '../api';
import {
@@ -313,10 +313,12 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
const tonwahl = tonspurwahl(datei);
if (tonwahl) reihe.appendChild(tonwahl);
const laden = knopf('Herunterladen', 'btn');
laden.prepend(icon('download'));
laden.onclick = () => void herunterladen(datei, laden, hinweisfeld);
reihe.appendChild(laden);
// **Kein „Herunterladen" im Browser.**
//
// Hier stand ein Knopf, der die Datei als MP4 herausgab. Das ist etwas
// anderes, als einen Strom zu zeigen: wer eine Datei bekommt, hat sie
// und kann sie weitergeben. Die Apps holen sich eine Mitnahme
// weiterhin über `/download` — dort ist sie an ein Gerät gebunden.
}
// **Runde Knöpfe ohne Beschriftung, wie bei Plex.** Gesehen, Favorit,
@@ -743,41 +745,7 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
return block;
}
/* ---------- Abspielen und Herunterladen ---------- */
/** Sorgt dafür, dass die Datei abholbereit ist — packt um, falls nötig. */
/** Baut die Datei für den **Download** auf — dort ist eine fertige richtig.
*
* **Nicht mehr fürs Abspielen.** Dafür gibt es den Strom, der sofort
* anfängt; hier auf eine vollständige MP4 zu warten hiess bei einem
* 4K-Film: Minuten, in denen sichtbar nichts geschieht. */
async function bereitstellen(datei: Datei, hinweisfeld: HTMLElement): Promise<boolean> {
let auskunft: Lieferung;
try { auskunft = await lieferung(datei.id); }
catch (fehler) { hinweisfeld.textContent = meldung(fehler); return false; }
if (auskunft.method === 'directFile') return true;
if (auskunft.packageState === 'ready') return true;
// **Hier steht, was passieren wird, bevor es passiert.**
const gruende: string[] = [];
if (auskunft.audioToEncode.length) gruende.push(`${auskunft.audioToEncode.length} Tonspur(en) werden neu gerechnet`);
if (auskunft.subtitlesDropped.length) gruende.push(`${auskunft.subtitlesDropped.length} Bilduntertitel können nicht mit`);
hinweisfeld.textContent = `Wird in eine MP4 umgepackt${gruende.length ? ` — ${gruende.join(', ')}` : ''}…`;
for (let i = 0; i < 600; i++) {
const antwort = await fetch(downloadAdresse(datei.id), { method: 'HEAD' });
if (antwort.ok) { hinweisfeld.textContent = ''; return true; }
if (antwort.status !== 202) { hinweisfeld.textContent = `Umpacken gescheitert (${antwort.status}).`; return false; }
const wie = await lieferung(datei.id);
if (wie.error) { hinweisfeld.textContent = wie.error; return false; }
hinweisfeld.textContent = `Wird umgepackt… ${Math.round(wie.progress * 100)} %`;
await new Promise(r => setTimeout(r, 1000));
}
hinweisfeld.textContent = 'Das Umpacken dauert ungewöhnlich lange.';
return false;
}
/* ---------- Abspielen ---------- */
/** Startet die Wiedergabe — ohne Vorlauf, ohne Warten.
*
@@ -796,21 +764,6 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
});
}
async function herunterladen(
datei: Datei, knopfDavon: HTMLButtonElement, hinweisfeld: HTMLElement,
): Promise<void> {
knopfDavon.disabled = true;
if (await bereitstellen(datei, hinweisfeld)) {
// Ein echter Link, kein Blob: der Browser lädt im Hintergrund und kann
// fortsetzen, statt den ganzen Film in den Speicher zu nehmen.
const a = document.createElement('a');
a.href = downloadAdresse(datei.id);
a.download = '';
a.click();
}
knopfDavon.disabled = false;
}
/* ---------- Zuordnung korrigieren ---------- */
function zuordnungsbereich(titel: string, jahr: number | null): HTMLElement {