Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/input/AudioEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ export interface AudioEngine {
readonly analyserNode: AnalyserNode
readonly mode: AudioMode
readonly isActive: boolean
readonly isPaused: boolean
connectMic(): Promise<void>
connectTabCapture(): Promise<void>
connectFile(file: File): Promise<void>
connectSpotify(stream: MediaStream): void
disconnect(): void
pause(): void
resume(): void
}

export function createAudioEngine(bus: Bus): AudioEngine {
Expand All @@ -21,6 +24,7 @@ export function createAudioEngine(bus: Bus): AudioEngine {
let stream: MediaStream | null = null
let audioElement: HTMLAudioElement | null = null
let mode: AudioMode = null
let paused = false

function ensureContext(): { ctx: AudioContext; analyser: AnalyserNode } {
if (!ctx) {
Expand All @@ -33,6 +37,7 @@ export function createAudioEngine(bus: Bus): AudioEngine {
}

function cleanupSource() {
paused = false
if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null }
if (audioElement) { audioElement.pause(); audioElement.src = ''; audioElement = null }
if (source) { try { source.disconnect() } catch { /* ignore */ } source = null }
Expand All @@ -44,6 +49,7 @@ export function createAudioEngine(bus: Bus): AudioEngine {
get analyserNode() { return ensureContext().analyser },
get mode() { return mode },
get isActive() { return mode !== null },
get isPaused() { return paused },

async connectMic() {
const { ctx: c, analyser: a } = ensureContext()
Expand Down Expand Up @@ -112,6 +118,26 @@ export function createAudioEngine(bus: Bus): AudioEngine {
mode = null
bus.emit('audio:disconnected', undefined as void)
},

pause() {
if (!mode || paused) return
paused = true
if (audioElement) {
audioElement.pause()
} else {
void ctx?.suspend()
}
},

resume() {
if (!mode || !paused) return
paused = false
if (audioElement) {
void audioElement.play()
} else {
void ctx?.resume()
}
},
}

return engine
Expand Down
26 changes: 22 additions & 4 deletions src/ui/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ export function AppUI({ store, app }: { store: Store; app: AppInterface }) {
bands: Float32Array | null
analysis: NonNullable<Parameters<typeof AudioTab>[0]['analysis']> | null
mode: string | null
isPaused: boolean
lfoPhases: number[]
}>({ bands: null, analysis: null, mode: null, lfoPhases: [] })
}>({ bands: null, analysis: null, mode: null, isPaused: false, lfoPhases: [] })

// Poll audio state at ~15fps for meters
useEffect(() => {
Expand All @@ -69,17 +70,20 @@ export function AppUI({ store, app }: { store: Store; app: AppInterface }) {

const lfoPhases = app.getLFOPhases()
const analyser = app.audioAnalyser
const engineMode = app.audioEngine.mode
const enginePaused = app.audioEngine.isPaused
if (analyser) {
setAudioLive({
bands: analyser.getBands(),
analysis: analyser.getAnalysis(),
mode: app.audioEngine.mode,
mode: engineMode,
isPaused: enginePaused,
lfoPhases,
})
} else {
setAudioLive(prev => {
if (prev.mode !== app.audioEngine.mode || prev.lfoPhases !== lfoPhases) {
return { bands: null, analysis: null, mode: app.audioEngine.mode, lfoPhases }
if (prev.mode !== engineMode || prev.isPaused !== enginePaused || prev.lfoPhases !== lfoPhases) {
return { bands: null, analysis: null, mode: engineMode, isPaused: enginePaused, lfoPhases }
}
return prev
})
Expand Down Expand Up @@ -160,6 +164,14 @@ export function AppUI({ store, app }: { store: Store; app: AppInterface }) {
app.audioEngine.disconnect()
}, [app])

const handlePause = useCallback(() => {
app.audioEngine.pause()
}, [app])

const handleResume = useCallback(() => {
app.audioEngine.resume()
}, [app])

const handleYouTubeLoad = useCallback((url: string) => {
app.youtubePlayer.loadVideo(url)
}, [app])
Expand Down Expand Up @@ -300,6 +312,12 @@ export function AppUI({ store, app }: { store: Store; app: AppInterface }) {

return html`
<${PanelToggle} onClick=${handleTogglePanel} />
${audioLive.mode !== null ? html`
<div id="pp" onClick=${audioLive.isPaused ? handleResume : handlePause}
title=${audioLive.isPaused ? 'Wiedergabe fortsetzen' : 'Pause'}>
${audioLive.isPaused ? '▶' : '⏸'}
</div>
` : null}
<${Panel}
tabs=${TABS}
activeTab=${state.activeTab}
Expand Down
7 changes: 5 additions & 2 deletions src/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ canvas#fx{position:fixed;top:0;left:0;z-index:2;pointer-events:none;}
.tab.active{color:var(--ac);border-bottom-color:var(--ac);}
.tc{display:none;flex:1;overflow-y:auto;padding:14px 12px;scrollbar-width:thin;scrollbar-color:var(--fg2) transparent;}
.tc.active{display:block;}
#pt{position:fixed;top:16px;right:16px;z-index:200;width:36px;height:36px;background:var(--pbg);border:1px solid var(--pbr);backdrop-filter:blur(20px);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.3s;opacity:0;}
body:hover #pt,body.su #pt{opacity:1;}
#pt{position:fixed;top:16px;right:16px;z-index:200;width:36px;height:36px;background:var(--pbg);border:1px solid var(--pbr);backdrop-filter:blur(20px);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.3s;opacity:1;}
:fullscreen #pt,:-webkit-full-screen #pt{opacity:0;pointer-events:none;}
#pp{position:fixed;top:16px;left:16px;z-index:200;width:36px;height:36px;background:var(--pbg);border:1px solid var(--pbr);backdrop-filter:blur(20px);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all 0.3s;user-select:none;}
#pp:hover{background:rgba(255,255,255,0.06);}
:fullscreen #pp,:-webkit-full-screen #pp{opacity:0;pointer-events:none;}
#pt:hover{background:rgba(255,255,255,0.06);cursor:pointer;}
#pt svg{width:16px;height:16px;stroke:var(--fd);fill:none;stroke-width:1.5;}
#panel.open~#pt{right:352px;}
Expand Down