Unten steht jetzt nur noch der Scrubber, wie in der App
Der strukturelle Unterschied, den ich übersehen hatte: in der iPad-App liegt unten nichts als der Regler über die volle Breite, darunter Zeit links und Restzeit rechts. Bei mir standen Zeit, Regler und Restzeit nebeneinander — der Regler wirkte dadurch wie ein Formularfeld zwischen zwei Beschriftungen — und darunter lag noch eine Werkzeugleiste mit Lautstärkeregler und Tempo-Auswahlfeld, die es in der App gar nicht gibt. Alles Einstellbare sitzt jetzt oben rechts beisammen: Tonspur, Untertitel, Qualität, Geschwindigkeit, Ton, Vollbild. Das Tempo als Liste wie die anderen; ein Auswahlfeld mitten in der Bedienleiste war das einzige Bauteil, das nach Formular aussah. Die Lautstärke über Mausrad auf dem Tonknopf und über die Pfeiltasten. Und ein Fehler, der genau das verhinderte, was gewünscht war: Die Bedienfläche lag als Ganzes über dem Bild und schluckte jeden Klick. "Ins Bild tippen hält an" funktionierte damit nur, solange die Bedienung unsichtbar war — also fast nie. Jetzt nehmen nur ihre Knöpfe Klicks entgegen. Nachgemessen: Klick pausiert, Pfeil rechts spult 30 Sekunden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
24e40f7403
commit
6d8440ede8
+46
-37
@@ -246,12 +246,19 @@ img { display: block; }
|
|||||||
}
|
}
|
||||||
#player video { width: 100%; height: 100%; object-fit: contain; background: #000; cursor: pointer; }
|
#player video { width: 100%; height: 100%; object-fit: contain; background: #000; cursor: pointer; }
|
||||||
|
|
||||||
|
/* **Die Fläche selbst nimmt keine Klicks entgegen, nur ihre Knöpfe.**
|
||||||
|
Sonst liegt sie über dem Bild und schluckt jeden Klick — und „ins Bild
|
||||||
|
tippen hält an" funktioniert genau dann nicht, wenn die Bedienung zu
|
||||||
|
sehen ist, also fast immer. */
|
||||||
.pl-chrome {
|
.pl-chrome {
|
||||||
position: absolute; inset: 0; opacity: 0; pointer-events: none;
|
position: absolute; inset: 0; opacity: 0; pointer-events: none;
|
||||||
transition: opacity .25s ease;
|
transition: opacity .25s ease;
|
||||||
display: grid; grid-template-rows: auto 1fr auto;
|
display: grid; grid-template-rows: auto 1fr auto;
|
||||||
}
|
}
|
||||||
.pl-chrome.on { opacity: 1; pointer-events: auto; }
|
.pl-chrome.on { opacity: 1; }
|
||||||
|
.pl-chrome.on button,
|
||||||
|
.pl-chrome.on .pl-track,
|
||||||
|
.pl-chrome.on select { pointer-events: auto; }
|
||||||
|
|
||||||
/* Zwei Verläufe statt einer Fläche: die Mitte bleibt frei, und genau dort
|
/* Zwei Verläufe statt einer Fläche: die Mitte bleibt frei, und genau dort
|
||||||
ist das Bild. */
|
ist das Bild. */
|
||||||
@@ -315,15 +322,18 @@ img { display: block; }
|
|||||||
beides unlesbar, und die Zahl ist die eigentliche Auskunft. */
|
beides unlesbar, und die Zahl ist die eigentliche Auskunft. */
|
||||||
#pl-back, #pl-fwd { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
#pl-back, #pl-fwd { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
.pl-scrub { display: flex; align-items: center; gap: var(--s-regular); }
|
/* Wie in der App: der Regler über die **volle Breite**, die Zeiten darunter.
|
||||||
.pl-scrub span {
|
Nebeneinander gestellt nimmt er in der Mitte Platz weg und wirkt wie ein
|
||||||
font-size: 13px; color: var(--text-2); font-variant-numeric: tabular-nums;
|
Formularfeld zwischen zwei Beschriftungen. */
|
||||||
min-width: 52px;
|
.pl-times {
|
||||||
|
display: flex; justify-content: space-between;
|
||||||
|
margin-top: var(--s-tight);
|
||||||
|
font-size: 13px; color: rgba(255,255,255,.8);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
.pl-scrub span:last-child { text-align: right; }
|
|
||||||
|
|
||||||
.pl-track {
|
.pl-track {
|
||||||
position: relative; flex: 1; height: 22px; cursor: pointer;
|
position: relative; width: 100%; height: 22px; cursor: pointer;
|
||||||
display: flex; align-items: center;
|
display: flex; align-items: center;
|
||||||
}
|
}
|
||||||
.pl-track::before {
|
.pl-track::before {
|
||||||
@@ -341,14 +351,7 @@ img { display: block; }
|
|||||||
}
|
}
|
||||||
.pl-track:hover i { transform: translateX(-50%) scale(1.25); }
|
.pl-track:hover i { transform: translateX(-50%) scale(1.25); }
|
||||||
|
|
||||||
.pl-tools {
|
|
||||||
display: flex; align-items: center; gap: var(--s-regular);
|
|
||||||
margin-top: var(--s-snug);
|
|
||||||
}
|
|
||||||
.pl-spacer { flex: 1; }
|
.pl-spacer { flex: 1; }
|
||||||
.pl-tools input[type=range] { width: 92px; accent-color: var(--accent); }
|
|
||||||
.pl-rate { font-size: 13px; color: var(--text-2); display: flex; align-items: center; gap: var(--s-snug); }
|
|
||||||
.pl-rate .pick { width: auto; padding: 6px var(--s-regular); font-size: 13px; }
|
|
||||||
|
|
||||||
/* Während geladen wird, treten die Transportknöpfe zurück: sie tun ohnehin
|
/* Während geladen wird, treten die Transportknöpfe zurück: sie tun ohnehin
|
||||||
nichts, und der Kreisel lag vorher genau hinter dem größten von ihnen. */
|
nichts, und der Kreisel lag vorher genau hinter dem größten von ihnen. */
|
||||||
@@ -1600,6 +1603,9 @@ function openPlayer(item) {
|
|||||||
<button class="pl-icon" id="pl-audio" aria-label="Tonspur" hidden>${icon('waveform')}</button>
|
<button class="pl-icon" id="pl-audio" aria-label="Tonspur" hidden>${icon('waveform')}</button>
|
||||||
<button class="pl-icon" id="pl-subs" aria-label="Untertitel" hidden>${icon('captions')}</button>
|
<button class="pl-icon" id="pl-subs" aria-label="Untertitel" hidden>${icon('captions')}</button>
|
||||||
<button class="pl-icon pl-quality" id="pl-qual" aria-label="Qualität">Original</button>
|
<button class="pl-icon pl-quality" id="pl-qual" aria-label="Qualität">Original</button>
|
||||||
|
<button class="pl-icon" id="pl-speed" aria-label="Geschwindigkeit">${icon('speed')}</button>
|
||||||
|
<button class="pl-icon" id="pl-mute" aria-label="Ton aus">${icon('sound')}</button>
|
||||||
|
<button class="pl-icon" id="pl-full" aria-label="Vollbild">${icon('expand')}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-center">
|
<div class="pl-center">
|
||||||
@@ -1609,18 +1615,11 @@ function openPlayer(item) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-bottom">
|
<div class="pl-bottom">
|
||||||
<div class="pl-scrub">
|
|
||||||
<span id="pl-now">0:00</span>
|
|
||||||
<div class="pl-track" id="pl-track"><div class="pl-buf"></div><div class="pl-fill"></div><i></i></div>
|
<div class="pl-track" id="pl-track"><div class="pl-buf"></div><div class="pl-fill"></div><i></i></div>
|
||||||
|
<div class="pl-times">
|
||||||
|
<span id="pl-now">0:00</span>
|
||||||
<span id="pl-left">–:--</span>
|
<span id="pl-left">–:--</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-tools">
|
|
||||||
<button class="pl-icon" id="pl-mute" aria-label="Ton aus">${icon('sound')}</button>
|
|
||||||
<input type="range" id="pl-vol" min="0" max="1" step="0.05" value="1" aria-label="Lautstärke">
|
|
||||||
<span class="pl-spacer"></span>
|
|
||||||
<label class="pl-rate">Tempo <select class="pick" id="pl-rate"></select></label>
|
|
||||||
<button class="pl-icon" id="pl-full" aria-label="Vollbild">${icon('expand')}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-menu" id="pl-menu" hidden></div>
|
<div class="pl-menu" id="pl-menu" hidden></div>
|
||||||
@@ -1715,20 +1714,29 @@ function openPlayer(item) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Ton, Tempo, Vollbild ---
|
// --- Ton, Tempo, Vollbild ---
|
||||||
const vol = el2('pl-vol');
|
const paintTon = () => {
|
||||||
vol.oninput = () => { v.volume = +vol.value; v.muted = +vol.value === 0; };
|
el2('pl-mute').innerHTML = icon(v.muted || v.volume === 0 ? 'muted' : 'sound');
|
||||||
el2('pl-mute').onclick = () => {
|
el2('pl-mute').title = `Lautstärke ${Math.round(v.volume * 100)} %`;
|
||||||
v.muted = !v.muted;
|
|
||||||
el2('pl-mute').innerHTML = icon(v.muted ? 'muted' : 'sound');
|
|
||||||
};
|
};
|
||||||
|
el2('pl-mute').onclick = () => { v.muted = !v.muted; paintTon(); };
|
||||||
|
|
||||||
const rate = el2('pl-rate');
|
// Das Tempo als Liste wie Ton und Untertitel — ein Auswahlfeld mitten in
|
||||||
RATES.forEach((r) => {
|
// der Bedienleiste war das einzige Bauteil, das nach Formular aussah.
|
||||||
const o = document.createElement('option');
|
el2('pl-speed').onclick = () => zeigeListe(el2('pl-speed'), 'Geschwindigkeit',
|
||||||
o.value = r; o.textContent = r === 1 ? 'Normal' : r + '×'; o.selected = r === P.rate;
|
RATES.map((r) => ({
|
||||||
rate.appendChild(o);
|
text: r === 1 ? 'Normal' : r + '×',
|
||||||
});
|
gewaehlt: Math.abs(v.playbackRate - r) < 0.01,
|
||||||
rate.onchange = () => { v.playbackRate = +rate.value; };
|
waehlen: () => { v.playbackRate = r; closeMenu(); show(); },
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Lautstärke über das Mausrad auf dem Tonknopf und über die Pfeiltasten —
|
||||||
|
// ein Schieberegler in der Leiste steht in der App auch nicht.
|
||||||
|
el2('pl-mute').addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
v.volume = Math.min(1, Math.max(0, v.volume - Math.sign(e.deltaY) * 0.05));
|
||||||
|
v.muted = v.volume === 0;
|
||||||
|
paintTon();
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
el2('pl-full').onclick = () =>
|
el2('pl-full').onclick = () =>
|
||||||
document.fullscreenElement ? document.exitFullscreen() : layer.requestFullscreen?.();
|
document.fullscreenElement ? document.exitFullscreen() : layer.requestFullscreen?.();
|
||||||
@@ -1740,8 +1748,8 @@ function openPlayer(item) {
|
|||||||
' ': () => toggle(), k: () => toggle(),
|
' ': () => toggle(), k: () => toggle(),
|
||||||
ArrowLeft: () => { v.currentTime -= P.skipBackward; },
|
ArrowLeft: () => { v.currentTime -= P.skipBackward; },
|
||||||
ArrowRight: () => { v.currentTime += P.skipForward; },
|
ArrowRight: () => { v.currentTime += P.skipForward; },
|
||||||
ArrowUp: () => { v.volume = Math.min(1, v.volume + 0.1); vol.value = v.volume; },
|
ArrowUp: () => { v.volume = Math.min(1, v.volume + 0.1); paintTon(); },
|
||||||
ArrowDown: () => { v.volume = Math.max(0, v.volume - 0.1); vol.value = v.volume; },
|
ArrowDown: () => { v.volume = Math.max(0, v.volume - 0.1); paintTon(); },
|
||||||
f: () => el2('pl-full').click(),
|
f: () => el2('pl-full').click(),
|
||||||
Escape: () => close(),
|
Escape: () => close(),
|
||||||
};
|
};
|
||||||
@@ -2068,6 +2076,7 @@ function streamURL(path, query) {
|
|||||||
const ICONS = {
|
const ICONS = {
|
||||||
play: '<path d="M8 5.5v13l11-6.5z" fill="currentColor" stroke="none"/>',
|
play: '<path d="M8 5.5v13l11-6.5z" fill="currentColor" stroke="none"/>',
|
||||||
waveform: '<path d="M3 12h2M7 8v8M11 4.5v15M15 8v8M19 11h2"/>',
|
waveform: '<path d="M3 12h2M7 8v8M11 4.5v15M15 8v8M19 11h2"/>',
|
||||||
|
speed: '<path d="M12 20a8 8 0 100-16 8 8 0 000 16z"/><path d="M12 12l4-3"/>',
|
||||||
captions: '<rect x="3" y="5.5" width="18" height="13" rx="3"/><path d="M9.5 10.5a2.2 2.2 0 100 3M16 10.5a2.2 2.2 0 100 3"/>',
|
captions: '<rect x="3" y="5.5" width="18" height="13" rx="3"/><path d="M9.5 10.5a2.2 2.2 0 100 3M16 10.5a2.2 2.2 0 100 3"/>',
|
||||||
captionsOn: '<rect x="3" y="5.5" width="18" height="13" rx="3" fill="currentColor" stroke="none"/><path d="M9.5 10.5a2.2 2.2 0 100 3M16 10.5a2.2 2.2 0 100 3" stroke="#0B0B0D"/>',
|
captionsOn: '<rect x="3" y="5.5" width="18" height="13" rx="3" fill="currentColor" stroke="none"/><path d="M9.5 10.5a2.2 2.2 0 100 3M16 10.5a2.2 2.2 0 100 3" stroke="#0B0B0D"/>',
|
||||||
check: '<path d="M5 12.5l4.5 4.5L19 7.5"/>',
|
check: '<path d="M5 12.5l4.5 4.5L19 7.5"/>',
|
||||||
|
|||||||
Reference in New Issue
Block a user