Stream lässt sich nicht mehr anhalten

Was der Server sendet, läuft. Wer anhält, fällt aus dem Gleichlauf und
müsste den Rückstand hinterher mit erhöhtem Tempo aufholen. Statt der
Browser-Steuerung gibt es jetzt eine eigene Leiste: Lautstärke, Stumm und
Vollbild. Pausieren, Spulen und Geschwindigkeit fehlen darin.

Abgedeckt sind alle Wege zum Anhalten, weil jeder davon ein pause-Ereignis
auslöst: Rechtsklick-Menü, Bild-in-Bild, Medientasten der Tastatur. Die
Leertaste wird zusätzlich abgefangen, sie käme sonst dem Video zuvor.

Der Ton startet stumm, sonst verweigert der Browser die automatische
Wiedergabe. Ein Klick auf "Ton einschalten" gilt als Geste und schaltet ihn
frei.

Beim Tonweg zwei Fehler behoben:
- Der Loopback zum Mithören nahm das Ende der aufrufenden Shell mit. Mitten
  im Film hörte dann niemand mehr etwas. setsid löst ihn davon.
- Der Lautstärkeregler fasst seit der Umstellung die neue Senke an, nicht
  mehr das Ausgabegerät. Stand das auf halber Kraft, war bei "voll
  aufgedreht" Schluss bei der Hälfte. Jetzt geht das Gerät auf Anschlag und
  die Senke übernimmt den gewohnten Pegel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XVnRop5c3or3B8xE2hpgZ
This commit is contained in:
team3
2026-08-31 13:32:43 +02:00
parent af66ae6e1c
commit 56c3f10026
2 changed files with 199 additions and 15 deletions

View File

@@ -11,8 +11,50 @@ const video = ref(null)
const zustand = ref('lade')
const fehler = ref('')
// Stumm starten, sonst verweigert der Browser die automatische Wiedergabe.
// Den Ton schaltet der Zuschauer selbst ein — das gilt als Geste.
const stumm = ref(true)
const lautstaerke = ref(1)
const vollbild = ref(false)
let hls = null
// Was der Server sendet, läuft. Wer anhält, fällt aus dem Gleichlauf und
// müsste den Rückstand hinterher mit erhöhtem Tempo aufholen. Also lassen
// wir das Anhalten gar nicht erst zu.
function weiterlaufen() {
const v = video.value
if (!v || zustand.value !== 'laeuft') return
v.play().catch(() => {
// Nur wenn der Browser sich quer stellt, fragen wir den Zuschauer
zustand.value = 'bereit'
})
}
function tonAn() {
stumm.value = false
video.value.muted = false
video.value.volume = lautstaerke.value
weiterlaufen()
}
function lautstaerkeSetzen(wert) {
lautstaerke.value = wert
video.value.volume = wert
if (wert > 0 && stumm.value) tonAn()
}
function vollbildUmschalten() {
const halter = video.value?.parentElement
if (document.fullscreenElement) document.exitFullscreen()
else halter?.requestFullscreen?.()
}
// Die Leertaste hält sonst das Video an, sobald es den Fokus hat
function tasteAbfangen(e) {
if (e.code === 'Space' && e.target === video.value) e.preventDefault()
}
function verbinden() {
const quelle = '/stream/filmabend/index.m3u8'
@@ -74,13 +116,23 @@ function verbinden() {
}
function starten() {
video.value?.play()
zustand.value = 'laeuft'
video.value?.play()
}
onMounted(verbinden)
function vollbildGewechselt() {
vollbild.value = !!document.fullscreenElement
}
onMounted(() => {
verbinden()
document.addEventListener('fullscreenchange', vollbildGewechselt)
window.addEventListener('keydown', tasteAbfangen)
})
onUnmounted(() => {
document.removeEventListener('fullscreenchange', vollbildGewechselt)
window.removeEventListener('keydown', tasteAbfangen)
hls?.destroy()
hls = null
})
@@ -92,14 +144,45 @@ onUnmounted(() => {
<div class="buehne">
<!-- Ohne Stream zeigt der Browser nur seinen eigenen Ladekringel und
verrät nicht, warum nichts kommt -->
<video
v-show="zustand === 'laeuft' || zustand === 'bereit'"
ref="video"
class="player"
controls
playsinline
muted
></video>
<div v-show="zustand === 'laeuft' || zustand === 'bereit'" class="rahmen">
<video
ref="video"
class="player"
playsinline
muted
@pause="weiterlaufen"
@click.prevent
></video>
<div class="leiste">
<button
type="button"
class="knopf"
:title="stumm ? 'Ton einschalten' : 'Stumm schalten'"
@click="stumm ? tonAn() : (stumm = true, video.muted = true)"
>
{{ stumm ? '🔇' : '🔊' }}
</button>
<input
type="range"
class="regler"
min="0"
max="1"
step="0.02"
:value="stumm ? 0 : lautstaerke"
@input="lautstaerkeSetzen(Number($event.target.value))"
/>
<span class="live">live</span>
<button
type="button"
class="knopf"
:title="vollbild ? 'Vollbild beenden' : 'Vollbild'"
@click="vollbildUmschalten"
>
{{ vollbild ? '⤡' : '⤢' }}
</button>
</div>
</div>
<div v-if="zustand === 'wartet' || zustand === 'lade'" class="platzhalter">
<p class="gross">
@@ -111,6 +194,10 @@ onUnmounted(() => {
Wiedergabe starten
</button>
<button v-else-if="stumm && zustand === 'laeuft'" class="start" type="button" @click="tonAn">
Ton einschalten
</button>
<p v-if="fehler" class="note error">{{ fehler }}</p>
</div>
@@ -127,10 +214,84 @@ onUnmounted(() => {
min-width: 0;
}
.rahmen {
position: relative;
/* Sonst ragt das Bild unter den Fensterrand und die Leiste mit ihm */
max-height: calc(100vh - 5.5rem);
aspect-ratio: 16 / 9;
margin: 0 auto;
background: #000;
border-radius: 12px;
}
/* Im Vollbild füllt der Rahmen den Schirm, das Video bleibt im Format */
.rahmen:fullscreen {
max-height: none;
width: 100vw;
height: 100vh;
border-radius: 0;
}
.rahmen:fullscreen .player {
border: 0;
border-radius: 0;
}
.leiste {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 0.6rem;
align-items: center;
padding: 0.5rem 0.8rem;
opacity: 0;
transition: opacity 0.15s;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.75));
border-radius: 0 0 10px 10px;
}
.rahmen:hover .leiste,
.leiste:focus-within {
opacity: 1;
}
.knopf {
padding: 0.1rem 0.3rem;
font-size: 1.05rem;
line-height: 1;
}
.regler {
width: 7rem;
accent-color: var(--accent);
}
.live {
margin-left: auto;
color: #fff;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.live::before {
display: inline-block;
width: 0.5rem;
height: 0.5rem;
margin-right: 0.4rem;
background: var(--accent);
border-radius: 50%;
content: '';
}
.player {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
height: 100%;
object-fit: contain;
background: #000;
border: 1px solid var(--line);
border-radius: 12px;