Prefer UUIDs in the API (Phase 3 of the UUID migration)
CI / docker (push) Successful in 46s
CI / integration (push) Successful in 23s
CI / test (push) Successful in 30s
CI / publish (push) Skipped

- New /api/v1 endpoints: GET /soundtracks, /soundtracks/{uuid},
  /songs/{uuid} and /songs/{uuid}/file (streams the song, Range support,
  locked during sync)
- UUIDs added to every response with soundtracks or songs: queue info and
  list (SoundtrackUuid, SongUuid) and statistics (soundtrack_uuid,
  song_uuid). Existing fields stay for backward compatibility
- Play counts and broken-song removal use song UUIDs instead of
  soundtrack ID + song name, so songs with the same name are counted
  separately
- Statistics return an empty song list for soundtracks without songs
  instead of a single empty entry
- The legacy .id file cleanup stays until Phase 4 (#31)

Closes #29
This commit is contained in:
Sansan committed 2026-10-06 15:54:41 +02:00
1 parent 8c974a49cc
commit 2c3e97190d
25 files changed
+1521 -117

No files matched your search

+11 -1
View File
@@ -46,7 +46,8 @@ DELETE FROM song WHERE uuid = ANY(sqlc.arg(uuids)::uuid[]);
-- name: GetSongsBySoundtrackUUID :many
SELECT *
FROM song
WHERE soundtrack_uuid = $1;
WHERE soundtrack_uuid = $1
ORDER BY song_name;
-- name: GetSongUUIDsBySoundtrackId :many
SELECT uuid, soundtrack_uuid FROM song WHERE soundtrack_id = $1;
@@ -62,3 +63,12 @@ JOIN soundtrack ON soundtrack.id = song.soundtrack_id
WHERE soundtrack.deleted IS NULL
ORDER BY random()
LIMIT 1;
-- name: AddSongPlayedByUUID :exec
UPDATE song SET times_played = times_played + 1 WHERE uuid = $1;
-- name: GetActiveSongByUUID :one
SELECT sqlc.embed(song), soundtrack.soundtrack_name, soundtrack.path AS soundtrack_path
FROM song
JOIN soundtrack ON soundtrack.id = song.soundtrack_id
WHERE song.uuid = $1 AND soundtrack.deleted IS NULL;
+3
View File
@@ -62,3 +62,6 @@ AND deleted IS NULL;
-- name: GetSoundtrackUUIDById :one
SELECT uuid FROM soundtrack WHERE id = $1;
-- name: AddSoundtrackPlayedByUUID :exec
UPDATE soundtrack SET times_played = times_played + 1, last_played = now() WHERE uuid = $1;
+71 -37
View File
@@ -2,17 +2,23 @@
-- name: GetMostPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
@@ -24,17 +30,23 @@ LIMIT $1;
-- name: GetLeastPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
@@ -46,6 +58,8 @@ LIMIT $1;
-- name: GetMostPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
@@ -61,6 +75,8 @@ LIMIT $1;
-- name: GetLeastPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
@@ -76,16 +92,22 @@ LIMIT $1;
-- name: GetNeverPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.added,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.times_played = 0
@@ -96,16 +118,22 @@ ORDER BY g.soundtrack_name;
-- name: GetLastPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
@@ -117,16 +145,22 @@ LIMIT $1;
-- name: GetOldestPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
+3
View File
@@ -15,7 +15,9 @@ type Querier interface {
AddHashToSong(ctx context.Context, arg AddHashToSongParams) error
AddSong(ctx context.Context, arg AddSongParams) error
AddSongPlayed(ctx context.Context, arg AddSongPlayedParams) error
AddSongPlayedByUUID(ctx context.Context, argUuid uuid.UUID) error
AddSoundtrackPlayed(ctx context.Context, id int32) error
AddSoundtrackPlayedByUUID(ctx context.Context, argUuid uuid.UUID) error
CheckSong(ctx context.Context, arg CheckSongParams) (int64, error)
CheckSongWithHash(ctx context.Context, hash string) (int64, error)
ClearSongs(ctx context.Context) error
@@ -27,6 +29,7 @@ type Querier interface {
FetchAllSongs(ctx context.Context) ([]Song, error)
FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error)
FindSongsFromSoundtrack(ctx context.Context, soundtrackID int32) ([]Song, error)
GetActiveSongByUUID(ctx context.Context, argUuid uuid.UUID) (GetActiveSongByUUIDRow, error)
GetAllSoundtracksIncludingDeleted(ctx context.Context) ([]Soundtrack, error)
GetIdBySoundtrackName(ctx context.Context, soundtrackName string) (int32, error)
// Last played soundtracks (most recently played)
+41
View File
@@ -68,6 +68,15 @@ func (q *Queries) AddSongPlayed(ctx context.Context, arg AddSongPlayedParams) er
return err
}
const addSongPlayedByUUID = `-- name: AddSongPlayedByUUID :exec
UPDATE song SET times_played = times_played + 1 WHERE uuid = $1
`
func (q *Queries) AddSongPlayedByUUID(ctx context.Context, argUuid uuid.UUID) error {
_, err := q.db.Exec(ctx, addSongPlayedByUUID, argUuid)
return err
}
const checkSong = `-- name: CheckSong :one
SELECT COUNT(*) FROM song WHERE soundtrack_id = $1 AND path = $2
`
@@ -181,6 +190,37 @@ func (q *Queries) FindSongsFromSoundtrack(ctx context.Context, soundtrackID int3
return items, nil
}
const getActiveSongByUUID = `-- name: GetActiveSongByUUID :one
SELECT song.soundtrack_id, song.song_name, song.path, song.times_played, song.hash, song.file_name, song.uuid, song.soundtrack_uuid, soundtrack.soundtrack_name, soundtrack.path AS soundtrack_path
FROM song
JOIN soundtrack ON soundtrack.id = song.soundtrack_id
WHERE song.uuid = $1 AND soundtrack.deleted IS NULL
`
type GetActiveSongByUUIDRow struct {
Song Song `json:"song"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPath string `json:"soundtrack_path"`
}
func (q *Queries) GetActiveSongByUUID(ctx context.Context, argUuid uuid.UUID) (GetActiveSongByUUIDRow, error) {
row := q.db.QueryRow(ctx, getActiveSongByUUID, argUuid)
var i GetActiveSongByUUIDRow
err := row.Scan(
&i.Song.SoundtrackID,
&i.Song.SongName,
&i.Song.Path,
&i.Song.TimesPlayed,
&i.Song.Hash,
&i.Song.FileName,
&i.Song.Uuid,
&i.Song.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPath,
)
return i, err
}
const getRandomActiveSong = `-- name: GetRandomActiveSong :one
SELECT song.soundtrack_id, song.song_name, song.path, song.times_played, song.hash, song.file_name, song.uuid, song.soundtrack_uuid, soundtrack.path AS soundtrack_path
FROM song
@@ -285,6 +325,7 @@ const getSongsBySoundtrackUUID = `-- name: GetSongsBySoundtrackUUID :many
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, uuid, soundtrack_uuid
FROM song
WHERE soundtrack_uuid = $1
ORDER BY song_name
`
func (q *Queries) GetSongsBySoundtrackUUID(ctx context.Context, soundtrackUuid uuid.UUID) ([]Song, error) {
+9
View File
@@ -20,6 +20,15 @@ func (q *Queries) AddSoundtrackPlayed(ctx context.Context, id int32) error {
return err
}
const addSoundtrackPlayedByUUID = `-- name: AddSoundtrackPlayedByUUID :exec
UPDATE soundtrack SET times_played = times_played + 1, last_played = now() WHERE uuid = $1
`
func (q *Queries) AddSoundtrackPlayedByUUID(ctx context.Context, argUuid uuid.UUID) error {
_, err := q.db.Exec(ctx, addSoundtrackPlayedByUUID, argUuid)
return err
}
const clearSoundtracks = `-- name: ClearSoundtracks :exec
DELETE FROM soundtrack
`
+103 -49
View File
@@ -8,21 +8,29 @@ package repository
import (
"context"
"time"
"github.com/google/uuid"
)
const getLastPlayedSoundtracks = `-- name: GetLastPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
@@ -33,6 +41,7 @@ LIMIT $1
type GetLastPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
@@ -51,6 +60,7 @@ func (q *Queries) GetLastPlayedSoundtracks(ctx context.Context, limit int32) ([]
var i GetLastPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
@@ -69,6 +79,8 @@ func (q *Queries) GetLastPlayedSoundtracks(ctx context.Context, limit int32) ([]
const getLeastPlayedSongsWithSoundtrack = `-- name: GetLeastPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
@@ -82,12 +94,14 @@ LIMIT $1
`
type GetLeastPlayedSongsWithSoundtrackRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SongUuid uuid.UUID `json:"song_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
}
// Least played songs with their soundtrack info
@@ -102,6 +116,8 @@ func (q *Queries) GetLeastPlayedSongsWithSoundtrack(ctx context.Context, limit i
var i GetLeastPlayedSongsWithSoundtrackRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SongUuid,
&i.SoundtrackName,
&i.SongName,
&i.Path,
@@ -121,17 +137,23 @@ func (q *Queries) GetLeastPlayedSongsWithSoundtrack(ctx context.Context, limit i
const getLeastPlayedSoundtracksWithSongs = `-- name: GetLeastPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
@@ -142,6 +164,7 @@ LIMIT $1
type GetLeastPlayedSoundtracksWithSongsRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
@@ -160,6 +183,7 @@ func (q *Queries) GetLeastPlayedSoundtracksWithSongs(ctx context.Context, limit
var i GetLeastPlayedSoundtracksWithSongsRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
@@ -178,6 +202,8 @@ func (q *Queries) GetLeastPlayedSoundtracksWithSongs(ctx context.Context, limit
const getMostPlayedSongsWithSoundtrack = `-- name: GetMostPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
@@ -191,12 +217,14 @@ LIMIT $1
`
type GetMostPlayedSongsWithSoundtrackRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SongUuid uuid.UUID `json:"song_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
}
// Most played songs with their soundtrack info
@@ -211,6 +239,8 @@ func (q *Queries) GetMostPlayedSongsWithSoundtrack(ctx context.Context, limit in
var i GetMostPlayedSongsWithSoundtrackRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SongUuid,
&i.SoundtrackName,
&i.SongName,
&i.Path,
@@ -230,17 +260,23 @@ func (q *Queries) GetMostPlayedSongsWithSoundtrack(ctx context.Context, limit in
const getMostPlayedSoundtracksWithSongs = `-- name: GetMostPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
@@ -251,6 +287,7 @@ LIMIT $1
type GetMostPlayedSoundtracksWithSongsRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
@@ -269,6 +306,7 @@ func (q *Queries) GetMostPlayedSoundtracksWithSongs(ctx context.Context, limit i
var i GetMostPlayedSoundtracksWithSongsRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
@@ -287,16 +325,22 @@ func (q *Queries) GetMostPlayedSoundtracksWithSongs(ctx context.Context, limit i
const getNeverPlayedSoundtracks = `-- name: GetNeverPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.added,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.times_played = 0
@@ -306,6 +350,7 @@ ORDER BY g.soundtrack_name
type GetNeverPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
Added time.Time `json:"added"`
@@ -324,6 +369,7 @@ func (q *Queries) GetNeverPlayedSoundtracks(ctx context.Context) ([]GetNeverPlay
var i GetNeverPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.Added,
@@ -342,16 +388,22 @@ func (q *Queries) GetNeverPlayedSoundtracks(ctx context.Context) ([]GetNeverPlay
const getOldestPlayedSoundtracks = `-- name: GetOldestPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
json_agg(
json_build_object(
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) as songs
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
@@ -362,6 +414,7 @@ LIMIT $1
type GetOldestPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
@@ -380,6 +433,7 @@ func (q *Queries) GetOldestPlayedSoundtracks(ctx context.Context, limit int32) (
var i GetOldestPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,