Live portal: React + dockview multi-stream viewer behind Authentik; MediaMTX HLS/WebRTC/API re-enabled; is-live Discord hook
live/: every active stream opens as a dockable window (WHEP, HLS fallback), audio follows the focused window, per-window mute/volume, pinning. Caddy live.HOST block with forward-auth proxies /api /webrtc /hls to MediaMTX on loopback and serves /srv/live. WebRTC media on 8189 (firewall). configure.py asks the live hostname and an optional Discord webhook; MediaMTX -ffmpeg image runs the runOnReady hook. deployLive.sh builds and ships dist/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
46
live/src/useAudio.js
Normal file
46
live/src/useAudio.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { create } from './tinyStore.js'
|
||||
|
||||
// Per-stream audio state. Effective mute = override ?? !focused.
|
||||
// override: null (follow focus) | 'unmuted' (pinned on) | 'muted' (pinned off)
|
||||
export const audio = create({
|
||||
focused: null,
|
||||
streams: {}, // id -> { volume: 0..1, override }
|
||||
focus(id) {
|
||||
const s = { ...this.streams }
|
||||
if (id && !s[id]) s[id] = { volume: 0.8, override: null }
|
||||
if (id && s[id]) s[id] = { ...s[id], override: null } // focusing always gives you sound
|
||||
return { focused: id, streams: s }
|
||||
},
|
||||
ensure(id) {
|
||||
if (this.streams[id]) return null
|
||||
return { streams: { ...this.streams, [id]: { volume: 0.8, override: null } } }
|
||||
},
|
||||
forget(id) {
|
||||
const s = { ...this.streams }; delete s[id]
|
||||
return { streams: s, focused: this.focused === id ? null : this.focused }
|
||||
},
|
||||
setVolume(id, volume) {
|
||||
const cur = this.streams[id] || { volume: 0.8, override: null }
|
||||
return { streams: { ...this.streams, [id]: { ...cur, volume } } }
|
||||
},
|
||||
toggleMute(id) {
|
||||
const cur = this.streams[id] || { volume: 0.8, override: null }
|
||||
const effectiveMuted = cur.override ? cur.override === 'muted' : this.focused !== id
|
||||
return { streams: { ...this.streams, [id]: { ...cur, override: effectiveMuted ? 'unmuted' : 'muted' } } }
|
||||
},
|
||||
muteAll() {
|
||||
const s = {}; for (const k in this.streams) s[k] = { ...this.streams[k], override: 'muted' }
|
||||
return { streams: s }
|
||||
},
|
||||
followFocus() {
|
||||
const s = {}; for (const k in this.streams) s[k] = { ...this.streams[k], override: null }
|
||||
return { streams: s }
|
||||
},
|
||||
})
|
||||
|
||||
export function effectiveMuted(state, id) {
|
||||
const cur = state.streams[id]
|
||||
if (!cur) return true
|
||||
if (cur.override) return cur.override === 'muted'
|
||||
return state.focused !== id
|
||||
}
|
||||
Reference in New Issue
Block a user