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:
@@ -11,8 +11,50 @@ const video = ref(null)
|
|||||||
const zustand = ref('lade')
|
const zustand = ref('lade')
|
||||||
const fehler = ref('')
|
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
|
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() {
|
function verbinden() {
|
||||||
const quelle = '/stream/filmabend/index.m3u8'
|
const quelle = '/stream/filmabend/index.m3u8'
|
||||||
|
|
||||||
@@ -74,13 +116,23 @@ function verbinden() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function starten() {
|
function starten() {
|
||||||
video.value?.play()
|
|
||||||
zustand.value = 'laeuft'
|
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(() => {
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('fullscreenchange', vollbildGewechselt)
|
||||||
|
window.removeEventListener('keydown', tasteAbfangen)
|
||||||
hls?.destroy()
|
hls?.destroy()
|
||||||
hls = null
|
hls = null
|
||||||
})
|
})
|
||||||
@@ -92,15 +144,46 @@ onUnmounted(() => {
|
|||||||
<div class="buehne">
|
<div class="buehne">
|
||||||
<!-- Ohne Stream zeigt der Browser nur seinen eigenen Ladekringel und
|
<!-- Ohne Stream zeigt der Browser nur seinen eigenen Ladekringel und
|
||||||
verrät nicht, warum nichts kommt -->
|
verrät nicht, warum nichts kommt -->
|
||||||
|
<div v-show="zustand === 'laeuft' || zustand === 'bereit'" class="rahmen">
|
||||||
<video
|
<video
|
||||||
v-show="zustand === 'laeuft' || zustand === 'bereit'"
|
|
||||||
ref="video"
|
ref="video"
|
||||||
class="player"
|
class="player"
|
||||||
controls
|
|
||||||
playsinline
|
playsinline
|
||||||
muted
|
muted
|
||||||
|
@pause="weiterlaufen"
|
||||||
|
@click.prevent
|
||||||
></video>
|
></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">
|
<div v-if="zustand === 'wartet' || zustand === 'lade'" class="platzhalter">
|
||||||
<p class="gross">
|
<p class="gross">
|
||||||
{{ zustand === 'lade' ? 'Verbinde…' : 'Es wird gerade nichts gesendet.' }}
|
{{ zustand === 'lade' ? 'Verbinde…' : 'Es wird gerade nichts gesendet.' }}
|
||||||
@@ -111,6 +194,10 @@ onUnmounted(() => {
|
|||||||
Wiedergabe starten
|
Wiedergabe starten
|
||||||
</button>
|
</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>
|
<p v-if="fehler" class="note error">{{ fehler }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -127,10 +214,84 @@ onUnmounted(() => {
|
|||||||
min-width: 0;
|
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 {
|
.player {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
background: #000;
|
background: #000;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ for o in json.load(sys.stdin):
|
|||||||
" "$1"
|
" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lautstaerke_von() {
|
||||||
|
wpctl get-volume "$1" 2>/dev/null | awk '{print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
name_des_standards() {
|
name_des_standards() {
|
||||||
wpctl inspect @DEFAULT_AUDIO_SINK@ 2>/dev/null |
|
wpctl inspect @DEFAULT_AUDIO_SINK@ 2>/dev/null |
|
||||||
sed -n 's/.*node\.name = "\([^"]*\)".*/\1/p' | head -1
|
sed -n 's/.*node\.name = "\([^"]*\)".*/\1/p' | head -1
|
||||||
@@ -52,15 +56,29 @@ auf)
|
|||||||
sleep 1
|
sleep 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Damit du selbst weiter über dein gewohntes Gerät hörst
|
# Damit du selbst weiter über dein gewohntes Gerät hörst.
|
||||||
|
# setsid, sonst nimmt der Prozess das Ende dieser Shell mit und du
|
||||||
|
# hörst mitten im Film nichts mehr.
|
||||||
if ! pgrep -f "node.name=filmabend-mithoeren" > /dev/null; then
|
if ! pgrep -f "node.name=filmabend-mithoeren" > /dev/null; then
|
||||||
nohup pw-loopback -m '[FL FR]' \
|
setsid pw-loopback -m '[FL FR]' \
|
||||||
--capture-props="stream.capture.sink=true target.object=$SENKE node.name=filmabend-mithoeren" \
|
--capture-props="stream.capture.sink=true target.object=$SENKE node.name=filmabend-mithoeren" \
|
||||||
--playback-props="target.object=$ZURUECK node.name=filmabend-ausgabe" \
|
--playback-props="target.object=$ZURUECK node.name=filmabend-ausgabe" \
|
||||||
> /dev/null 2>&1 &
|
> /dev/null 2>&1 < /dev/null &
|
||||||
sleep 1
|
sleep 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Dein Lautstärkeregler fasst ab jetzt die neue Senke an, nicht mehr das
|
||||||
|
# Ausgabegerät. Stünde das weiter auf halber Kraft, wäre bei "voll
|
||||||
|
# aufgedreht" Schluss bei der Hälfte. Also: Gerät auf Anschlag, und die
|
||||||
|
# neue Senke übernimmt den Pegel, den du gewohnt bist.
|
||||||
|
ZIEL_ID="$(id_von "$ZURUECK")"
|
||||||
|
if [ -n "$ZIEL_ID" ]; then
|
||||||
|
ALTER_PEGEL="$(lautstaerke_von "$ZIEL_ID")"
|
||||||
|
[ -n "$ALTER_PEGEL" ] && echo "$ALTER_PEGEL" > "$MERKER.pegel"
|
||||||
|
wpctl set-volume "$ZIEL_ID" 1.0
|
||||||
|
[ -n "$ALTER_PEGEL" ] && wpctl set-volume "$(id_von "$SENKE")" "$ALTER_PEGEL"
|
||||||
|
fi
|
||||||
|
|
||||||
# Ab jetzt landet aller Ton in der Senke — und von dort bei dir und im Stream
|
# Ab jetzt landet aller Ton in der Senke — und von dort bei dir und im Stream
|
||||||
wpctl set-default "$(id_von "$SENKE")"
|
wpctl set-default "$(id_von "$SENKE")"
|
||||||
echo "Ton läuft über die Senke Filmabend, du hörst weiter über $ZURUECK."
|
echo "Ton läuft über die Senke Filmabend, du hörst weiter über $ZURUECK."
|
||||||
@@ -70,8 +88,13 @@ ab)
|
|||||||
ZURUECK="$(cat "$MERKER" 2>/dev/null || true)"
|
ZURUECK="$(cat "$MERKER" 2>/dev/null || true)"
|
||||||
if [ -n "$ZURUECK" ]; then
|
if [ -n "$ZURUECK" ]; then
|
||||||
ID="$(id_von "$ZURUECK")"
|
ID="$(id_von "$ZURUECK")"
|
||||||
[ -n "$ID" ] && wpctl set-default "$ID"
|
if [ -n "$ID" ]; then
|
||||||
|
wpctl set-default "$ID"
|
||||||
|
PEGEL="$(cat "$MERKER.pegel" 2>/dev/null || true)"
|
||||||
|
[ -n "$PEGEL" ] && wpctl set-volume "$ID" "$PEGEL"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$MERKER.pegel"
|
||||||
pkill -f "node.name=filmabend-mithoeren" 2>/dev/null || true
|
pkill -f "node.name=filmabend-mithoeren" 2>/dev/null || true
|
||||||
ID="$(id_von "$SENKE")"
|
ID="$(id_von "$SENKE")"
|
||||||
[ -n "$ID" ] && pw-cli destroy "$ID" > /dev/null 2>&1 || true
|
[ -n "$ID" ] && pw-cli destroy "$ID" > /dev/null 2>&1 || true
|
||||||
|
|||||||
Reference in New Issue
Block a user