Update audio playback to oto v3 API and fix imports

This commit is contained in:
Stu Leak 2026-01-04 05:17:40 -05:00
parent 602b5854cc
commit 9e39aee5c8

31
main.go
View File

@ -8,7 +8,9 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"image"
"image/color" "image/color"
"image/png"
"io" "io"
"log" "log"
"math" "math"
@ -7166,9 +7168,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
// Update options and color map for all registered quality widgets // Update options and color map for all registered quality widgets
qualityColorMap := ui.BuildQualityColorMap(newOptions) qualityColorMap := ui.BuildQualityColorMap(newOptions)
for _, w := range uiState.qualityWidgets { for _, w := range uiState.qualityWidgets {
w.Options = newOptions w.UpdateOptions(newOptions, qualityColorMap)
w.ColorMap = qualityColorMap
w.Refresh()
} }
// Use state manager to synchronize selected value across all widgets // Use state manager to synchronize selected value across all widgets
@ -10464,9 +10464,19 @@ var audioCtxGlobal struct {
func getAudioContext(sampleRate, channels, bytesPerSample int) (*oto.Context, error) { func getAudioContext(sampleRate, channels, bytesPerSample int) (*oto.Context, error) {
audioCtxGlobal.once.Do(func() { audioCtxGlobal.once.Do(func() {
// Increased from 2048 (42ms) to 8192 (170ms) for smoother playback _ = bytesPerSample
// Larger buffer prevents audio stuttering and underruns // Larger buffer prevents audio stuttering and underruns
audioCtxGlobal.ctx, audioCtxGlobal.err = oto.NewContext(sampleRate, channels, bytesPerSample, 8192) ctx, ready, err := oto.NewContext(&oto.NewContextOptions{
SampleRate: sampleRate,
ChannelCount: channels,
Format: oto.FormatSignedInt16LE,
BufferSize: 170 * time.Millisecond,
})
if err == nil && ready != nil {
<-ready
}
audioCtxGlobal.ctx = ctx
audioCtxGlobal.err = err
}) })
return audioCtxGlobal.ctx, audioCtxGlobal.err return audioCtxGlobal.ctx, audioCtxGlobal.err
} }
@ -10869,17 +10879,21 @@ func (p *playSession) runAudio(offset float64) {
p.audioActive.Store(false) p.audioActive.Store(false)
return return
} }
player := ctx.NewPlayer() pr, pw := io.Pipe()
player := ctx.NewPlayer(pr)
if player == nil { if player == nil {
logging.Debug(logging.CatFFMPEG, "audio player creation failed (video-only playback)") logging.Debug(logging.CatFFMPEG, "audio player creation failed (video-only playback)")
p.audioActive.Store(false) p.audioActive.Store(false)
return return
} }
player.Play()
p.audioActive.Store(true) // Mark audio as active p.audioActive.Store(true) // Mark audio as active
localPlayer := player localPlayer := player
go func() { go func() {
defer cmd.Process.Kill() defer cmd.Process.Kill()
defer localPlayer.Close() defer localPlayer.Close()
defer pr.Close()
defer pw.Close()
defer p.audioActive.Store(false) // Mark audio as inactive when done defer p.audioActive.Store(false) // Mark audio as inactive when done
// Increased from 4096 (21ms) to 16384 (85ms) for smoother playback // Increased from 4096 (21ms) to 16384 (85ms) for smoother playback
// Larger chunks reduce read frequency and improve performance // Larger chunks reduce read frequency and improve performance
@ -10905,7 +10919,10 @@ func (p *playSession) runAudio(offset float64) {
} }
// Volume is now handled by FFmpeg, just write directly // Volume is now handled by FFmpeg, just write directly
// This eliminates per-sample processing overhead // This eliminates per-sample processing overhead
localPlayer.Write(chunk[:n]) if _, werr := pw.Write(chunk[:n]); werr != nil {
logging.Debug(logging.CatFFMPEG, "audio write failed: %v", werr)
return
}
// Update audio master clock for A/V sync // Update audio master clock for A/V sync
bytesWritten += int64(n) bytesWritten += int64(n)