Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion music_assistant/controllers/music/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
CONF_SYNC_INTERVAL = "sync_interval"
CONF_DELETED_PROVIDERS = "deleted_providers"

DB_SCHEMA_VERSION: Final[int] = 43
DB_SCHEMA_VERSION: Final[int] = 44

# tracks longer that this will not be included in radio mode
RADIO_TRACK_MAX_DURATION_SECS: Final[int] = 20 * 60
Expand Down
55 changes: 54 additions & 1 deletion music_assistant/controllers/music/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from copy import deepcopy
from datetime import datetime
from itertools import zip_longest
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, NamedTuple, cast

from music_assistant_models.background_task import BackgroundTask, TaskMetadata, TaskSchedule
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
Expand Down Expand Up @@ -110,6 +110,15 @@
from music_assistant.providers.builtin import BuiltinProvider


class RecentPlayedTrack(NamedTuple):
"""A recently played track from the playlog, with the artist recorded at play time."""

item_id: str
provider: str
name: str
artist: str | None


class MusicController(MusicDatabaseSetupMixin, CoreController):
"""Several helpers around the musicproviders."""

Expand Down Expand Up @@ -727,6 +736,45 @@ async def recently_played(
)
return result

async def recently_played_tracks(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this hacky bandaid tbh

self,
limit: int,
played_after_timestamp: int,
userid: str | None = None,
) -> list[RecentPlayedTrack]:
"""
Return recently played, fully played tracks with their recorded artist, most recent first.

:param limit: Maximum number of plays to return.
:param played_after_timestamp: Only include plays at or after this epoch-seconds timestamp.
:param userid: Restrict to this user (defaults to the current session user, else all users).
"""
query = (
f"SELECT item_id, provider, name, artist FROM {DB_TABLE_PLAYLOG} "
"WHERE media_type = 'track' AND fully_played = 1 "
"AND timestamp >= :played_after_timestamp "
)
params: dict[str, Any] = {"played_after_timestamp": played_after_timestamp}
if userid:
query += "AND userid = :userid "
params["userid"] = userid
elif user := get_current_user():
query += "AND userid = :userid "
params["userid"] = user.user_id
query += "ORDER BY timestamp DESC"
db_rows = await self.mass.music.database.get_rows_from_query(
query, params=params, limit=limit
)
return [
RecentPlayedTrack(
item_id=db_row["item_id"],
provider=db_row["provider"],
name=db_row["name"],
artist=db_row["artist"],
)
for db_row in db_rows
]

@api_command("music/recently_added_tracks")
async def recently_added_tracks(self, limit: int = 10) -> list[Track]:
"""Return a list of the last added tracks."""
Expand Down Expand Up @@ -1358,11 +1406,16 @@ async def mark_item_played(
# one-off items like TTS or some sound effect etc.
return

# store the primary artist so streaming plays not in the library remain seedable
item_artists = getattr(media_item, "artists", None)
artist = item_artists[0].name if item_artists else None

params = {
"item_id": media_item.item_id,
"provider": media_item.provider,
"media_type": media_item.media_type.value,
"name": media_item.name,
"artist": artist,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what problem is this going to solve ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description explains it in detail. Basically I can t use the play log to provide seeds for last.fm without an artist

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we cant just do a cheap library lookup ? so that lastfm lookups only act on library items ?

@OzGav OzGav Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for people who are listening to streaming tracks which are not in their library. If we limited to library only then we may not get enough seeds or they wont be representative of the users actual play history.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcelveldt see my comment above

"image": serialize_to_json(media_item.image.to_dict()) if media_item.image else None,
"fully_played": fully_played,
"seconds_played": seconds_played,
Expand Down
1 change: 1 addition & 0 deletions music_assistant/controllers/music/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ async def __create_database_tables(self) -> None:
[provider] TEXT NOT NULL,
[media_type] TEXT NOT NULL,
[name] TEXT NOT NULL,
[artist] TEXT,
[image] json,
[timestamp] INTEGER DEFAULT 0,
[fully_played] BOOLEAN,
Expand Down
9 changes: 9 additions & 0 deletions music_assistant/controllers/music/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,15 @@ async def _get_or_create_genre(
if "duplicate column" not in str(err):
raise

if prev_version <= 43:
# add artist column to playlog so recommendation seeds remain available for
# streaming plays that were never added to the library
try:
await database.execute(f"ALTER TABLE {DB_TABLE_PLAYLOG} ADD COLUMN artist TEXT")
except Exception as err:
if "duplicate column" not in str(err):
raise

# save changes
await database.commit()

Expand Down
Loading