feat: selectable audio source

Add an audio source picker to the GUI so users can capture a specific
PipeWire device (sink monitor or microphone) instead of only the system
default output.

- vendor go2tv.app/screencast and patch the audio stream to accept a
  target PipeWire node serial (PW_KEY_TARGET_OBJECT); the upstream lib
  only ever auto-connected to the default
- capture: ListAudioSources enumerates PipeWire sinks/sources via pw-dump;
  OpenPipeWire takes the selected node serial
- flinger/config/gui: AudioSource config field, persisted and exposed as
  an Audio source dropdown (default output + enumerated devices)

Also fixes two pre-existing bugs surfaced by stop/start testing:
- engine Stop now waits for the video/audio/stats goroutines before
  destroying the encoder (was a use-after-free SIGSEGV)
- Start/Stop now pause/resume the engine instead of tearing down and
  re-opening the portal session, which the portal cannot reliably do
  in-process (2nd CreateSession returned Ended/cancelled). Capture
  session stays open across stop/start.
This commit is contained in:
2026-09-19 21:44:10 +01:00
parent f1f2bfe1a3
commit 3b3355e002
9 changed files with 366 additions and 29 deletions
+7 -5
View File
@@ -24,12 +24,14 @@ type PipeWire struct {
}
// OpenPipeWire opens a PipeWire capture session. streamIndex selects which
// monitor to capture when multiple are present. Triggering the portal
// consent dialog is expected; the compositor decides whether to show it.
func OpenPipeWire(streamIndex int, audio bool) (*PipeWire, error) {
// monitor to capture when multiple are present; audioSourceSerial optionally
// selects a specific PipeWire audio node (0 = system default). Triggering the
// portal consent dialog is expected; the compositor decides whether to show it.
func OpenPipeWire(streamIndex int, audio bool, audioSourceSerial uint64) (*PipeWire, error) {
s, err := capture.Open(&capture.Options{
StreamIndex: streamIndex,
IncludeAudio: audio,
StreamIndex: streamIndex,
IncludeAudio: audio,
AudioSourceSerial: audioSourceSerial,
})
if err != nil {
return nil, err