fix mpv mem leak, waveform gen when double-clicking to play tracks; clean up logs

This commit is contained in:
Drew Weymouth
2025-07-28 18:15:12 -07:00
parent 333b96dd3b
commit 9a1325b92d
4 changed files with 48 additions and 40 deletions
+4 -4
View File
@@ -100,7 +100,7 @@ func (a *AudioCache) cacheFile(id, dlURL string) {
e.done = true e.done = true
} }
a.mutex.Unlock() a.mutex.Unlock()
} else if err != context.DeadlineExceeded { } else if err != nil && err != context.DeadlineExceeded {
log.Printf("error downloading audio file: %v", err) log.Printf("error downloading audio file: %v", err)
} }
cancel() // release ctx resources when done cancel() // release ctx resources when done
@@ -120,9 +120,9 @@ func (a *AudioCache) CacheOnly(keep string, fetch []AudioCacheRequest) {
return a.ID == id return a.ID == id
}) { }) {
_ = e _ = e
//e.cancel() e.cancel()
//_ = os.Remove(a.pathForID(id)) _ = os.Remove(a.pathForID(id))
//delete(a.entries, id) delete(a.entries, id)
} }
} }
-2
View File
@@ -584,7 +584,6 @@ func (p *playbackEngine) cacheNextTracks() {
id = np.Metadata().ID id = np.Metadata().ID
} }
p.audiocache.CacheOnly(id, fetch) p.audiocache.CacheOnly(id, fetch)
log.Println("fetching files", sharedutil.MapSlice(fetch, func(a AudioCacheRequest) string { return a.ID }))
} }
} }
@@ -685,7 +684,6 @@ func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
if isTrack && p.audiocache != nil { if isTrack && p.audiocache != nil {
if filepath := p.audiocache.PathForCachedFile(track.ID); filepath != "" { if filepath := p.audiocache.PathForCachedFile(track.ID); filepath != "" {
url = filepath url = filepath
log.Println("playing file from cache")
} }
} }
if url == "" { if url == "" {
+42 -29
View File
@@ -84,34 +84,44 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
p.lastPlayTime = curTime p.lastPlayTime = curTime
}) })
var curWaveformJob *WaveformImageJob var waveformJobs [3]*WaveformImageJob
var nextWaveformJob *WaveformImageJob findJob := func(id string) (*WaveformImageJob, bool) {
var refreshCancel context.CancelFunc for _, j := range waveformJobs {
if j != nil && j.ItemID == id {
return j, true
}
}
return nil, false
}
addJob := func(job *WaveformImageJob) {
waveformJobs[0].Cancel()
waveformJobs[0] = waveformJobs[1]
waveformJobs[1] = waveformJobs[2]
waveformJobs[2] = job
}
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) { p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
if p.wfmGen != nil && item != nil && item.Metadata().Type == mediaprovider.MediaItemTypeTrack { if p.wfmGen != nil && item != nil && item.Metadata().Type == mediaprovider.MediaItemTypeTrack {
log.Println("preparing waveform image for next track ", item.Metadata().ID) // start generating waveform image for next-up track
id := item.Metadata().ID addJob(p.wfmGen.StartWaveformGeneration(item.(*mediaprovider.Track)))
_ = p.cache.PathForCachedOrDownloadingFile(id)
curWaveformJob.Cancel()
curWaveformJob = nextWaveformJob
nextWaveformJob = p.wfmGen.StartWaveformGeneration(item.(*mediaprovider.Track))
} }
}) })
lastPlayingID := ""
var wfmImageUpdateCancel context.CancelFunc
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) { p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
// Autoplay if enabled and we are on the last track // Autoplay if enabled and we are on the last track
if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 { if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
p.enqueueAutoplayTracks() p.enqueueAutoplayTracks()
} }
if refreshCancel != nil { if wfmImageUpdateCancel != nil {
refreshCancel() wfmImageUpdateCancel()
} }
updateUnfinishedJob := func(job *WaveformImageJob) { updateUnfinishedJob := func(job *WaveformImageJob) {
ctx, c := context.WithCancel(p.cache.rootCtx) ctx, c := context.WithCancel(p.cache.rootCtx)
refreshCancel = c wfmImageUpdateCancel = c
go func(ctx context.Context, job *WaveformImageJob) { go func(ctx context.Context, job *WaveformImageJob) {
for { for {
time.Sleep(333 * time.Millisecond) time.Sleep(333 * time.Millisecond)
@@ -132,24 +142,27 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
} }
if item != nil { if item != nil {
log.Println("Playing track ", item.Metadata().ID) // cancel possible waveform generation job for previous track
var im *WaveformImage if old, ok := findJob(lastPlayingID); ok {
done := false old.Cancel()
if nextWaveformJob.ItemID == item.Metadata().ID {
log.Println("Have waveform in progress for", item.Metadata().ID)
done = nextWaveformJob.Done()
im = nextWaveformJob.Get()
} }
if im != nil { lastPlayingID = item.Metadata().ID
for _, cb := range p.onWaveformImgUpdate {
cb(im) var job *WaveformImageJob
} if j, ok := findJob(item.Metadata().ID); ok {
if !done { job = j
updateUnfinishedJob(nextWaveformJob)
}
} else if tr, ok := item.(*mediaprovider.Track); ok { } else if tr, ok := item.(*mediaprovider.Track); ok {
curWaveformJob = p.wfmGen.StartWaveformGeneration(tr) job = p.wfmGen.StartWaveformGeneration(tr)
updateUnfinishedJob(curWaveformJob) addJob(job)
}
if job != nil {
img := job.Get()
for _, cb := range p.onWaveformImgUpdate {
cb(img)
}
if !job.done && job != nil {
updateUnfinishedJob(job)
}
} }
} }
+2 -5
View File
@@ -64,7 +64,6 @@ func (w *WaveformImageJob) Err() error {
func (w *WaveformImageJob) Get() *WaveformImage { func (w *WaveformImageJob) Get() *WaveformImage {
if w.Done() { if w.Done() {
log.Println("returning image directly")
return w.img return w.img
} }
// return a new *WaveformImage with data copied // return a new *WaveformImage with data copied
@@ -143,7 +142,6 @@ func (w *WaveformImageGenerator) StartWaveformGeneration(item *mediaprovider.Tra
path = srv.Addr() path = srv.Addr()
log.Println("streaming file to MPV at ", path)
go srv.Serve() go srv.Serve()
time.Sleep(10 * time.Millisecond) // make sure server has time to come up time.Sleep(10 * time.Millisecond) // make sure server has time to come up
} }
@@ -155,7 +153,6 @@ func (w *WaveformImageGenerator) StartWaveformGeneration(item *mediaprovider.Tra
err := convertToWav(ctx, path, transcodeFile) err := convertToWav(ctx, path, transcodeFile)
wavConvertDone = true wavConvertDone = true
if err != nil { if err != nil {
log.Println("Error converting to wav", err)
job.setError(err) job.setError(err)
} }
}() }()
@@ -178,7 +175,6 @@ func (w *WaveformImageGenerator) StartWaveformGeneration(item *mediaprovider.Tra
go func() { go func() {
err := analyzeWavFile(ctx, transcodeFile, data, item.Duration.Milliseconds(), func() bool { return wavConvertDone }) err := analyzeWavFile(ctx, transcodeFile, data, item.Duration.Milliseconds(), func() bool { return wavConvertDone })
if err != nil { if err != nil {
log.Println("error analyzing wav", err.Error())
job.setError(err) job.setError(err)
} }
data.done = true data.done = true
@@ -249,7 +245,6 @@ func generateWaveformImage(ctx context.Context, data *waveformData, job *Wavefor
func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformData, millisecs int64, fileDone func() bool) error { func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformData, millisecs int64, fileDone func() bool) error {
f, err := os.Open(transcodeFile) f, err := os.Open(transcodeFile)
if err != nil { if err != nil {
log.Println("error opening transcoded file")
return err return err
} }
defer f.Close() defer f.Close()
@@ -409,6 +404,8 @@ func convertToWav(ctx context.Context, inPath, outPath string) error {
return err return err
} }
defer m.TerminateDestroy()
m.Command([]string{"loadfile", inPath, "replace"}) m.Command([]string{"loadfile", inPath, "replace"})
// Wait for MPV idle or ctx expiry // Wait for MPV idle or ctx expiry