Auto-refresh queue view
This commit is contained in:
parent
c98c1aa924
commit
1a268ce983
111
main.go
111
main.go
|
|
@ -939,6 +939,9 @@ type appState struct {
|
|||
ripProgressBar *widget.ProgressBar
|
||||
ripStatusLabel *widget.Label
|
||||
|
||||
queueAutoRefreshStop chan struct{}
|
||||
queueAutoRefreshRunning bool
|
||||
|
||||
// Subtitles module state
|
||||
subtitleVideoPath string
|
||||
subtitleFilePath string
|
||||
|
|
@ -1549,6 +1552,7 @@ func (s *appState) showErrorWithCopy(title string, err error) {
|
|||
func (s *appState) showMainMenu() {
|
||||
s.stopPreview()
|
||||
s.stopPlayer()
|
||||
s.stopQueueAutoRefresh()
|
||||
s.active = ""
|
||||
|
||||
// Track navigation history
|
||||
|
|
@ -1657,6 +1661,7 @@ func (s *appState) showQueue() {
|
|||
s.lastModule = s.active
|
||||
s.active = "queue"
|
||||
s.refreshQueueView()
|
||||
s.startQueueAutoRefresh()
|
||||
}
|
||||
|
||||
// refreshQueueView rebuilds the queue UI while preserving scroll position and inline active conversion.
|
||||
|
|
@ -1835,6 +1840,49 @@ func (s *appState) refreshQueueView() {
|
|||
s.setContent(container.NewPadded(view))
|
||||
}
|
||||
|
||||
func (s *appState) startQueueAutoRefresh() {
|
||||
if s.queueAutoRefreshRunning {
|
||||
return
|
||||
}
|
||||
stop := make(chan struct{})
|
||||
s.queueAutoRefreshStop = stop
|
||||
s.queueAutoRefreshRunning = true
|
||||
go func() {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if s.active != "queue" {
|
||||
return
|
||||
}
|
||||
app := fyne.CurrentApp()
|
||||
if app == nil || app.Driver() == nil {
|
||||
continue
|
||||
}
|
||||
app.Driver().DoFromGoroutine(func() {
|
||||
if s.active == "queue" {
|
||||
s.refreshQueueView()
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *appState) stopQueueAutoRefresh() {
|
||||
if !s.queueAutoRefreshRunning {
|
||||
return
|
||||
}
|
||||
if s.queueAutoRefreshStop != nil {
|
||||
close(s.queueAutoRefreshStop)
|
||||
}
|
||||
s.queueAutoRefreshStop = nil
|
||||
s.queueAutoRefreshRunning = false
|
||||
}
|
||||
|
||||
// addConvertToQueue adds a conversion job to the queue
|
||||
func (s *appState) addConvertToQueue() error {
|
||||
if s.source == nil {
|
||||
|
|
@ -2278,6 +2326,9 @@ func (s *appState) showBenchmarkHistory() {
|
|||
}
|
||||
|
||||
func (s *appState) showModule(id string) {
|
||||
if id != "queue" {
|
||||
s.stopQueueAutoRefresh()
|
||||
}
|
||||
// Track navigation history
|
||||
s.pushNavigationHistory(id)
|
||||
|
||||
|
|
@ -9259,31 +9310,45 @@ func (p *playSession) runVideo(offset float64) {
|
|||
return
|
||||
}
|
||||
|
||||
// Adaptive frame timing with drift correction
|
||||
now := time.Now()
|
||||
behind := now.Sub(nextFrameAt)
|
||||
// Sync video to audio master clock
|
||||
videoFrameTime := offset + (float64(p.frameN) / p.fps)
|
||||
p.videoTime = videoFrameTime
|
||||
|
||||
if behind < 0 {
|
||||
// We're ahead of schedule, sleep until next frame time
|
||||
time.Sleep(-behind)
|
||||
nextFrameAt = nextFrameAt.Add(frameDur)
|
||||
} else if behind > frameDur*3 {
|
||||
// We're way behind (>3 frames), drop this frame and resync
|
||||
if p.frameN%30 == 0 { // Log occasionally to avoid spam
|
||||
logging.Debug(logging.CatFFMPEG, "dropping frame %d, %.0fms behind", p.frameN, behind.Seconds()*1000)
|
||||
}
|
||||
nextFrameAt = now
|
||||
p.frameN++
|
||||
if p.fps > 0 {
|
||||
p.current = offset + (float64(p.frameN) / p.fps)
|
||||
}
|
||||
continue
|
||||
} else if behind > frameDur/2 {
|
||||
// We're moderately behind, try to catch up gradually
|
||||
nextFrameAt = now.Add(frameDur / 2)
|
||||
// Get audio clock time
|
||||
var audioClockTime float64
|
||||
if audioTimeVal := p.audioTime.Load(); audioTimeVal != nil {
|
||||
audioClockTime = audioTimeVal.(float64)
|
||||
} else {
|
||||
// We're slightly behind or on time, maintain normal pace
|
||||
nextFrameAt = nextFrameAt.Add(frameDur)
|
||||
audioClockTime = videoFrameTime
|
||||
}
|
||||
|
||||
// Calculate A/V sync difference
|
||||
avDiff := videoFrameTime - audioClockTime
|
||||
|
||||
// Adaptive timing based on A/V sync
|
||||
if avDiff < -frameDur.Seconds()*3 {
|
||||
// Video is way ahead of audio (>3 frames) - wait longer
|
||||
time.Sleep(frameDur * 2)
|
||||
if p.frameN%30 == 0 {
|
||||
logging.Debug(logging.CatFFMPEG, "video ahead %.0fms, slowing down", -avDiff*1000)
|
||||
}
|
||||
} else if avDiff > frameDur.Seconds()*3 {
|
||||
// Video is way behind audio (>3 frames) - drop frame
|
||||
if p.frameN%30 == 0 {
|
||||
logging.Debug(logging.CatFFMPEG, "video behind %.0fms, dropping frame", avDiff*1000)
|
||||
}
|
||||
p.frameN++
|
||||
p.current = offset + (float64(p.frameN) / p.fps)
|
||||
continue
|
||||
} else if avDiff > frameDur.Seconds() {
|
||||
// Video slightly behind - speed up (skip sleep)
|
||||
// No sleep, render frame immediately
|
||||
} else if avDiff < -frameDur.Seconds() {
|
||||
// Video slightly ahead - slow down
|
||||
time.Sleep(frameDur + time.Duration(math.Abs(avDiff)*float64(time.Second)))
|
||||
} else {
|
||||
// In sync - normal timing
|
||||
time.Sleep(frameDur)
|
||||
}
|
||||
// Allocate a fresh frame to avoid concurrent texture reuse issues.
|
||||
frame := image.NewRGBA(image.Rect(0, 0, p.targetW, p.targetH))
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user