Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd13f0f19 | ||
|
|
144470a413 |
+10
-9
@@ -149,11 +149,16 @@ export type Bibliothek = {
|
|||||||
extraVersionCount: number;
|
extraVersionCount: number;
|
||||||
};
|
};
|
||||||
export type Seite<T> = { items: T[]; total: number };
|
export type Seite<T> = { items: T[]; total: number };
|
||||||
export type Lieferung = {
|
|
||||||
method: 'directFile' | 'remux'; reasons: string[];
|
/* **Herunterladen gibt es im Browser nicht mehr.**
|
||||||
audioToEncode: number[]; subtitlesDropped: number[];
|
*
|
||||||
packageState: string; progress: number; error: string | null;
|
* 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 = {
|
export type Scanstand = {
|
||||||
state: string; filesFound: number | null; added: number | null;
|
state: string; filesFound: number | null; added: number | null;
|
||||||
removed: number | null; unrecognised: number | null; probed: 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 film = (id: string) => anfrage<Film>(`/movies/${id}`);
|
||||||
export const serie = (id: string) => anfrage<Serie>(`/series/${id}`);
|
export const serie = (id: string) => anfrage<Serie>(`/series/${id}`);
|
||||||
|
|
||||||
export const lieferung = (datei: string) => anfrage<Lieferung>(`/files/${datei}/delivery`);
|
|
||||||
|
|
||||||
/* ---------- Stand je Benutzer ---------- */
|
/* ---------- Stand je Benutzer ---------- */
|
||||||
|
|
||||||
export const weiterschauen = (take = 20) =>
|
export const weiterschauen = (take = 20) =>
|
||||||
@@ -614,8 +617,6 @@ export const favorit = (id: string, wert: boolean) =>
|
|||||||
method: 'PUT', body: JSON.stringify({ favourite: wert }),
|
method: 'PUT', body: JSON.stringify({ favourite: wert }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadAdresse = (datei: string) => `/api/v1/files/${datei}/download`;
|
|
||||||
|
|
||||||
/* ---------- Strom ---------- */
|
/* ---------- Strom ---------- */
|
||||||
|
|
||||||
/** Eine wählbare Spur, so wie sie im Menü steht. */
|
/** Eine wählbare Spur, so wie sie im Menü steht. */
|
||||||
|
|||||||
@@ -838,6 +838,26 @@ textarea.feld {
|
|||||||
}
|
}
|
||||||
.pl-video { width: 100%; height: 100%; object-fit: contain; background: #000; }
|
.pl-video { width: 100%; height: 100%; object-fit: contain; background: #000; }
|
||||||
|
|
||||||
|
/* **Das Bild füllt die Fläche, dann sitzt es auch mittig.**
|
||||||
|
*
|
||||||
|
* Gemessen an einem 21:9-Film auf einem 1024×768-Fenster: die Hülle war
|
||||||
|
* 768 hoch, der Videokasten 459 — und er klebte bei oben 0. Die Breite
|
||||||
|
* stimmte, die Höhe nicht, und der schwarze Rand lag ganz unten statt
|
||||||
|
* gleichmässig oben und unten. Im Fenster fällt das kaum auf, im Vollbild
|
||||||
|
* sofort.
|
||||||
|
*
|
||||||
|
* **Woran es lag.** Die Hülle ist ein Raster mit `place-items: center`.
|
||||||
|
* Damit wird das Videoelement nicht gedehnt, und sein `height: 100%` löst
|
||||||
|
* sich gegen eine Zeile auf, die ihre Höhe wiederum aus dem Element nimmt —
|
||||||
|
* die Prozentangabe hatte nichts, woran sie sich halten konnte.
|
||||||
|
*
|
||||||
|
* Absolut über die ganze Hülle nimmt die Frage weg: `object-fit: contain`
|
||||||
|
* rechnet dann gegen den vollen Bildschirm und legt den schwarzen Rand von
|
||||||
|
* selbst zu gleichen Teilen oben und unten. Die kleine Leiste positioniert
|
||||||
|
* dasselbe Element ohnehin absolut und bleibt unberührt — deshalb das
|
||||||
|
* `:not(.pl-klein)`. */
|
||||||
|
.pl:not(.pl-klein) .pl-video { position: absolute; inset: 0; }
|
||||||
|
|
||||||
.pl-dreher { position: absolute; margin: 0; }
|
.pl-dreher { position: absolute; margin: 0; }
|
||||||
|
|
||||||
/* Kopf und Leiste blenden sich weg, während etwas läuft. */
|
/* Kopf und Leiste blenden sich weg, während etwas läuft. */
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ApiFehler,
|
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,
|
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,
|
type Mitwirkender, type Spur, aehnliche,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import {
|
import {
|
||||||
@@ -313,10 +313,12 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
|||||||
const tonwahl = tonspurwahl(datei);
|
const tonwahl = tonspurwahl(datei);
|
||||||
if (tonwahl) reihe.appendChild(tonwahl);
|
if (tonwahl) reihe.appendChild(tonwahl);
|
||||||
|
|
||||||
const laden = knopf('Herunterladen', 'btn');
|
// **Kein „Herunterladen" im Browser.**
|
||||||
laden.prepend(icon('download'));
|
//
|
||||||
laden.onclick = () => void herunterladen(datei, laden, hinweisfeld);
|
// Hier stand ein Knopf, der die Datei als MP4 herausgab. Das ist etwas
|
||||||
reihe.appendChild(laden);
|
// 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,
|
// **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;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Abspielen und Herunterladen ---------- */
|
/* ---------- Abspielen ---------- */
|
||||||
|
|
||||||
/** 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Startet die Wiedergabe — ohne Vorlauf, ohne Warten.
|
/** 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 ---------- */
|
/* ---------- Zuordnung korrigieren ---------- */
|
||||||
|
|
||||||
function zuordnungsbereich(titel: string, jahr: number | null): HTMLElement {
|
function zuordnungsbereich(titel: string, jahr: number | null): HTMLElement {
|
||||||
|
|||||||
Reference in New Issue
Block a user