Er packte um, was gar nicht am Umpacken lag

Der Browser bekam stillschweigend Apples Geräteprofil — und mit ihm HEVC, AC3
und EAC3, von denen er keines öffnet. Der Server packte die Datei
pflichtschuldig nach MP4 um, liess Bild und Ton dabei unberührt, und heraus
kam eine tadellose Datei, mit der Chrome nichts anfangen konnte. Umpacken
fasst das Bild nun einmal nicht an.

Jetzt sagt das Gerät selbst, was es kann, und es gibt einen zweiten Weg neben
der fertigen Datei: HLS, das nach Sekunden anfängt statt nach Minuten.
Dasselbe Rohr erledigt beides — mkv nach fMP4 mit „-c:v copy", 4K nach 480p
mit libx264 — und eine MP4, die der Browser ohnehin öffnet, geht ganz ohne
ffmpeg hinaus.

Dazu eine eigene Steuerleiste: Tonspur und Untertitel während der Wiedergabe
umschaltbar, vier Auflösungsstufen, Tastenkürzel, und bei einem Fehler ein
deutscher Satz statt eines schwarzen Bildes.

Gemessen statt vermutet: Chromium antwortet auf canPlayType für HLS mit
"maybe" und kann es dann doch nicht — deshalb entscheidet Hls.isSupported().
hls.js wiegt 372 KB und wird erst beim Abspielen geholt; die Oberfläche
bleibt bei 109.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-05 18:52:27 +02:00
co-authored by Claude Opus 5
parent 557708b769
commit 4853200d5c
16 changed files with 2894 additions and 65 deletions
+134 -3
View File
@@ -718,9 +718,140 @@ h2 { font-size: var(--t-abschnitt); font-weight: 700; letter-spacing: -.015em; m
.btn-gefahr { background: var(--destructive); color: #fff; font-weight: 600; }
.btn-gefahr:hover { background: var(--destructive-dim); }
/* Der Spieler über allem. */
.pl-fill {
position: fixed; inset: 0; z-index: 100; background: #000;
/* ---------- Der Spieler ----------
*
* **Eigene Steuerung statt der eingebauten.** Die Leiste des Browsers kennt
* weder Tonspuren noch Qualitätsstufen — und die Tonspurwahl ist der Grund,
* aus dem es diesen Server gibt. Ausserdem sah sie, wie der Benutzer sagte,
* „gefühlt von 1930" aus. */
.pl {
position: fixed; inset: 0; z-index: 150; background: #000;
display: grid; place-items: center; outline: none;
}
.pl-video { width: 100%; height: 100%; object-fit: contain; background: #000; }
.pl-dreher { position: absolute; margin: 0; }
/* Kopf und Leiste blenden sich weg, während etwas läuft. */
.pl-kopf, .pl-leiste {
position: absolute; left: 0; right: 0; z-index: 2;
display: flex; align-items: center; gap: var(--s-regular);
padding: 14px 18px;
transition: opacity .2s ease-out, transform .2s ease-out;
}
.pl-kopf {
top: 0; justify-content: space-between;
background: linear-gradient(to bottom, rgba(0, 0, 0, .78), transparent);
padding-top: max(14px, env(safe-area-inset-top));
}
.pl-leiste {
bottom: 0; flex-direction: column; align-items: stretch; gap: 8px;
background: linear-gradient(to top, rgba(0, 0, 0, .86), transparent);
padding-bottom: max(14px, env(safe-area-inset-bottom));
}
.pl-still .pl-kopf { opacity: 0; transform: translateY(-8px); pointer-events: none; }
.pl-still .pl-leiste { opacity: 0; transform: translateY(8px); pointer-events: none; }
.pl-still .pl-video { cursor: none; }
.pl-titel {
font-size: 15px; font-weight: 600; color: #fff;
text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pl-unten { display: flex; align-items: center; justify-content: space-between; gap: var(--s-regular); }
.pl-gruppe { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* **44 Bildpunkte, nicht 32.** Darunter trifft ein Finger daneben, und ein
Fehlgriff auf der Zeitleiste wirft einen aus dem Film. */
.pl-rund {
display: grid; place-items: center;
width: 44px; height: 44px; flex: 0 0 auto;
border: 0; border-radius: 50%; background: transparent; color: #fff;
cursor: pointer; transition: background .15s;
}
.pl-rund:hover, .pl-rund:focus-visible { background: rgba(255, 255, 255, .16); }
.pl-rund svg { width: 22px; height: 22px; }
.pl-stand, .pl-rest {
font-size: 13px; color: rgba(255, 255, 255, .88);
font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Zeit- und Lautstärkeregler: dieselbe Form, verschiedene Breite. */
.pl-zeit, .pl-laut {
-webkit-appearance: none; appearance: none;
height: 20px; background: transparent; cursor: pointer; margin: 0;
}
.pl-zeit { width: 100%; }
.pl-laut { width: 84px; flex: 0 0 auto; }
.pl-zeit::-webkit-slider-runnable-track, .pl-laut::-webkit-slider-runnable-track {
height: 4px; border-radius: 2px;
background: linear-gradient(to right, var(--accent) var(--anteil, 0%),
rgba(255, 255, 255, .28) var(--anteil, 0%));
}
.pl-laut::-webkit-slider-runnable-track { background: rgba(255, 255, 255, .28); }
.pl-zeit::-moz-range-track, .pl-laut::-moz-range-track {
height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .28);
}
.pl-zeit::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--accent); }
.pl-zeit::-webkit-slider-thumb, .pl-laut::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 14px; height: 14px; margin-top: -5px;
border-radius: 50%; background: #fff; border: 0;
box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.pl-zeit::-moz-range-thumb, .pl-laut::-moz-range-thumb {
width: 14px; height: 14px; border-radius: 50%; background: #fff; border: 0;
}
.pl-zeit:focus-visible, .pl-laut:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Die Menüs gehen nach oben auf — nach unten wäre ausserhalb des Bildes. */
.pl-menue { position: relative; }
.pl-liste {
position: absolute; bottom: calc(100% + 8px); right: 0;
display: none; flex-direction: column; min-width: 180px;
padding: 6px; background: rgba(20, 20, 23, .96);
border: 1px solid var(--line); border-radius: var(--r-karte);
box-shadow: 0 12px 36px rgba(0, 0, 0, .6);
max-height: 50vh; overflow-y: auto;
}
.pl-menue.auf .pl-liste { display: flex; }
.pl-punkt {
display: flex; align-items: center; gap: 8px;
background: none; border: 0; color: var(--text);
padding: 9px 12px; border-radius: var(--r-control);
font-size: 14px; text-align: left; cursor: pointer; white-space: nowrap;
}
.pl-punkt:hover, .pl-punkt:focus-visible { background: var(--raised); }
.pl-punkt .icon { width: 16px; height: 16px; flex: 0 0 auto; }
.pl-punkt .icon svg { width: 16px; height: 16px; }
/* Ohne Haken bleibt der Platz stehen — sonst springt die Beschriftung. */
.pl-punkt:not(.an) { padding-left: 36px; }
.pl-meldung {
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
z-index: 3; max-width: min(520px, calc(100vw - 48px));
padding: var(--s-regular) var(--s-section);
background: rgba(20, 20, 23, .96); color: var(--text);
border: 1px solid rgba(229, 72, 77, .45); border-radius: var(--r-karte);
text-align: center; line-height: 1.5; font-size: 15px;
}
/* Hinter dem Spieler soll nichts scrollen. */
body.pl-offen { overflow: hidden; }
@media (max-width: 640px) {
.pl-laut { display: none; }
.pl-rest { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.pl-kopf, .pl-leiste { transition: none; }
}
/* ---------- Schmale Bildschirme ---------- */