Skip to content

Add Tag Player plugin for linking identifiers to media#2958

Draft
ztripez wants to merge 2 commits intomusic-assistant:devfrom
ztripez:feature/tagplayer-provider
Draft

Add Tag Player plugin for linking identifiers to media#2958
ztripez wants to merge 2 commits intomusic-assistant:devfrom
ztripez:feature/tagplayer-provider

Conversation

@ztripez
Copy link
Copy Markdown
Contributor

@ztripez ztripez commented Jan 10, 2026

Summary

Adds a Tag Player plugin that links arbitrary identifiers (NFC tags, QR codes, or any string) to media items for quick playback.

Complete rewrite based on reviewer feedback from @marcelveldt and @MarvinSchenkel:

  • Changed from MusicProviderPluginProvider (manifest type: plugin)
  • Replaced custom SQLite table with provider mappings (available=False)
  • URI resolution uses existing get_library_item_by_prov_id SQL lookups — no custom DB code
  • Cleanup is automatic: when the provider is removed, MA removes all its mappings

Personal note

I built a tag player for my kids using NFC tags. Initially I made a Home Assistant automation that acted like an abomination of a tag database, but Music Assistant is already the source of truth for my media library — the tag mappings belong here.

How it works

Tags are stored as provider mappings on existing library items with available=False (so MA never tries to stream through tagplayer). The tag ID becomes the item_id in the mapping, and resolution uses the standard provider_mappings table lookup.

API Commands

Command Description
tagplayer/link Link a tag ID to a media item (e.g., target="playlist/42")
tagplayer/unlink Remove a tag mapping
tagplayer/get Get a single tag's mapping info
tagplayer/list List all tag mappings across all media types
tagplayer/play Play a tag directly on a player (primary interface for NFC/QR → HA → playback)

URI format

tagplayer://<media_type>/<tag_id> — media type must match the linked item type (e.g., tagplayer://playlist/party-mix for a playlist tag).

Usage example

# Link any string to a playlist
await mass.send_command("tagplayer/link", tag_id="party-mix", target="playlist/42")

# Play directly via API (primary use case for HA automations)
await mass.send_command("tagplayer/play", tag_id="party-mix", player_id="living_room")

# Also works with NFC tag serials
await mass.send_command("tagplayer/link", tag_id="04:A3:22:1A:5E:3B:80", target="audiobook/5")

Supported media types

Tracks, albums, playlists, artists, radio, audiobooks, podcasts

Tests

24 unit tests covering lifecycle, link/unlink/get/list, playback, error handling, and target parsing.

Comment on lines +92 to +100
async def _create_db_table(self) -> None:
"""Create the tagplayer mappings table if it doesn't exist."""
await self.mass.music.database.execute(
f"""CREATE TABLE IF NOT EXISTS {DB_TABLE_TAGPLAYER}(
[tag_id] TEXT PRIMARY KEY,
[media_type] TEXT NOT NULL,
[item_id] INTEGER NOT NULL
);"""
)
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.

Why would you need a separate db ? You can simply use provider mappings to existing items.

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.

I don't,
I think i was so deep into my own thing it never occurred to me to use provider mappings.

Comment on lines +121 to +124
await self.mass.music.database.insert_or_replace(
DB_TABLE_TAGPLAYER,
{"tag_id": tag_id, "media_type": media_type.value, "item_id": item_id},
)
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.

We cant accept music providers doing direct db modifications

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.

Fair enough.

Comment on lines +135 to +137
await self.mass.music.database.delete(DB_TABLE_TAGPLAYER, {"tag_id": tag_id})
self.logger.debug("Unlinked tag '%s'", tag_id)
return {"status": "ok", "tag_id": tag_id}
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.

See my previous comment - this is unacceptable. My suggestion would be that you add a provider mapping to an existing item with the available flag set to False (to prevent playback being attempted to the provider).

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.

Agreed

@OzGav
Copy link
Copy Markdown
Contributor

OzGav commented Jan 13, 2026

As you get closer to merging I will just need some text for the docs

@MarvinSchenkel
Copy link
Copy Markdown
Contributor

Just thinking out loud here.. Does it make sense that this functionality is implemented as a MusicProvider? I know technically it is now the only way to make it work, but to me this feels more like a plugin.

Thoughts?

@marcelveldt
Copy link
Copy Markdown
Member

Just thinking out loud here.. Does it make sense that this functionality is implemented as a MusicProvider? I know technically it is now the only way to make it work, but to me this feels more like a plugin.

Thoughts?

I never tested this but theoretically I think you can just add any provider mapping to items. Might be worth a test.
So you have a plugin that can register additional provider mappings to existing music library items.

@MarvinSchenkel
Copy link
Copy Markdown
Contributor

Marking this PR as draft so we can keep track of which PRs needs our attention. Please mark as 'Ready for review' when you want us to have another look 🙏 .

@MarvinSchenkel MarvinSchenkel marked this pull request as draft January 22, 2026 10:39
@ztripez
Copy link
Copy Markdown
Contributor Author

ztripez commented Jan 30, 2026

Just thinking out loud here.. Does it make sense that this functionality is implemented as a MusicProvider? I know technically it is now the only way to make it work, but to me this feels more like a plugin.
Thoughts?

I never tested this but theoretically I think you can just add any provider mapping to items. Might be worth a test. So you have a plugin that can register additional provider mappings to existing music library items.

Do you want me to explore this instead?

Complete rewrite based on reviewer feedback (marcelveldt, MarvinSchenkel):
- Changed from MusicProvider to PluginProvider (plugin type)
- Replaced custom SQLite table with provider mappings (available=False)
- URI resolution uses existing get_library_item_by_prov_id SQL lookups
- 5 API commands: link, unlink, get, list, play
- 24 unit tests covering lifecycle, CRUD, playback, and parsing
@ztripez ztripez force-pushed the feature/tagplayer-provider branch from 2dda0ef to 36f814e Compare February 27, 2026 21:18
@ztripez ztripez changed the title Add Tag Player provider for linking identifiers to media Add Tag Player plugin for linking identifiers to media Feb 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 27, 2026

🔒 Dependency Security Report

✅ No dependency changes detected in this PR.

@ztripez
Copy link
Copy Markdown
Contributor Author

ztripez commented Feb 27, 2026

Rewrite complete!

Based on the feedback from @marcelveldt and @MarvinSchenkel, this is now a PluginProvider that uses provider mappings instead of a custom SQLite table:

  • PluginProvider subclass (manifest type: plugin)
  • Tags stored as ProviderMapping(available=False) on existing library items
  • available=False means MA never tries to stream through tagplayer, but URI resolution still works via the standard get_library_item_by_prov_id SQL lookup
  • No direct DB access — everything goes through the existing controller API
  • Automatic cleanup when the provider is removed

The 5 API commands (link, unlink, get, list, play) remain the same. The play command is the primary interface for NFC/QR → Home Assistant automation → playback.

24 unit tests included. Ready for review!

@ztripez ztripez marked this pull request as ready for review February 27, 2026 21:19
@marcelveldt
Copy link
Copy Markdown
Member

So my idea actually worked?

MediaType.ARTIST,
MediaType.RADIO,
MediaType.AUDIOBOOK,
MediaType.PODCAST,
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.

Let's add the new MediaType.GENRE here as well?

"description": "Link NFC tags, QR codes, or any identifier to media items for quick playback.",
"codeowners": ["@ztripez"],
"requirements": [],
"documentation": "https://music-assistant.io/music-providers/tagplayer/",
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.

This should point to /plugins/ now

@MarvinSchenkel MarvinSchenkel requested a review from Copilot March 4, 2026 15:35
@MarvinSchenkel MarvinSchenkel review requested due to automatic review settings March 4, 2026 15:35
@MarvinSchenkel
Copy link
Copy Markdown
Contributor

Few small things and then this should be good to go. Would be awesome if you can add some documentation, especially about how to set it up from a HA perspective as well. FYI, we moved the docs here since we launched a new website. Marking this as draft again, please set it to 'ready for review' when you want us to have another look.

@MarvinSchenkel MarvinSchenkel marked this pull request as draft March 4, 2026 15:37
@OzGav OzGav added this to the 2.9.0 milestone Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants