Files
yellowjacket/backend/queue/handlers.go
T
yonlu 9bf2bbab2e feat(M003/S01): play history tracking — schema, migration, recording hook
Migration 10:
- play_history table (audio_file_id FK, played_at DATETIME, CASCADE delete)
- play_count + last_played columns on audio_files (denormalized)
- Recreated track_metadata VIEW with play_count and last_played columns

Play recording:
- queue.recordPlay() inserts play_history row + updates denormalized columns
- Called from OnPlaybackFinished after queue advance completes
- Mutex released before DB write to avoid MaxOpenConns(1) deadlock
- Natural finish only — skip/stop does not count

Tests:
- TestMigration10PlayHistory: schema, columns, VIEW, round-trip verification
- All 49 smart playlist + 15 service + existing DB tests still pass
2026-03-21 15:23:18 -04:00

54 lines
1005 B
Go

package queue
// OnPlaybackFinished is called when a track finishes playing naturally.
// This drives the auto-advance behavior and records the play.
func (q *Queue) OnPlaybackFinished() {
q.mu.Lock()
if len(q.tracks) == 0 {
q.mu.Unlock()
return
}
// Capture the track that just finished before advancing.
finishedID := q.tracks[q.currentIndex].AudioFileID
// Repeat One: replay the current track.
if q.repeatMode == RepeatOne {
if q.playCurrentTrack() {
q.emitIndexChanged()
}
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
nextIdx := q.nextIndex()
if nextIdx == -1 {
// Queue exhausted — this is the extension point for a future fallback playlist.
q.onQueueExhausted()
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
prevIndex := q.currentIndex
q.currentIndex = nextIdx
if !q.playCurrentTrack() {
q.currentIndex = prevIndex
q.mu.Unlock()
q.recordPlay(finishedID)
return
}
q.emitIndexChanged()
q.mu.Unlock()
q.recordPlay(finishedID)
}