feat(events): add the position, playback-failure and play-count events
Three events the frontend had no way to learn about: - `PlaybackPositionChanged` carries `player.PositionInfo`, so the seek bar can render what the player is doing instead of counting seconds itself. - `PlaybackFailed` carries the file and the reason, from both the load and the play path, so a track that will not play stops being a silent no-op. - `TrackPlayCountChanged` carries everything needed to patch one track in place. `TrackMetadataChanged` means "the tags on disk were rewritten" and costs the frontend its entire library cache; finishing a track used to emit it. An event's cost is part of its meaning, and the expensive one must not be reused for something cheap.
This commit is contained in:
@@ -13,6 +13,20 @@ const (
|
|||||||
SeekFailed = "SeekFailed"
|
SeekFailed = "SeekFailed"
|
||||||
VolumeChanged = "VolumeChanged"
|
VolumeChanged = "VolumeChanged"
|
||||||
MuteChanged = "MuteChanged"
|
MuteChanged = "MuteChanged"
|
||||||
|
|
||||||
|
// PlaybackPositionChanged carries the player's own position
|
||||||
|
// (payload: player.PositionInfo) once a second while playing and
|
||||||
|
// immediately after any seek, pause, resume or track change. The
|
||||||
|
// seek bar renders what it is told and interpolates only between
|
||||||
|
// ticks, so it can be at most one tick wrong and can never
|
||||||
|
// accumulate error the way a pure local counter did.
|
||||||
|
PlaybackPositionChanged = "PlaybackPositionChanged"
|
||||||
|
|
||||||
|
// PlaybackFailed (payload: {filePath, reason}) fires when a track
|
||||||
|
// could not be loaded or started — a moved file, an unreadable
|
||||||
|
// one, an unsupported codec. Without it the failure was a silent
|
||||||
|
// no-op: the queue reverted its index and nothing reached the UI.
|
||||||
|
PlaybackFailed = "PlaybackFailed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Queue events (backend → frontend push).
|
// Queue events (backend → frontend push).
|
||||||
@@ -70,11 +84,31 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Tag writing events.
|
// Tag writing events.
|
||||||
|
//
|
||||||
|
// TrackMetadataChanged means "tags on disk were rewritten", and the
|
||||||
|
// frontend answers it by throwing the whole library cache away and
|
||||||
|
// refetching — which is correct, because a retag can change an album
|
||||||
|
// name, an artist, a genre, and therefore every derived collection.
|
||||||
|
//
|
||||||
|
// It must therefore not be reused for anything cheaper. Finishing a
|
||||||
|
// track used to emit it, so every song cost a full refetch: ~37 MB
|
||||||
|
// across the IPC per track at 50 000 tracks, and the user's track
|
||||||
|
// selection cleared while music played (audit perf.C1/C2). That is
|
||||||
|
// what TrackPlayCountChanged below exists to separate.
|
||||||
const (
|
const (
|
||||||
TrackMetadataChanged = "TrackMetadataChanged"
|
TrackMetadataChanged = "TrackMetadataChanged"
|
||||||
BatchWriteProgress = "BatchWriteProgress"
|
BatchWriteProgress = "BatchWriteProgress"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Play statistics events.
|
||||||
|
//
|
||||||
|
// TrackPlayCountChanged carries everything needed to patch the one
|
||||||
|
// track in place, precisely so no consumer has any reason to invalidate
|
||||||
|
// a collection: {audioFileId, filePath, playCount, lastPlayed}.
|
||||||
|
const (
|
||||||
|
TrackPlayCountChanged = "TrackPlayCountChanged"
|
||||||
|
)
|
||||||
|
|
||||||
// Autotag apply events — emitted while an async ApplyAsync job is in flight so the review UI can render per-folder progress.
|
// Autotag apply events — emitted while an async ApplyAsync job is in flight so the review UI can render per-folder progress.
|
||||||
const (
|
const (
|
||||||
AutotagApplyStarted = "AutotagApplyStarted" // {groupKey: string, total: int}
|
AutotagApplyStarted = "AutotagApplyStarted" // {groupKey: string, total: int}
|
||||||
|
|||||||
+21
-1
@@ -8,6 +8,8 @@ export const Events = {
|
|||||||
SeekFailed: "SeekFailed",
|
SeekFailed: "SeekFailed",
|
||||||
VolumeChanged: "VolumeChanged",
|
VolumeChanged: "VolumeChanged",
|
||||||
MuteChanged: "MuteChanged",
|
MuteChanged: "MuteChanged",
|
||||||
|
PlaybackPositionChanged: "PlaybackPositionChanged",
|
||||||
|
PlaybackFailed: "PlaybackFailed",
|
||||||
|
|
||||||
// Queue events (backend → frontend push)
|
// Queue events (backend → frontend push)
|
||||||
QueueChanged: "QueueChanged",
|
QueueChanged: "QueueChanged",
|
||||||
@@ -49,10 +51,28 @@ export const Events = {
|
|||||||
LibraryRenamed: "LibraryRenamed",
|
LibraryRenamed: "LibraryRenamed",
|
||||||
LibraryRemoved: "LibraryRemoved",
|
LibraryRemoved: "LibraryRemoved",
|
||||||
|
|
||||||
// Tag writing events
|
// Tag writing events.
|
||||||
|
//
|
||||||
|
// TrackMetadataChanged means "tags on disk were rewritten", and the
|
||||||
|
// frontend answers it by throwing the whole library cache away and
|
||||||
|
// refetching — which is correct, because a retag can change an album
|
||||||
|
// name, an artist, a genre, and therefore every derived collection.
|
||||||
|
//
|
||||||
|
// It must therefore not be reused for anything cheaper. Finishing a
|
||||||
|
// track used to emit it, so every song cost a full refetch: ~37 MB
|
||||||
|
// across the IPC per track at 50 000 tracks, and the user's track
|
||||||
|
// selection cleared while music played (audit perf.C1/C2). That is
|
||||||
|
// what TrackPlayCountChanged below exists to separate
|
||||||
TrackMetadataChanged: "TrackMetadataChanged",
|
TrackMetadataChanged: "TrackMetadataChanged",
|
||||||
BatchWriteProgress: "BatchWriteProgress",
|
BatchWriteProgress: "BatchWriteProgress",
|
||||||
|
|
||||||
|
// Play statistics events.
|
||||||
|
//
|
||||||
|
// TrackPlayCountChanged carries everything needed to patch the one
|
||||||
|
// track in place, precisely so no consumer has any reason to invalidate
|
||||||
|
// a collection: {audioFileId, filePath, playCount, lastPlayed}
|
||||||
|
TrackPlayCountChanged: "TrackPlayCountChanged",
|
||||||
|
|
||||||
// Autotag apply events — emitted while an async ApplyAsync job is in flight so the review UI can render per-folder progress
|
// Autotag apply events — emitted while an async ApplyAsync job is in flight so the review UI can render per-folder progress
|
||||||
AutotagApplyStarted: "AutotagApplyStarted",
|
AutotagApplyStarted: "AutotagApplyStarted",
|
||||||
AutotagApplyProgress: "AutotagApplyProgress",
|
AutotagApplyProgress: "AutotagApplyProgress",
|
||||||
|
|||||||
Reference in New Issue
Block a user