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
This commit is contained in:
@@ -1,21 +1,28 @@
|
||||
package queue
|
||||
|
||||
// OnPlaybackFinished is called when a track finishes playing naturally.
|
||||
// This drives the auto-advance behavior.
|
||||
// This drives the auto-advance behavior and records the play.
|
||||
func (q *Queue) OnPlaybackFinished() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -23,6 +30,8 @@ func (q *Queue) OnPlaybackFinished() {
|
||||
if nextIdx == -1 {
|
||||
// Queue exhausted — this is the extension point for a future fallback playlist.
|
||||
q.onQueueExhausted()
|
||||
q.mu.Unlock()
|
||||
q.recordPlay(finishedID)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -32,9 +41,13 @@ func (q *Queue) OnPlaybackFinished() {
|
||||
|
||||
if !q.playCurrentTrack() {
|
||||
q.currentIndex = prevIndex
|
||||
q.mu.Unlock()
|
||||
q.recordPlay(finishedID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
q.emitIndexChanged()
|
||||
q.mu.Unlock()
|
||||
q.recordPlay(finishedID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user