run audio-only playback through audio slot

This commit is contained in:
Dmitry Sergeev
2026-08-28 22:16:57 +03:00
parent bcb0ae6a6d
commit d49f4ff15f
2 changed files with 151 additions and 21 deletions
+149 -19
View File
@@ -8,6 +8,7 @@ import (
"log"
mxladapter "mxl-player/internal/adapter/mxl"
"mxl-player/internal/imgui"
"mxl-player/internal/output"
"mxl-player/internal/playback"
"mxl-player/internal/renderer"
"mxl-player/internal/sdl"
@@ -149,6 +150,7 @@ func main() {
}
// path selection
useVideoSlot := args.AudioFlowId == ""
useAudioSlot := args.AudioFlowId != "" && args.VideoFlowId == ""
runtime.LockOSThread()
if err := sdl.Load(); err != nil {
@@ -303,25 +305,7 @@ func main() {
case args.VideoFlowId != "":
// VideoSlot owns opening and closing the video reader.
case args.AudioFlowId != "":
audioSrc, err = source.OpenAudio(args.Domain, args.AudioFlowId)
if err != nil {
log.Fatalf("audio source: %v", err)
}
aChans = audioSrc.Channels()
audioBatch = uint64(audioSrc.Rate().Num) / (100 * uint64(audioSrc.Rate().Den))
if audioBatch == 0 {
audioBatch = 1
}
audioStream = sdl.OpenAudioDeviceStream(sdlAudioDevice, sdl.AudioSpec{
Format: sdl.AudioF32,
Channels: int32(aChans),
Freq: int32(audioSrc.Rate().Num / audioSrc.Rate().Den),
})
if audioStream == 0 {
log.Fatalf("audio: %s", sdl.GetError())
}
sdl.ResumeAudioStreamDevice(audioStream)
fmt.Printf("audio: %dch %d/%d Hz\n", aChans, audioSrc.Rate().Num, audioSrc.Rate().Den)
// AudioSlot owns opening and closing the audio reader.
default:
// No configured feeds. Renderer and GUI use the placeholder.
}
@@ -390,6 +374,7 @@ func main() {
showStats bool = true
)
videoActive := args.VideoFlowId != ""
audioActive := useAudioSlot
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -437,6 +422,44 @@ func main() {
}
videoCommands := make(chan playback.FeedConfig, 1)
audioOutput := output.NewSDLAudioSink(sdlAudioDevice)
defer audioOutput.Close()
audioWorker, err := playback.NewAudioWorker(
mxladapter.AudioFactory{},
audioOutput,
retryPolicy,
mxladapter.ShouldRetry,
func(status playback.Status) {
statusStore.Observe(status)
if status.Err != nil {
log.Printf(
"audio: state=%v attempt=%d failed=%d: %v",
status.State,
status.Attempt,
status.FailedAttempts,
status.Err,
)
return
}
log.Printf(
"audio: state=%v attempt=%d failed=%d",
status.State,
status.Attempt,
status.FailedAttempts,
)
},
)
if err != nil {
panic(err)
}
audioSlot, err := playback.NewAudioSlot(audioWorker)
if err != nil {
panic(err)
}
audioCommands := make(chan playback.FeedConfig, 1)
reopen := func(params reconnectParams) error {
// Close current sources
if syncSrc != nil {
@@ -499,6 +522,17 @@ func main() {
default:
}
}
enqueueAudioConfig := func(config playback.FeedConfig) {
select {
case <-audioCommands:
default:
}
select {
case audioCommands <- config:
default:
}
}
doReconnect := func() {
if useVideoSlot {
@@ -516,6 +550,22 @@ func main() {
enqueueVideoConfig(config)
return
}
if useAudioSlot {
audioActive = audioStr != ""
config := playback.FeedConfig{}
if audioStr != "" {
config = playback.FeedConfig{
Domain: domainStr,
UUID: audioStr,
Active: true,
}
}
enqueueAudioConfig(config)
return
}
// legacy
select {
case <-control:
default:
@@ -542,6 +592,21 @@ func main() {
}
return
}
if useAudioSlot {
err := audioSlot.Run(
ctx,
playback.FeedConfig{
Domain: args.Domain,
UUID: args.AudioFlowId,
Active: args.AudioFlowId != "",
},
audioCommands,
)
if err != nil && !errors.Is(err, context.Canceled) {
log.Printf("audio slot: %v", err)
}
return
}
// Audio-only mode: independent loop.
if audioSrc != nil && syncSrc == nil && videoSrc == nil {
@@ -929,6 +994,71 @@ func main() {
cimgui.Text("Video actual: not started")
}
}
if useAudioSlot && audioActive {
if cimgui.Button("Stop audio") {
audioActive = false
enqueueAudioConfig(playback.FeedConfig{
Domain: domainStr,
UUID: audioStr,
Active: false,
})
}
}
if useAudioSlot && !audioActive && audioStr != "" {
if cimgui.Button("Resume audio") {
audioActive = true
enqueueAudioConfig(playback.FeedConfig{
Domain: domainStr,
UUID: audioStr,
Active: true,
})
}
}
if useAudioSlot && audioStr != "" {
if cimgui.Button("Remove audio") {
audioActive = false
audioStr = ""
enqueueAudioConfig(playback.FeedConfig{})
}
}
if useAudioSlot {
if audioActive {
cimgui.Text("Audio desired: active")
} else if audioStr != "" {
cimgui.Text("Audio desired: stopped")
} else {
cimgui.Text("Audio desired: not configured")
}
if status, ok := statusStore.Snapshot(playback.UnitAudio); ok {
cimgui.Text(fmt.Sprintf(
"Audio actual: %s",
status.State,
))
cimgui.Text(fmt.Sprintf(
"Attempt: %d, failed: %d",
status.Attempt,
status.FailedAttempts,
))
if status.RetryIn > 0 {
cimgui.Text(fmt.Sprintf(
"Retry in: %s",
status.RetryIn.Round(time.Millisecond),
))
}
if status.Err != nil {
cimgui.TextWrapped(status.Err.Error())
}
} else {
cimgui.Text("Audio actual: not started")
}
}
cimgui.End()
gui.EndFrame()
lastFrame = time.Now()
+2 -2
View File
@@ -14,7 +14,7 @@ Size=200,200
Collapsed=0
[Window][Connection]
Pos=979,700
Size=695,233
Pos=174,363
Size=986,237
Collapsed=0