#Mobile app
1 messages · Page 3 of 1
Did that.
2026.03.23.1721 working fine with 2.8.0.b22
Ah ok! I hadn't found this. I'm away from home at the moment, but will check this out when I get back.
Sorry guys, how do I play Genre? It says no playable items....
Also @fast grove do we have Firebase console, so I could setup Crashlytics?
Or do we prefer any other logging solution?
Can't find anything special about it. Should it be played via standard player_queues/play_media with genre ID? Or should I get all artists/albums for genre and put them there explicitly?
You should be able to use a regular play_media call with a genre uri or genre media item object
Okay, I will try harder 🙂
Huh, I can't reproduce the original issue, but looks like uri was null for genres. I made fallback to library://genre/{id} and it started to work - but now in debug I see uri populated as well...
2026.03.27.1440 working flawlessly with 2.8.0rc3
Managed to build an unsigned IPA on GitHub, I'm out and about at the moment so have no way of testing, but if anyone wants to try.
https://github.com/jozefKruszynski/mobile-app/releases/tag/ios-debug-2026.03.28.1205
I'll test it when I get home this evening
We should maybe start with setting up Testflight. We have a Apple Development agreement etc. for the OHF. All we need to do is figure out how to set it up.
Kun jij ook hier ff naar kijken ?
https://github.com/music-assistant/server/pull/3376
Lijkt veel op wat jij laatst had gedaan
I wanted to see if I could get a build up and running that works.
The signed build should be a relatively straightforward step after that I believe.
Then we can probably look at the testflight process
The build works well for me after the song and dance number of setting up dev mode on my iPhone etc.
No point trying to build a signed app without testflight so that is the next step, also saw oauth is not supported on ios
This we also need to fix before testflight I reckon.
I am also no expert here, would just like to help to push this forward.
For the TestFlight build
I highly recommend using one version number like 1.2.0
But for fixing things distinguish by the build number. If one version like 1.2.0 is approved the next builds are automatically approved so no further reviews by the App Store Team…
Oh yes that’s a mess.. A lot of dark patterns from Apple.
I saw you gave some solid advice to the guy in the apple tv stagefront app thread about this.
We should decide then if we want to always ensure that the android and iOS builds have the same version number or we make the decision that we keep the versioning separate across OSes
On unrelated topic - I see that although my app player isn't in UI, it's still visible in "transfer queue" list...
Also my Sendspin respeaker players show "universal_player" as type - is it okay?
I would sync them. For testing the initial version I think it doesn’t matter at all. But for the production version I would not have different version numbers across different OSes. This would make it hard to later see in which version something is broken.
We then need to be very strict about version bumps. I think it also makes sense, but it could become problematic if/when one or the other app stores refuses something randomly
I lead Android+iOS teams for almost 10 years. Sticking to the same build number and version number between platforms was always giving unnecessary pain. Especially when something needs to be fixed on one platform, but not on the other one.
This was what I assumed would be the case
There are virtually no advantages to carry cognitive load of handling same version between platforms 🙂
But then explain how you tackle down a specific issue across multiple platforms? If you have a complete different version number
(I don’t wanna disagree just learn something)
That's easy, last build date.
Issues are still reported on one on another platform. They're bound to that platform, then after investigation - if the trouble is common - both build numbers are added to ticket.
And the fix to the issue should contain iOS and Android fix versions. It's easier to tackle, just build both, and record corresponding version.
Because each platform can release on their own pace, so single fix (even in common code) can be delayed for iOS, while already released on Android, because of some induced platform-specific changes.
This makes sense. Then we should add a Report a problem button that creates a GitHub ticket with all necessary information at a single place. As the MA userbase is growing I expect also to have more than 1000+ Users for that single application
Ohh, I guess link to Github will be enough. Too much hassle 🙂
Not if you asked Claude 😉
Nope. I do use Claude, but it's not simple refactoring - it's user-facing functionality, that relies on 3rdparty API (Github), so it can give troubles even if we don't break anything. In these cases I prefer KISS principle instead of relying on AI knowing my codebase better than me.
Absolutely true. Maybe a simple link to GitHub that copies all information into clipboard.
For testing and bug fixing would be great to have more information
Yes, something like that - at maximum. Or the page that will have link, and copy-enabled sytem info.
We will need GH issue template with mandatory version and stuff.
Just wanted to second this! Similar background, similar conclusion. Mobile apps are locally running software, so you deal with bug reports from different versions even if you're on a single platform anyway.
@abstract trench what are you thinking for the Github APKs going forward? Is there an advantage to them being debuggable? I was just playing around at adding a command line arg for building selfSignedRelease in release mode, but we could also just change it so that isDebuggable is false always for it.
It does feel like once proper release mode APKs are getting distributed it might feel a bit lax to just be using a non "official" key for signing. I'm not sure how Home Assistant's Android signing is usually handled and who manages keys.
There's already proper internal testing process on GPlay. Marcel and Marvin helped me with setup.
We can keep debuggable builds so far - there's nothing secret. 🙂
When we're ready, we can stop making debug builds and just publish to GPlay.
But for now, do you want to keep the APKs on Github debuggable? I just meant that it would be good to have a simple way to build a non-debuggable ("release mode" for Compose) APK for checking performance during development, but that could just be selfSignedRelease if there's no reason to keep that build variant as debuggable.
If we do want to keep the Github APKs debuggable we can just add a new build variant or a command line property to get release mode APKs
Well, while we have plenty of bugs, probably giving people easy way to get logs is good thing.
Yup makes sense
I don't want to make another keystore... 🙂 It will get messy
I can add a build variant that uses the same signing config. That's probably simplest.
And yeah. No more keystores haha.
@abstract trench I've noticed there's a "pop-in" for the gradient color when switching from the compact to expanded player. It looks like some recomposition happens that causes the animation to run again. I was going to refactor the player pager a bit anyway to try and make the different modes a bit more distinct in the code (it's hard to follow at the moment), so I'll look at that as well. Just a heads up that I'm playing around in that area (HomeScreen and PlayerPager mainly).
Okay, thank you. I will focus on crashes and other boring stuff and leave UI to expert. 🙂
I see problem with podcasts right now. When I try to seek, every time the progress returned to me starts from 0. Stream seems to be correct, but progress bar always jumps to 0 and starts from there, wherever I tap on it. Found it on mobile app, confirmed on web. Music seems to be working good.
Same problem with audiobooks.
I believe that is a known issue but I can't find the thread on GH at the moment
@mellow widget I noticed today that whole home screen gets recomposed twice a second. Mitigated it.
@abstract trench ah great! Yeah I'd seen that we were getting regular recompositions in the player, but I'd thought that was just to playback state updating and hadn't investigated up the hierarchy. It looks like it was something to do with the view model "actions" from your PR? I haven't really dug into those at all.
I like Compose a lot, but the "magic" that its compiler adds to make recompositions happen makes it very, very easy to have things redrawing on a tick by accident.
This app is going to be especially sensitive to that given all the "live" state like playback, speakers getting updated etc.
That was one of the reasons.
It actually still redraws cruelly when progress bar is visible. But that's a bit harder to solve. Basically the progress elements should be exposed separately from all other state to make it work...
Oh damn. I was today years old when I discovered that MA doesn't return track duration for non-library tracks. Or is it bug?
There's 0.0
Should be returned AFAIK. What music provider is that track from.?
Tidal
I feel like Tidal is more and more doomed.
I ran into this myself - sometimes we have track durations of 00:00
I think this is some core bug somewhere
Did the tracks come from a MA playlist in this case ?
Great catch! Yes, and same tracks played directly from search HAVE duration.
Reproduced it and fixed in next nightly - stupid parsing error
Been a while since I’ve been over here, how’s iOS development coming along?
Dev is OK, publishing is kinda stuck
Anything I can do to help iOS publishing unstuck?
Is there one out on testing or do I have to do the usual pull out the Mac and compile myself?
Unfortunately, no TestFlight as of now. We're working in it!
Gotcha yeah take your time Apple is a pain in the butt
I'm Android guy all the way - this adds to it. Hopefully Marvin will help us!
As long as you pay for the developer license I’ve just learned that it’s a pretty straightforward and mature publishing platform. Except frustration with that possible review conversation back and forth with a first-line reviewer…
I mean, I'm no iOS dev at all 🙂
Hey guys @mortal wadi @fast grove will we be able to use Localise for mobile app?
I don't know can we? 100% open to linking the app to lokalise but is it a thing on mobile?
Checking now
Yeah Localise works with Android XML files (which we need for KMP). I will. be able to automate this as well with CI. But it should be different project in Localise, since it's XML (MA uses JSON as far as I know).
For people finding this thread, the iOS app now has a public Test Flight beta: https://testflight.apple.com/join/4byCu2Nk
Crashes like crazy on most of the things. No development after initial build guide?
Audiobooks and local MA playlist image metadata isn't loading
I will be able to jump back on this next month
Cant confirm crashes
I will report when they happen again
What I find annoying the app claims audio playback even nothing is playing
play/pause is also buggy from lock screen
Guys, for user support with app use https://discord.com/channels/753947050995089438/1472013190970871902
Is there an easy way to run ./gradlew detektAll and have it fail only if it can't fix things? From skimming docs it seems like you can do this at a config level, but obviously we'd still want that task to fail on CI if any problems are detected. I'd have thought there would be an arg or somthing.
I've generally used ktlint without autofixing in the past, so haven't really played around with detekt before
Right now it always has to be run twice if there are problems getting autofixed to get a green run
Same, I use Lint. There's check mode, where it faiuls always - and there's fix mode, where it's fixing what it can fix, and fails on other errors.
With Detekt, I run it, then after failure I click "run with stacktrace" and it succeeds.
Does anyone but me experience progress bar seek bug? I try to seek - the track is switching to required time, but visual progress goes back to 0 and starts from there. It was like this for podcasts/audiobooks a while ago - Marcel fixed it reportedly - now I see the same with regular tracks.
What are the tasks/args for those modes? ./gradlew tasks just lists detekt and detektBaseline (for different targets)
I'm honestly not sure
🙂
Oh so you mean you just use ./gradlew detektAll as well?
Yeah 🙂
@brave sentinel how up to date is https://github.com/music-assistant/mobile-app/blob/dev/ios_build_instructions.md? I'd like to be setup for at least running iOS locally. I noticed that the instructions expect the WebRTC framework to be in the repo (which isn't) so thought I'd pause just in case I end up going down a rabbit hole for nothing here.
Oh, I don’t know why it says it should be there, we explicitly don’t vendor the framework because it’s so large. You can grab the one mentioned just below that bit - it has the fix for the recent crashes.
Great I'll give that a go soon!
Hi @abstract trench @mortal wadi , I don't see a channel for Core questions, but since I am developing for a mobile app, here it goes:
API question on cache-invalidation strategy, want to confirm I'm reading this right before I commit to an approach.
Designing the library cache for users with thousands of tracks where re-fetching everything on cold start is painful. Read through dev + models and here's what I think the surface looks like:
music_sync_completedfires when sync tasks drain, but the event carries no payload (no counts, no per-provider breakdown)- No
modified_since/updated_afterfilter on<type>/library_items - DB has
timestamp_modifiedwith an auto-update trigger, butMediaItemonly exposesdate_addedover the wire media_item_added/media_item_updated/media_item_deletedemit the full item as payload- No
library/changes_since/ version-cursor endpoint
Is the intended pattern: bulk-load once, then keep the cache live off the per-item media_item_* events, with a full invalidate-and-reload as the fallback for reconnects / missed-event windows? Or is there a delta facility I missed?
If that's the pattern, three small asks that would make it robust against dropped events — happy to PR any of them if the direction's welcome:
- expose
timestamp_modifiedonMediaItem(column + trigger already exist, just hydrate the field) - add a
modified_sincefilter to<type>/library_items— pairs with #1 for bounded reconciliation - include
{added, updated, removed}counts per provider on themusic_sync_completedpayload, so clients can skip reconciliation when a sync run was a no-op
Want to check intent before writing anything that conflicts with something already on the roadmap.
AFAIK the intent right now is as you described. Full invalidation on restart. It makes sense for Web, not souch for mobile. 🙂
Your proposed improvements make perfect sense, and probably official mobile app could benefit from that as well. We don't have local DB yet, but we might think on it!
@fast grove Could you please double check this?
Damn, we need somehow shorten down the iOS build time. It takes ages. 🙂
Does the default picture from missing album art in the app come from the server or it is local to the app?
If local where in https://github.com/music-assistant/mobile-app/ are they?
I think they're all drawn on demand using the KMP Canvas. An example: https://github.com/music-assistant/mobile-app/blob/dev/composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/common/painters/PlaceholderPainter.kt.
Thank you
Is it similar to like SVG drawing?
Seems like it. Wonder if there is a converter to convert that
Are these genre icons sent from music assistant?
Yes
are you using sendspin + ma api calls for data or just sendspin?
Impossible to use only Sendspin. We use it only for audio stream.
It's possible to use it for player management too, but not for library or other players.
Does the app allow you to play direct if you didn't want to use sendspin?
No. It used to use direct URLs before, but doesn't do now - legal stuff... That's also why we can't cache a lot of stream.
Isn't that the premise of sendspin, a global cache synced between players?
It's giving every player chunks that are needed depending on client lag - but the cache exposed to every client is pretty short.
what is the legal limit for a cache?
I feel like you would be less on the hook connecting to direct linked radio stations than streaming stuff yourself to multiple devices
As the fault would lay on the station for not obtaining the appropriate licenses
Now your friend comes over connects to your sendspin and walks outside of your property area that could be illegal in some places 😄 That responsibility lies on you to tackle them before they cross the boundary.
Seems when caching is concerned it only seems illegal if you turn it into a permanent copy to play multiple times, says you can make a single copy of a legal broadcast to listen to at a later time but you can't keep it no longer than reasonably necessary to listen to it at a more convenient time.
Says the cache can be as long as you need to prevent network dropouts. As long as the cache is an automated, essential part of the streaming process and lasts only for the duration of that playback, it does not infringe copyright. Whether your device buffers 5 seconds (standard radio stream) or 30 minutes of audio to prevent network dropouts, it remains entirely legal because the system deletes the older cached blocks as new ones arrive.
Hopefully other places have sane rules like that as well.
It's better to talk about it with Marcel and Maxim. I just told how it is now. 🙂
@fast grove @grand jewel guys what's current policy about cache? I suppose we're actually are pretty far with mobile app now, it's pretty useable - apart from hiccups because of cache. Well, WebRTC too, bit I have thoughts about it.
Have you tried to use the visualizer stuff?
through the sendspin protocol
I feel like that would be a lot of data to spam to multiple players. Almost killed the lvgl player sending the progress bar duration time updates 😄
Let alone a wild amount audio spectrum points
No, I don't plan it. Go3ss WLED is doing it.
I would debounce it.
Just create a new thread here in devs support is easiest. We can potentially expose timestamp_modified for library/db items but that is at item level. I think you more need something like like a etag or timestamp of last modification for the library itself. But I also wonder why you need to cache in the first place ? Because the whole library is fully paged and in general something like 100 items of a list would return in less than 25ms.
It's strange that I send buffer_capacity = 10_000_000 (it's microseconds, right?), but getting back 30 seconds buffer.
@abstract trench just been familiarizing myself with the foreground service setup after reading https://github.com/music-assistant/mobile-app/issues/379#issuecomment-4446562260. Is there a reason not to just start a foreground service (and notification) on app launch? From skimming the code, I think we'd need to switch the wifi lock on and off while the service is running to not drain extra battery when there's no playback, but that's doable.
Just interested if it's a "by design" thing or a "not done it yet" thing!
I don't want to hold service all the time. State management (e.g. when disconnected, unauthorized, reconnecting etc.) and notification management, when, say, there's no players with content to play, or there's no local player etc. - it will be awful .. Not to mention battery drain - system won't be able to kill the app, while foreground service is active, no matter if WiFi lock is acquired or not.
Gotcha! So right now, from looking at the service code, it runs while anything is playing and then stops when everything is stopped (or I'm guessing when the app doesn't know anything is running because of a disconnect).
That all makes sense, as I said I was just interested in if that was by design. "It keeps things simple" is a good answer!
Yeah, it's still a bit complicated with Android Auto and new Google Assistant integration - but I try to hold it on KISS grounds. 🙂
@mellow widget heads up - I prepare huge refactoring. Too much things are done "for MVP" in data layer, need proper factories, client data objects layer, and many other things to change for better DI and less hassle on UI layer.
Cool! I'll try and stick to UI tweaks for now then. I was going to look at fixing backstack saved state issues next any way. Let me know if you want any input to or to discuss ideas for data layer stuff - it sounds like you've got a plan though.
Yeah mostly done for this pass
Opening PR
Is AppMediaItem going to be sealed with data subclasses? That's be a wee gift.
Planning it
Hero. It's an annoying refactor.
Damn, after moving to data classes it fails with some unparseable NPE...
like "non-nullable text is null" on composing.
We don't use !! anywhere, so it shouldn't happen.
Stacktrace is useless.
Yeah every time I've tried it I've run into something like that. It's a surprisingly subtle shift. It's the sort of thing that you'd hope agent could help with, but it seems like a class of problem that tech has trouble with.
Probably needs to be done a single property at a time 😒
BTW I'm fixing scroll state too, don't bother 🙂
Unless you're actually on it already 🙂
I've got a solution but wasn't finished yet
Okay, I will delete my local branch
I bet your solution will be more elegant. 🙂
haha nah I'm sure you had it
We just need to use rememberSaveableStateHolderNavEntryDecorator to decorate back stack entries, but it also looks like that doesn't fix all screens.
Well, see... I was just saving lazy states in VMs
Nah we shouldn't need that
Yes, decorator should do it automagically
I think some screens don't work with it because the scroll state "owner" ends up being a Column that's wrapping a lazy grid (ItemDetailsScreen for example). Hopefully just need to move everything into the lazy component as item bits.
Honestly Navigation 3 + Compose is kind of strange. So much boilerplate to achieve all the things that just work with a multi Activity or Fragment app. They really need some higher level components wrapping all this (which is basically what I've ended up building).
It'll be much nicer for any times you have to do non-standard navigation I suppose!
Ooof we also have a wee problem because we have a single view model with a single "state" shared between every item details screen. As well as causing problems for scroll state (every screen is being reloaded when navigating between them), it also looks like there's a bug where sometimes details for a previous screen get loaded into the current one (race condition with the requests probably).
I can rework that! I feel like the simplest thing is to probably just have loadItem return the value for that request (as a state or stateflow) and then have that as saveable state for the screen. You could do a ViewModel per screen, but that feels super heavy and you could also keep keyed state in the view model, but you run into cache invalidation problems there.
Ah damn actually that won't work really as the data isn't immutable (sorting, favourites etc). We could do a ViewModel per entry in the back stack or change the way the current shared ViewModel works to support having multiple items loaded (which would be great in the long term for things like preloading of albums for artist etc.). I'll pause and wait for you to finish that refactor as I'll want to explore options on top of your changes.
I will investigate if I can use automagic navigation 3 ViewModel scoping in the mean time, but I'm not sure it works in KMP
I feel like VM per item screen is the way to go all in all... 🙁
Yeah, it does seem like it fits in best with Nav 3 and Koin from playing around!
The current version I have is broken due to some of the nav libraries dropping support for the iosX64 target which is now removed from Compose Multiplatform. I'm going to wait for you to merge your stuff and then come back to fix it - I'd like to do a little rework on how we manage ViewModels and screens to try and make it clearer how everything is scoped, but I'd rather do that on top instead of dealing with a nasty rebase.
Okay.
Try it! No fails. 🙂
I just found that Metadata on items are returning a heck of images. Like dozens, sometimes more than 100 image data objects. Do we need that much?...
I’d say no. I would expect for your purposes you just want the preferred image. So just one of each type.
@abstract trench quick question on ActionsViewModel there's a series of data classes like PlaylistActions that get passed down to Composables. My assumption is that this is to decouple the Composables from the ViewModel itself, but if that's the reason could it just be a series of interfaces that the ViewModel implements? If there's no reason not to do that, I can follow-up with that change after https://github.com/music-assistant/mobile-app/pull/404.
Yeah it's legacy stuff, you absolutely can rip it off. 🙂
Would be great to add Crashlytics before public beta 🙂
Also Lokalize (which we have set up in the app, need to plug in the project, need OHF help).
Yeah does Home Assistant/Music Assistant already have an account?
Yup afaik
Cool! Yeah, would be great to have.
I assume that only happens when you request a "full" object ?
Because in that case we send all metadata recursively.
In listings we should only send a simplified object (with example itemmapping objects for track artists etc)
Yeah looks like. I'll check what we're getting in lists.
Yeah we have a Lokalize account for the MA frontend but I have no clue if/how we can add the mobile app there as well.
Yeah need separate project there, reportedly.
Hey, just a heads-up: PlayerQueue now has an is_dynamic boolean field. The server sets it whenever radio_source changes and also correctly restores it after a restart. So on the client side you can just use queue.is_dynamic directly — no need to inspect radio_source yourself anymore. This can be used e.g. to disable the Shuffle and Repeat buttons when a dynamic playlist is active. Everything is already merged into dev.
Guys, is there a way to setup both local and external HA address in MA?
Asking because connection to HA is better via local IP, but when we try to login via HA (both on proxied connection or WebRTC while remote) - local IP is useless.
I dont thnk so. Maybe ask in #1260489193763639338 so the question doesnt get lost
Guys, are there dynamic colors for any particular media item? Or only those in the queue?
@grand jewel I suppose we can't, because we have many artworks, so it would be too resource-expenaive?
Correct, they will only be fetched for the current+next queue item
But we could add an api command to retrieve it for a media item or media image.
That must ofc be used with care. Not wise to call it on a large listing of media items
Yeah that was my next question... But actually we will be doing it on device - I'm examining current logic on server to make it similar.
We do it now - but colors aren't that great
But looks like we can match them to server ones pretty easily
PoC is in works.
So we won't need that palette at all.
Can’t you then just better use the server for this instead of duplicating on the client? What is the usecase?
To unload the server?
But if it's easy to expose from server, it's not worth duplicating the logic of course. These calls will be reasonably fast I think...
(depending on the server hardware for sure)
I dont think it will be THAT heavy and it can also be cached. In the end server will have more capacity to do this than a mobile client (although new ones are really powerful nowadays)
Added benefit is that you can request it for any image, not only url
Yeah it's not that bad for client as well 🙂
What should the command look like ? request the colors for a MediaItemImage object ?
Client does read bitmaps anyways - for displaying. So it's pretty easy to extract dominant color
Yeah that works
If it already has the image at that point then yes, overhead would be minimal.
I'll add the command anyways and let you decide if you also want to do it in the client itself.
@abstract trench
https://github.com/music-assistant/server/pull/4193
If you want we can also backport to stable as this is a relative safe addition
We're using local now - we'll see if I fixed it for iOS. If yes, then we can wait for next release easily!
Thank you for fast reaction!
Yeah but next stable release is 3 months away. If we do patch release, it is available in next stable patch (maybe even this weekend). Just say the word...
Okay. Let me release my fixes that might work for iOS local extraction - if it works, we're okay. If it doesn't - we will rely on server, then I will ask you to patch.
Wait, I found this:
const buildHeroEntries = (randomize = false): HeroEntry[] => {
const playlists = findFolder("playlists made for you", "made for you");
const mood = findFolder("find your mood", "mood");
const stations = findFolder("stations for you", "radio stations for you");
const releases = findFolder("new releases for you");
// "Artist-focused" stations = artist items inside the stations folder.
const artistStations = (stations?.items ?? []).filter(
(i) => i.media_type === MediaType.ARTIST,
);
...
Does that mean that Top Picks for you - is completely frontend feature?
Yes and it will break with the new server localized approach so meh
Yeeah. I think we'll skip it.
Yes and then we better look for a way to offer this from backend in a structured way instead of this magic guessing on frontend. I missed this tbh, otherwise I would have raised this concern earlier.
As far as I can see, there isn't a way to grab multiple media items (bulk fetch) with one command to the server at the moment using either item URIs or item/provider ids. Is that correct?
I'm working on shortcuts for mobile and currently need to do a music/item_by_uri for each shortcut. That should be fine as I'd expect the number of shortcuts to be pretty small (less than 10), but just wanted to double-check there isn't a better approach I'm missing.
Yeah I was looking into that and you're right most probably.
hmmm, that is indeed a bit suboptimal, but most/all of that data should be cached so these calls will be cheap.
If it ever gets any performance issues then we can look into it and maybe let the backend provide the data in one-go.
Yeah I don't think it's the end of the world for this use case. Long term, it'd be nice to have a version of item_by_uri (and probably get_library_item) that take an array of identifiers and then return an array of items.
Just another thing to double-check: there's not currently an event that the server fires when shortcuts are added/removed right?
I wasnt really involved in the shortcuts feature but I suspect/fear that it is frontend-only and just stored in the (serverside) userpreferences. If it's something we'd like to be useful in all clients, we should make it a server driven feature
Yeah that lines up with my impressions from reading the code: the shortcuts are stored in user preferences (in sidebar.shortcuts) available via auth/me and the frontend uses a repository/store "update local and then send to API". Obviously the consequences of that are that there's more complexity in the client (you need to maintain a local store) and there's no way for other clients to stay consistent without polling. You can demonstrate the latter by adding a shortcut with the web client open in two tabs - the second "instance" will be out of date until a refresh.
Again, not the end of the world! It does feel a bit out of whack given how the rest of the API works though.
From my perspective, the ideal would be to have shortcuts exposed as a list somewhere for displaying in one place (as they already are in sidebar.shortcuts), but also have an isShortcut property on items and have shortcut edits fire the item updated events (like with favourites).
Do you want me to file an issue against the server? It's potentially something I can contribute as I'd rather hold off on implementing add/remove in the mobile app if we're going to be able to avoid needing to store the shortcuts state locally later.
I think we need to tackle the issue properly and implement it serverside. That will also solve your "I now need to do a request per shortcut" problem. Ideally the feature is exposed as additional endpoint/command and we will fire an event if the collection changes for a user. I'll write it on my TODO list for the next couple of days. Shouldn't be a big deal to adjust.
Yeah sorry if my description wasn't clear, I meant that the server should handle all that: shortcuts endpoint and events fired when they change. Sounds like we're in agreement. Thanks for taking care of it!
One thing though: I do think having a property on items would be really useful. You can ideally add/remove shortcuts from anywhere you see an item, so if that data isn't part of the item itself (and only part of specific shortcuts commands/events), you're always going to have to maintain a local representation of shortcuts and watch shortcut specific events from anywhere items are displayed in clients.
Of course if a client doesn't implement shortcuts then this probably adds some amount of wasted performance overhead, but I'm assuming that should be able to be pretty minimal.
For library items this would be super easy to add as it will just mean joining another db table but if we want the shortcuts to be flexible so you can reference ANY media item (so also something that is not in your library) then it will come with a performance penalty as we'll need to resolve this for every item.
So maybe better would be if a client just keeps the list of shortcuts in its global store so it knows this itself ?
Are shortcuts only meant to be linked to an media item or like a small automation which triggers a playback of specific items to a specific player (group)?
For now its just a shortcut to a media item but if we do it right we can extend it by attaching a preference to it what you want to do when clicking it. That would however be a future enhancement though (if there is enough interest) - for now it would just be terated as any media item
Yep can understand a simple play or pause action would be great so I don’t want to use HA to connect a shortcut with a player as I always listen to a specific playlist at specific rooms so yeah this would be a nice addition for the future. Personally I would only allow to use media items of your collection as I want to have a direct feedback of a shortcut and not waiting like a second or two to see it.
BTW: If shortcuts are added to players a player should also can call a shortcut based on a button press for example but that’s not the scope of the app here 😉
I've not read enough of the server code to have context to authoratively comment, but I'd guess the performance hit would still be minimal for non-library items: you'd need to hit the DB to grab the shortcuts, and then add the isShortcuts value to the response by matching the items based on URIs (the ol' "in-memory join" essentially). If that work (decorating items with whether their shortcuts or not based on a list of shortcut URIs) doesn't happen on the server, it has to happen on the client anyway so it would still feel worth the hit to me? Again though, I could likely be talking nonsense as I'm coming from a "here's how the server might already be implemented" rather than than a "here's how it actually is" perspective 🙂. Would be interesting to know why I'm wrong here if I am!
Yes we already do this for the new image_id (for the imageproxy) and the translation support but we should be careful of not adding too much "JIT" processing for every item that crosses the wire but I guess one more is not that bad.
Given the fact that shortcuts are just a handful of items, we can just keep it in memory so it will be cheap to add it.
Leave it with me and I'll come up with something later this week.
Amazing thanks! Yeah that's a great point that each feature like this (favourites, shortcuts, images etc) in isolation isn't much of a hit, but it could definitely snowball long term.
Haha! Yes.
So I'd imagined that the app could be released independently of the server and that the app should be backwards compatible through fallbacks (optionally fields in parsed responses etc), but I don't have a feel for if that will work long term with Music Assistant. Has there already been any major versioning of the API or things being moved around rather than simply being added?
We already have some forks based on schema.
Forks of the server or you just mean the schema changed?
Sorry I misread, you mean forking logic based on the API response/version I think. Yeah, the shortcuts stuff I just worked on doesn't require the data to actually be in the API response - very recent example.
See, now we have server strings, and DSTM renaming - both are basically breaking changes.
Good thing that server is deprecating things instead of removing them - but at some point that will be the case too. 🙂
Yeah, but I think it's ok to eventually drop support for older server versions as well.
I would drop support every 3 stable releases.
But yeah, I'd hope we can just maintain an independent release pipeline, but obviously we'd probably end up planning some feature additions around things be added to the server.
I think there needs to be a policy until it becomes an issue really!
I'm being optimistic I guess haha
Absolutely as everybody puts effort into it and effort should spend on features and not to support a small userbase that are too lazy to update a HA APP.
Sorry: "don't think there needs to be a polcy"
Yeah that's fair
I usually update every time after a patch release is available. So 3 major stable releases are a long way if we think every quarter comes a new release
Major = .10, .11 etc
Is there a normal cadence for server releases?
I have auto update on my server through Portainer fetch 🙂
Should disable it probably - it might interrupt playback. But we're not THAT addicted to the music.
HomeAssistant + OS is updated very late as it runs so stable I usually skip 2-3 monthly releases and then I got so much more to explore.
Yeah, HA is different thing, I use MA on Docker.
More convenient to me.
I would not spend so much time here to support older releases as in the current state I would assume a lot of changes are still coming due to Sendspin etc. So I would be here in the beginning a bit more strict
Had a complex Proxmox setup but now only a thin client for ha+MA. The rest runs on unraid: TVheadend, Paperless, Scrypted. So if one breaks the others aren’t affected
Yeah that's what I have - Proxmox cluster with several LXCs and ~30 Docker stacks... Deep is the hole.
Ok so looks like the release cadence is every ~3 months: https://www.music-assistant.io/release/
So yeah saying the app supports the current and 2 previous stable releases feels right. That basically requires people to upgrade very 9-12 months.
Although I imagine in practice we'd actually support more releases (even if it's accidental)
I can just add a note about compatility to the README @abstract trench if you agree with the 3 release number? I think that gives us a nice window for users where some things might have to be optional/flagged, but not forever. We'd probably also just want to try and keep compatibility with the current beta, but not need to sync releases with the server.
Yeah feels okay. Just remember that recently we got pretty major upgrade (Sendspin) and DON'T support versions before that...
Migration to Sendspin.
That luckily would be our "oldest" release (2.7, 2.8, 2.9), so we don't need to put a caveat in. Nice.
I've added a to-do to write this up in the README
So did you also want to change branching strategies for release @abstract trench? It looks like right now we have automation for both iOS and Android and it handles version codes, names, tags and the GitHub release etc. I guess we'd just need to update some of that config for release naming.
For open source GitHub branching, I've found doing PRs to main, tagging releases and then branching off the tags for hotfixes (patch version) works nicely.
And that would work with your current automations I think
Yeah, I actually was setting it up like this... It should work - we just need to cleanup and groom tagging.
Cool I can add a note about that as well!
Should we rename dev to main?
Well, we can work with whichever. But I think it's ready to be renamed. From there we can branch releases I think.
(if we need hotfixes)
Yeah I like that way of working because you only need to care about branches for the hotfixes rather than maintaining multiple at once all the time
Yeah let's rename to main just so it's clearer for folks coming in that's it's the default/trunk
Will change my local now before I forget 😂
Why keep backwards compatible up to 3 releases ? That is 9 months.
I would do just do latest stable release -1 (so max 1 older stable release)
And with the schema versions you can even add features that are still in beta.
True also
Just felt like the right sort of time period, but honestly I'd imagine you have a better sense of how up to date people are staying. If latest stable + 1 (so right it'd be 2.8 and 2.9) feels good to you, I'm happy with it.
Will iOS also be moved from teancoms personal account? Or will that be a concern for later? It works for now, but might be good thing to think about (would be nice if app could be migrated to OHF account including the current TestFlight history and users).
I've been pinging Marcel / Martin occasionally about that, hopefully we can figure it out soon.
Ah I'd thought it already had! Thanks for pinging. Yes it should move I reckon.
There's request for Sleep Timer in the app. Should we do it locally, or will it be implemented on server? Because as far as I'm concerned, having local timers for remote players would be awful idea - so we would implement only for local player.
I would prob. wait for server, there's a discussion on it; https://github.com/orgs/music-assistant/discussions/5526 if we only would implement for local player, we'll likely get people saying they're using app to control their bedside speaker, but the timer doesn't work/isn't there.
Weeeeelllll that feature request is one of many. 🙂
Fair😇 And currently not very popular under the voters…
Yeah. I closed issue anyways - your reasoning added to my hesitation.
I was actually thinking about sleep timers just the other day (well, night, as I was listening to an audiobook in bed. 😄). Anyway: https://github.com/music-assistant/server/pull/4432
Don't do it please.
Let's deliver features that don't raise more questions than resolve.
I mean, good that you started it 🙂
What questions?
Nevermind. I was reading like blind. I thought you did it on mobile.
Great foundation!
Oh, phew. I was really confused for a second. 😄
🙂
Sorry
We can work with that API from scratch, and it also stops player automatically, so purely UI feature for us.
Ha yeah I had the same reaction until I saw "server" in the URL.
I shouldn't have dropped a server PR into the mobile-app thread, that's on me, despite it being inspired by this thread. 😅
Continuing conversation on the icons @mellow widget @woven mesa @river sonnet
I guess we can use lucide font as well to generate full map
Same way we do it for MDI now
There is official font
So now we would: search MDI, search Lucide, then search in subset of custom icons.
For custom icons we will need their names and SVG.
Yeah to clarify (based on your last question in the other channel @abstract trench ) I meant that would mean clients could just map to whatever icons they want. Front end changing to Lucide wouldn't matter - we could continue using MDI in the app. Obviously that's not the place we've ended up in though!
I think if we need to support two sets, your plan is good @abstract trench 👍
But I would be interested to know if instead of introducing another set of identifiers, the front end could just map their new icons to mdi ones and continue to have them as the single set of identifiers that the API serves. That would also mean the front end could show "newer" icons for the existing mdi selections.
We could have something like this:
{ "id": "homepod-mini", "source": "custom", "svg": "custom-svgs/homepod-mini.svg", "codepoint": "U+F500", "mdi": "mdi-speaker" },
{ "id": "sonos", "source": "custom", "svg": "custom-svgs/sonos.svg", "codepoint": "U+F501", "mdi": "mdi-speaker" },
{ "id": "mac", "source": "custom", "svg": "custom-svgs/mac.svg", "codepoint": "U+F502", "mdi": "mdi-laptop" },
{ "id": "apple-tv", "source": "custom", "svg": "custom-svgs/apple-tv.svg", "codepoint": "U+F503", "mdi": "mdi-apple" },
{ "id": "speaker", "source": "lucide", "lucide": "speaker", "mdi": "mdi-speaker" },
btw:
I have to boost, but will catch up on this later 👍
As far as I know, aliases are also defined in MDI, right? Do we also need these aliases for Lucide and custom icons?
Something like this to add on every entry ?
"aliases": ["homepod", "apple-homepod-mini"]
I would love us to also have some (custom?) icons which are area based, such as a kitchen or a livingroom.
The Hue app has this as well and its really nice for recognition of a speaker
Do you mean this would be part of the API response?
MDI icons have these kinds of options already don't they?
I guess I'm a bit confused about the general concept of how this should work. My impression was that MA and HA both used the MDI icon set to give users a broad set of choices, but that that obviously meant that clients all had to support the MDI sets (based on string IDs). And now MA is planning to support both Lucide and MDI (maybe eventually removing the latter) with each having its own set of IDs at a server level. Is that correct?
I mean we could still have the name as source of truth of the backend and have this kind of solution for custom icons and lucide
I get it but tbh, mdi icons feels like they were created 20 years ago. It would be strange to invest a lot of time to enhance the ui/ux with new components and keep mdi icons
I know this situation is kinda annoying for you, I totally get that and we should have thought about it before (sorry about that)
But I really think we should get away from mdi and find a proper solution with lucide and custom icons
I think it's also that it feels a bit overly disruptive to have a visual change in one client affect another? If we think of MA as a system with a server and multiple clients, which web libraries are used in the web front end shouldn't affect mobile or desktop clients right?
I do agree that it's not ideal to have the IDs of the player icons tied to MDI though. Was there any discussion of trying to define a smaller icon ID set (on the server) not tied to any actual icon set to give clients more flexibility? I do think that would be the ideal setup personally.
One other solution is that we could have one entry for the mobile app and another one for the desktop app ?
Just a remark - it really feels like a bias against. 🙂 But I am tech guy and never was good in understanding UI (although I'm good with UX).
Anyways - as Lucide is "official" now, I think we will have to support it.
Don't bother with substitutions from MDI - if we do it, we do it.
I guess something I should say out loud that I've probably made too implicit is that I don't think the server should have to know anything about the client. It should just say "this player looks like a speaker" and the clients can decide how that looks.
Right now that is sadly all tied to MDI so I guess I'm arguing for a different setup entirely really
(I honestly don't see THAT much difference in https://icon-sets.iconify.design/mdi/desktop-classic/ vs https://lucide.dev/icons/computer - apart from changing things everywhere. 🙂
Well if we add our own icons for players, with picker and stuff - that's another thing. But we want it to be uniform betweeh FE and mobile...
Would definitely be good to have some MA icon set, distributed for everyone.
I'm not meaning each client has its own picker. Just a shared ID set and then they map to whatever icons set they use.
But then players in app would look different from Web.
( == a lot of complaints)
I guess I don't see that as a problem as long as a speaker always looks like a speaker etc, but I agree people might complain
Also you would need to map those values to icons manually
For MDI, for example, we take font, and rip the names out.
It'd let you use more native feelings icons on different platforms and update look and feel over time without having to change the "schema" or format of the player data . It's definitely a trade off though I get it.
But yeah maybe that's me trying to redesign everything from the ground up (it is).
I agree with this as well to a certain degree. We just just have a curated picker of an icon/devicetype and then each client can translate it to what they support. But... in terms of consistency having the same icons, regardless of client would be even better.
The whole point that drove this was that we want to have recognizable icons for the players. Like an Homepod to describe the speaker type or like a symbol to describe the kitchen.
What if.... we only have custom SVG icons ?
And we have an endpoint to fetch the icon data ?
A bit like imageproxy
This is btw also where we need to go for the providericons as currently they're embedded in the provider manifest payload but that is becoming painful
Yup that would be cool.
Yeah I think having the icons work as SVG image assets the server can provide us a nice compromise: clients can't have their own styling (which isn't a huge loss in practice), but get the advantage of not having to worry about mapping icon IDs and most importantly icons can be updated without clients need to make changes.
I'd need to remind myself how iOS/Android do with dynamic SVG rendering these days, as I seem to remember that unintuitively being a problem.
Ok did some quick research on this and it's in (sadly) still in the state I suspected: no native rendering of SVGs in iOS or Android. It does look like there are libraries for doing SVG rendering for both iOS and Android out there (https://github.com/BigBadaboom/androidsvg and https://github.com/mchoe/SwiftSVG for example) so I'd imagine we could end up using these to back a shared component. Probably makes sense for us to do a quick spike on showing an SVG icon like this before deciding it's our favourite route though.
I guess there's also the potential of having the server provide icons as SVGs or BMPs with the API accepting dimensions for the latter. I don't like that as much, but it might be that that something like that is more practical given Android/iOS lack of (dynamic) SVG support.
Oh that would be sub-optimal... 🙂
yikes, you would have expected this to be in a better shape in 2026
So we're back to the drawing board then
I don't think it's completely out of the question to show dynamic SVGs, but yeah it's definitely not as straightforward as any of us would hope.
Then maybe back to the idea of a preselected set of icon names for which we'll have a mdi and lucide variant but that still leaves us with custom icons, unless we can reproduce those with both
Well we can show SVG icons statically: they can be converted to vector assets for Kotlin Multiplatform to use.
Yeah and no server-side updates
So if we migrated to a small set of custom icons, we could just publish the SVGs for each icon ID that the API will serve and the clients can either use them or convert them (which mobile would do)
Obviously less choice for users, but means that icons can be consistent and can be updated over time without too much disruption for clients (they can just update their packaged assets)
Yeah, I guess that would be the best way forward. We just need to make sure to not change that icon set too often 😅
I mean it's definitely pushing more work into visual design, but an upside is being able to have more music centric visuals.
Roon has a limited set that worked really well in my experience: https://roon.app/en/mastery?id=7c3f5d6d-02aa-4b49-8cde-55b6705e656e
Master Roon with our updated tips, tricks & advanced features guide. Expert insights, hidden features, and pro techniques for power users. New tips here.
You can always add to it (there would need to be a default generic fallback "speaker" icon), but yeah we'd need to be careful not to do that a lot and end up with clients needing to ship 100s of icons anyway.
Font would be better 🙂 Single file, vector inside. Both MDI and Lucifer have font.
The Roon icons are great indeed! I was a Roon user before I started MA (maybe because of it)
Oh, you will find friend in @mellow widget 🙂 He's Roon lover.
Roon is amazing but its such a closed box that I kept running into stupid limitations (especially with its weird api) and that got me into building MA
But still, everytime I see it I'm in love with their UI
I mentioned this before but that’s literally true of me. I was on a 90-day trial before buying a lifetime license for Roon, and I spent that time building out a Winamp-skin compatible client, and on day 89 decided I couldn’t take their weird API limitations anymore and started working on MA stuff. Literally that day. 😄 Haven’t looked back…
Ha wouldn't say "lover". There's many reasons I ditched it for MA.
Roon ARC was 'meh' at best 😬
Yeah neither the main Roon app or ARC actually provided decent stable audio playback on Android.
But anyway, Roon is a good example of using a limited icon set. Is that idea something that we could feasibly adopt? It sounds like @fast grove is onboard, but I'm aware that it's a change in direction for the front end, and requires design work to define the icon set.
I'm happy to write up a more formal proposal if that would be helpful
I'd rather have a limited icon set which is consistent than something that is inconsistent
@brave sentinel for reference, this is the prompt that I use in claude.md:
# Persona
From now one act as pessimistic, self-critical code architecture professional with anxiety.
Check yourself on every step.
Ask questions if there's littlest ambiguity.
Don't accept "good enough" solution, make it the best one, robust, complete, optimized, effective and architecturally perfect.
Also make MINIMAL changes to the code upon refactoring, be short and specific.
Be reasonably sarcastic, but no exsaturation or induced sarcasm.
Use SOLID principles where it's reasonable.
Use KISS principle, don't over-complicate solutions. They should be laconic and sharp.
I do as well, actually 😕 You've sent it to me before.
Right, forgot it..
🙂
I still have sometimes to do something like I just did:
Stop and roll back what you're doing. I don't think introducing bunch of new booleans and constants will help. The behaviour should be reactive, not stateful. Re-read your system prompt, and act as experienced engineer by SOLID and KISS principles, instead of introducing more ambiguity and potential fail points into the code.
From next Android we won't be able to force portrait mode on the phone.
App should be able to support any orientation/screen size.
https://play.google.com/store/apps/details?id=io.music_assistant.client - can anyone verify if it's visible not only for me?
Requested url cant be found on server
How about this one? https://play.google.com/apps/testing/io.music_assistant.client
🥳
Lemme grab my android device and see what happens 🙂
Okay, everyone merging the PRs into main now:
- We squash.
- We give readable descriptions.
Okay? 🙂
We can close beta for now @fast grove what do you think? Or should we make it accessible to everyone?
I always have to read these rules about 3 times, but I'm pretty sure we're still ok for Android 17 as we only lock on devices with a screen width of <= 600dp ("compact devices").
https://developer.android.com/about/versions/17/changes/ff-restrictions-ignored
Is my Galaxy sw600dp? Then we're ok 🙂
We only lock on those devices so if you can't rotate then it is!
Sounds good. You can enforce squashes for the repo by going to Settings > General > Pull requests and then unticking all the options except for "Allow squash merging".
I personally prefer merge commits because they allow for finer grain bisecting. You can always view the PR merges with git log --first-parent? This isn't really a hill I choose to die on though. If we want to squashes, I'm happy to just have it as the only option.
While we're at it, it would also be nice to make sure "Require status checks to pass before merging " is enabled for main as in branch rules as well. I can add a PR template to encourage more detail in PR descriptions.
The log in What's new is built from PR descriptions, so I just don't want things like "fixed tests" in Play Store. Other things don't bother me 🙂
Could we not use --first-parent there? That's what GitHub's generated release notes do as well.
Yas! On my device now.
The thing we are going to have to deal with is local network access permissions once we target Android 17. That won't be required until August 2027 though.
Will that work nice with all the other stuff like squash?
The formatting? Yeah it just filters the commits. You can try git log --first-parent --pretty=format:"- %s" android-v0.4.0..HEAD -- . ':!.github' as an example.
I can do an update to the actions and README tomorrow as well along with a PR template. We'll want to stop the debug APK release now that we've got Play Store betas so we're not publishing with two keys (as we discussed a wee while ago).
Let's wait on Marcel's decision. If we stick open beta - then yes. I honestly only wanted to see if it works. We can disable it.
Yeah In guess OHF has pretty reliable account
Hmmm looks like builds are having a hard time hitting the CDN for the webfont: https://github.com/music-assistant/mobile-app/actions/runs/28446110101/job/84313669977
Could just be temporary. If it keeps happening we can package it locally.
And on that: @woven mesa , we was there any reaction to the discussion about the propsal for creating a limited icon set for all MA clients as a migration away from MDI (as an alternative to using Lucide)?
I did not really understand sorry. You wanna remove lucide for all the MA clients ?
No, we'll create a list of pre-approved/pre-defined icon names and then these can be translated to the icon set by each client
Yup! Sorry was on a call. Thanks for jumping in @fast grove.
So for the moment should the mobile app just remain the same, and we can adapt to the new limited icon names when the server is able to start providing them?
We'd also discussed having SVGs available for each of these icon names that clients could use to keep things consistent. Are you thinking that would be available at the same time @mortal wadi or that it'd be a future thing?
SVG's is possible but last time you said that the SVG support for mobile apps was severely limited.
Dynamically yes, but we can convert SVGs to vector assets and include them statically. So for a limited set it's definitely doable. I'd mentioned that here: #1343766783768789002 message
if we will have static set of those svgs, it's the best variant. We just pack them with the app, no loading.
Ah ok, makes sense. Let's go with a static set then of icons and extend them later.
I'm assuming that Lucide icons are in fact also just svg's ?
They have a lot of forms they distribute. SG is one of them. If we take subset of that, w e will be able to move that into our project code.
@fast grove sorry to spam you, but I reckon this probably got buried by other pings: #1343766783768789002 message
I leave that call for you but in general I'm a fan of public betas for open source projects
Nice! Ok so I'd vote we just make it public with a link like we have now (not sure if it's searchable in Play Store, but either way seems fine). We can add the link to the README like we have for TestFlight and stop building the "self signed" release @abstract trench
And for Apple appstore lets pick a moment this week to set everything up
@abstract trench I'll do a wee PR for the Android workflow changes
Ummm, I tried to put fake power control into one of my sendspin players. It "worked", but mobile app stopped receiving any sendspin players in players/all. Only if something changing on the player, it gets received. I removed that power control from player, but still don't receive any of them.
P.S. Guess it was some fluke, restarted MA and issue is gone.
I've created a "1.0" milestone so that we can demarcate things that should get done before a wider release vs things that could be done now or later down the road: https://github.com/music-assistant/mobile-app/milestone/4
I'm going to start having a think about new things that should go in here as well!
@abstract trench @mellow widget I have been looking into a proposal to consolidate the "audio chain" we talked about before and let my robot create a draft PR on the mobile app repo.
Most important change is that with this proposal you no longer have to collect the chain from various sources and the quality label will be determined server-side.
I can imagine that mobile app is only interested in displaying a portion of the data.
First feedback I'm looking for is if this is the datamodel you expected @mellow widget ?
BTW: Sorry that the PR arrived before my message here. I am experimenting with CoPilot desktop app and it was a bit more eager than I anticipated.
So I wrote some thoughts in the PR, but it sounds like:
we're able to fetch the audio chain information through a command (by queue) and get an event every time it updates. It looks like we get easy access to everything in about inputs, outputs, server processing and DSP through that command/event data which is great.
Is that a correct understanding of the general approach here?
yes, and with my comment that the AI overengineered some stuff so I'm tweaking
And then essentially the chain breaks down into input, processing (volume normalization etc) and outputs. The outputs contain the DSP details, the input and outputs get a "fidelity" each and there is also an overall summary of the fidelity.
I'd more expected the processing and DSP details to be one chain, but I can see that it works the way it does because "processing" happens before the outputs and then each output can have it's own DSP.
yeah, there can be 2 types of processing happening;
on the input signal: volume normalization
on the output signal, resampling, dithering, dsp, limiter
Would it make sense to have those in a list/array rather than properties on the output? Presumably there is an ordering to them?
Also can't DSP be multi stage?
My other thought was that the fidelity summary seems superfluous? There are also a bunch of properties like "revision" and "state" that don't seem needed for how I'd think to implement it, but they might be things that other clients would find useful, or I'm missing something.
I guess the other important question is around event timing: I'm guessing we get the event every time any of these details change for any queue right? So if I start playing a track I get an event, I skip to the next track I get an event, I play something else I get an event etc.
OK, final proposal for the models looks like this (python, but you get the point):
class AudioQuality(StrEnum):
UNKNOWN = "unknown"
LOW = "low"
STANDARD = "standard"
LOSSLESS = "lossless"
HI_RES = "hi_res"
class AudioNormalizationMeasurementSource(StrEnum):
UNKNOWN = "unknown"
TRACK = "track"
ALBUM = "album"
LIVE = "live"
FALLBACK = "fallback"
@dataclass(kw_only=True)
class AudioFidelity(DataClassDictMixin):
"""Effective audio fidelity."""
quality: AudioQuality = AudioQuality.UNKNOWN
bit_perfect: bool | None = None
@dataclass(kw_only=True)
class AudioInputDetails(DataClassDictMixin):
"""Audio entering Music Assistant."""
# Provider instance that supplied the stream.
provider: str = ""
# Original/provider audio format.
audio_format: AudioFormat | None = None
fidelity: AudioFidelity = field(default_factory=AudioFidelity)
@dataclass(kw_only=True)
class AudioNormalizationDetails(DataClassDictMixin):
"""Effective volume normalization."""
mode: VolumeNormalizationMode = VolumeNormalizationMode.UNKNOWN
measurement_source: AudioNormalizationMeasurementSource = (
AudioNormalizationMeasurementSource.UNKNOWN
)
target_lufs: float | None = None
measured_lufs: float | None = None
applied_gain_db: float | None = None
@dataclass(kw_only=True)
class AudioQueueProcessing(DataClassDictMixin):
"""Shared processing before player-specific output processing."""
# Internal PCM format, including F32 headroom when applicable.
pcm_format: AudioFormat | None = None
normalization: AudioNormalizationDetails | None = None
playback_speed: float = 1.0
# Effective mode; DISABLED means no crossfade processing.
crossfade_mode: CrossfadeMode = CrossfadeMode.DISABLED
# Whether another audio source is mixed into the queue stream.
overlay_active: bool = False
@dataclass(kw_only=True)
class AudioDSPDetails(DataClassDictMixin):
"""Effective player-specific DSP."""
state: DSPState = DSPState.UNKNOWN
input_gain: float = 0.0
filters: list[DSPFilter] = field(default_factory=list)
output_gain: float = 0.0
output_limiter: bool = False
@dataclass(kw_only=True)
class AudioOutputDetails(DataClassDictMixin):
"""Audio delivered to one or more equivalent player outputs."""
player_ids: list[str] = field(default_factory=list)
dsp: AudioDSPDetails = field(default_factory=AudioDSPDetails)
# Only set for explicit left/right routing.
source_channel: AudioChannel | None = None
# Furthest downstream format known to the server.
output_format: AudioFormat | None = None
fidelity: AudioFidelity = field(default_factory=AudioFidelity)
@dataclass(kw_only=True)
class AudioProcessingChain(DataClassDictMixin):
"""Effective audio processing chain for the current queue item."""
queue_id: str = ""
queue_item_id: str | None = None
input: AudioInputDetails | None = None
queue_processing: AudioQueueProcessing | None = None
outputs: list[AudioOutputDetails] = field(default_factory=list)
most important ordering is the division between inputchain and output.
sure but that would make the model even more complex while in real world it doenst matter for the presentation. Most important is at which part of the chain something happens and that is now reflected in the one object
I think I'm getting confused here as I'm not seeing the definition DSPState or DSPFilter. Is filters the list of DSP stages?
It looks like this new proposal is a clearer on that: you get input, processing and outputs and then each output has its own DSP details. Overall fidelity can be calculated from the "worst" one out of the input/outputs if you want to show it for the whole chain. I like that standard quality has stayed in there as well. Thanks!
As far as I can see, this gives us (and other clients) as much detail as we'd ever need, but also seems like it doesn't make building simple visual representations awkward either.
One thing I can't think of a reason for is queue_item_id? I'm probably missing a use case here, but in my mind you'd only ever be fetching (or merging) these by queue ID, and I'm not sure what value being able to link it to a queue item gets us.
the audio stream is applicable for the queue item it relates to, not the queue, or even better said, on the streamdetails of the queue item.
but we can fix this by storing the AudioProcessingChain on the streamdetails itself instead of giving it it's own endpoint. then you can guarantee that the bundle stays together.
Right yes that was a brain fart on my part. You want queue id and queue item id for it to be properly addressed. I think the idea that you could fetch by queue threw me off there so that's one vote for attaching to the stream details instead 😂
yeah doing that simplification 👍
So basically to get the AudioProcessingChain, you'd go through the QueueItem so you could get that for the current item from PlayerQueue for example?
yes it will only be filled for queue.current_item
And you also thinking that clients just use the queue_updated event instead of introducing a new one specifically for the audio chain?
If it's on current_item, that would make sense anyway
lets just jeep the queue_updated event for this to keep it KISS
Yeah I like that
It makes it very easy for existing clients to adapt as they'll probably already be using that command or hooked into the event
Yes and we'll keep the old data intact until 2.10 stable
Yes true
so clients have some time to migrate
And if for some reason you did just want to build a client that showed audio chains, it's not a big ask to fetch the queue and then get to the chain through current_item.
The only thing I left out of the server models is the color per quality as I think that should be something client decided
Glad about the direction you guys are going in. 🙂
We're all in this together - with proper aligment we can achieve great things
Yeah agreed that should be a client decision. Colors could be different depending on the scheme used by the client.
@abstract trench I'll start writing up issue(s) later today or tomorrow around using this. I think having a good way to get between the audio chain and DSP settings (and vice versa) is something to add as well.
@abstract trench just on all the APK questions: are you not able to download the universal APK from Play Store console like I suggested, or is that not available until after review?
It's impossible with API. It's easier to build both APK and bundle together.
Do we have access to keys to do that?
Ah sorry just seen you've already made the change
Right so yeah you're using a different key (the debug key) because we only have the upload key not the actual signing key (not sure if it's an uploaded one or a Play generated one)
https://github.com/music-assistant/mobile-app/commit/5f667e8c3380468c0ffcee1b87ab48bde5054421
Being honest, I don't think we should be distributing with two keys (two valid APKs end up with different certs, more keys to manage etc.) - maybe that's how OHF does things for the Home Assistant companion app as well though?
Even if we do use two keys, I really don't think we should use a debug key as one of them. The debug key itself isn't inherently weaker (in terms of security), but it uses default aliases and passwords, is stored in a keystore with a default password and even without them being guessable it's all exposed in our Github Action config.
Yeah that's fine
I think interchangeability isn't that matter
and we have both debug and release in well_known
You want to create another key for GH APKs?
Agreed interchangeability is a minor issue! If both are verifiable then it's just adding a bit of complexity to verification, but there's no actual issue with them being different from a security perspective (other than the situation of needing to uninstall to reinstall becoming a valid scenario). I do think the Github APK one should be at least as secure as the Play Store one though: the keystore should use non-default strong passphrases that aren't shown in our action config or logs. I'm not sure how OHF usually manages keys, but ideally it's stored somewhere secure but well documented as well.
I'm guessing using the Play Store key to sign the GitHub APK is out for the question? I don't have access, so don't know if it's a Play generated one or one that OHF manages and has uploaded, but I'm assuming it's the former given that that option hasn't come up.
Well, I searched on how to use GH action with GPlay API to get that APK - but no luck. If you have this in the docs somewhere - hit me. 🙂
No I mean use the same key that the Play Store is using to sign. You can upload your own App Signing key instead of using a generated one which means you have the ability to just build and sign an APK without the Play Store being involved.
Ah. No I guess we can't
Right if we're using a Play Store generated key then I think the only options for getting a signed APK are to download from the Play dev console or build an APK and sign with a different key. It does look like there might be API options for grabbing the Play Store signed universal APK:
I don't think we can do it synchronously, while uploading the version, creating tag and release in GH action though.
Ah, need to wait for it to be generated I guess?
I guess so. We can try...
Does adding it to the GitHub Release manually after the fact cause any problems for things like Obtanium or is it just annoying?
But first I want 0.7.0 to be up and running.
It's possible and probably the way to do it.
Talking to Claude 🙂
Yeah as much as it's annoying I'd vote to just add it manually after the fact and remove automated build with the debug key. If Claude has a cool way to do it with the API that we've not seen from a search that'd be ace, but I think the manual task is worth it here if not.
I guess there might be a way to poll the API until it's ready etc, but would need to explore it more
I would generally just do this manually until it pissed me off 😂. Which I guess is basically how all software engineering works really.
It will do it in the flow.
At least it will try for 15-20 minutes.
Thanks so much for giving it a go!
Got new(ish) phone today, and the app shows predictive back now... I haven't seen it on my S21 Ultra. Had to upgrade due to lack of security upgrades... 🙁
Ah hadn't realized you weren't seeing them!
So few apps actually have it. I basically only see it in Settings.
Well, then we're cool!
@mellow widget one more thing we should perhaps add to the audio chain details in the DSP section... the preset
So backend keeps track of the selected preset someone selected I mean
Ah ok yes! We're showing that in the app right now, but not familiar with how it's retrieved (not able to look at the code right now).
We only show the selected preset temporarly after selecting it from within app itself (check shortly after disappears). Afaik app doesn't otherwise know which preset is active, its simply on/off toggle.
Currently backend doesn't track if a preset was selected but we can add that
What if user selected preset, and then tuned manually?
it would reset
or show "custom" as preset
@abstract trench think we're good to do another Android release?
Sec 🙂
Wanna merge that items fix
Ah nice you found a fix for that
Should we add another tag in gh for PRs, something like 'chore' and any PR with that tag gets omitted from the automated changelog? Thinking of https://github.com/music-assistant/mobile-app/pull/796 but there's been a few where the users couldn't possibly care less. 😅
Well you can call it that.
I see you added "chore"? Can you add omitting it from What's New?
Well, now we know that APK strategy works. 🙂
Oh wow yeah that needs to be much more complex now. I'm going to have a look at writing something up to try and simplify the view a little. Right now we end up with doubled up albums, and it's not clear where exactly things come from. The important thing for the moment is though: you can see everything in the single provider case most folks will have!
It's now how it is in web 🙂
Yeah I'm just realizing that! I'm pretty sure that has exactly the same problem. I'm putting together a concrete example to show the problem.
Wow I think web is actually broken
IDK how long would it be to fetch everything from every provider, and then de-duplicate. I guess that should be server-side. Like - all distinct albums from all providers (but then we'd have to figure out priority, so we're delivering best possible experience to user).
@mellow widget latest beta has the changes to the audiochain included - frontend already uses it
Amazing! I've got a todo to start writing up an issue and we'll discuss design for how we present it there.
Let me know if you miss something or something is not clear. It should contain the whole model as we discussed here in any case
Are the API docs updated? I'm guessing the Schema is all generated? That'll be the only thing I need as we decided on no extra commands as far as I can remember.
Yeah api docs are generated so up to date
/api-docs/schemas#schema-AudioProcessingChain
@fast grove @mortal wadi something I've not found docs for and can't quite figure out is when the API chooses to return items with library as the provider and with itemId as the library ID vs when it returns items with their "original" provider and provider item ID.
I'm meaning for items that are in the library obviously 😆
From observation, it seems like if we grab an individual item (like music/albums/get) it returns the library provider version regardless of the item_id and provider_instance_id_or_domain, but if you fetch a list (of artist albums for example), the provider in returned items (and with it the item ID) are determined by the provider_instance_id_or_domain used in the request.
if you do music/albums/get
it will always resolve to the full library version if there is one, regardless if you passed it a library id or a provider id
but listings (like artist albums) are strict and only listen to what you literally passed. This way you can divide library items from provider specific
before we had no way to divide albums in the library from albums existing on a provider
Right that makes sense so items always come back as their "library version" for the individual item endpoints/commands (regardless of how they are addressed) but come back with the provider used for the list endpoints/commands. It's "detail" vs "list" views of the data.
And is that the case for all media types yeah?
Oh, but search always returns items with "library" if they're in the library as well. Again, that makes sense.
yes
the reasoning is that if you do request the full details, you want to get everything we have and we auto resolve a provider into a library - but when looking at artist albums for example you care about your library albums but you want to use the provider albums for discovering of more
when we had that auto mangled together people got confused
Yes but search nowadays also has a filter to select providers where we also accept "library" as provider so you can lazy load the global search if you want - and return library results ASAP and teh slow providers later
Yeah it's maybe a bit of "display" logic leaking into the API which has caught me off guard, but it's definitely super useful. I think otherwise, clients would have to do a lot of work to determine if they want the library version or the provider version and that logic would end up being basically the same everywhere. Seems like a good choice!
I'd file that under "the right amount of magic"
That was exactly the reason. When the server was mixing it all together, the clients could no longer influence it. Now they have full control
I think one of the next things we should tackle on this regard is paging and filtering of lists like playlist tracks
Yeah pagination will definitely be helpful for long playlists
Yes and a stable contract between server and clients on sort and filter options
Do we already have localizations on server side in 2.10 beta? Can we start implementing it?
Same question about removing main player from group. 🙂
And will we make player icons common lib too?
yes this is already in the current beta
yes, this as well (although the frontend needs an update)
Yeah, @woven mesa is working on that - should land soon
@fast grove please take my head and put it into the server code related to those two 🙂
Another important change is that the providers icons are now no longer embedded in the manifest (as that message got super huge) but you now simply request the icon with a command
Want me to ask my bot to write you a quick draft proposal of what needs to be adjusted ?
Oh and don't know if @mortal wadi already told you but he's working on the webrtc improvements - its already lightyears faster now
You mean reconnections?
Please. 🙂
everything
Awesome news!
Oh, another thing: are we 100% positive that server can give up to 30 seconds of stream upfront? I will be re-checking this part on app side, so want to be bold on server side.
I'll probably finish the icon lib either next week or the week after @abstract trench 👍
Didn't have the time to start it before
Yes and it can even give more if you want
Will dig into this. 30 sec should be more than enough for any hiccup.
We're also working on a simple HTTP endpoint to fetch audio (with a api command to create a virtual player/queue) which could be something in potential for remote listening (and then fetch a very large portion of the queue stream for offline playback) - but let's first see how far you come with using sendspin
Wow, so those numerous user requests initiated some implementation?
I personally use our app for all this time in my car - and have no problems.
It is more intended for endpoints which we cant control but that can take a URL
It's still a bit in discussion but I think it will land within a few weeks
That is good to know!
Just verified buffer: started playback, and after several seconds turned off wifi and mobile data. For 25 seconds it was continuing playback, and then I turned data conenction back on - no gaps everything plays smoothly.
So the complaints about insufficient buffer are false.
Of course, buffer is wiped on playback pause (server sends stream stop command, so we dismantle buffer)
yeah, and this only fixable with a complete offline playback player I think
but also more complicated to maintain
That's actually the biggest gap between the non-mobile use-case and the mobile-app use-case, imo. Our phones are constantly interrupting playback for a second or two or three, to announce incoming texts or when I pop an earpod out to talk to my fiance or because GPS is telling me to turn left, etc. My stream to the wiim in my living room isn't interrupted unless I manually hit pause. And it has my home network to use to connect to my home server 10' away. "Fixing" pause/resume on mobile in some way is imo the biggest bang-for-buck thing we could do. I don't have a solution (off the top of my head) just noting it as a real difference in kind.
Wait, Android "ducks", no pausing. So no dismantling
iOS decides for us, and - apart from explicitly opting in to pause-not-duck for audiobooks and podcasts - we have no say over which one it decides to do.
Well if user uses FLAC - I can't imagine the potato internet that will give troubles on playback start... 🙂
Maybe some Edge.
I don't often have problems with resuming (though spoilers, I'm working on a PR to better handle resuming from the lockscreen and immediately-on-foregrounding) but it's often a couple of seconds of wait-wait-wait-ok-now-audio-is-going-again. Which only happens because we've wiped the buffer.
Hmm I advertized 60 seconds in client/hello buffer_capacity (60 000 000) - but server still gives back ~30 seconds. Is this incorrect place to ask for more? 🙂
ah crap, spec says it's bytes.
Not microseconds
right?
it's pretty likely that we cap it in the server at 30 seconds still
Well it doesn't hurt to advertize more, right? 🙂
If it's bytes, I'd actually decrease to ~30MB (so 30_000_000).
But if it's microseconds, I would make that dependent on codec used.
Ok so I made the repo, i was curious after our discussion ahah
If we agree on the process, i will implement the solution on the fe and be repo
I made a preview icons with github for you to see it directly what we have: https://music-assistant.github.io/shared-icons/
Since we didn't decided on the other icons, i put a small list but definitvely open to discussion/enhancement
Ok that's something to work with I guess.
Right now we have material icons for players. So many users gonna have that. We will need to replace those with equivalents, or drop to default, right?
On the fetching: there's three ways.
First, we fetch from repo online by name. Fastest, dirtiest, online updates.
Second - we include all icons from repo into each build. Chunkier, harder to update (build required).
Third: we watch the folder from mobile repo, convert that into font on change, and include font into the app. Harder on build, effective in app, but again - harder to update (build required)
we can maybe setup versioning on the icons repo ?
once you release a new version of the icon set, it created a bump PR towards frontend and mobile app repos ?
Yeah that works too.
Still have to decide how to consume it
SVGs or font
A couple of questions:
- How often do we expect this to change? From the tone of the README, it sounds like it will hopefully be rare and that any changes would likely be additional or just tweaks so not things clients would absolutely need to take up quickly.
- Will the front end only allow picking options with the manifest IDs? If so, clients don't strictly have to deal with the mdi or aliases. I'd actually be tempted to not even define them to prevent them hanging around forever and just do a migration as part of the next server update.
@abstract trench I'm thinking if the rate of change is low, we can literally just convert the SVGs to vector assets by hand (I volunteer as tribute) and create an id to asset map. This is a small enough set for that. If we have to change/add an icon later, it's a very small amount of work to convert and alter the mappings. Again, that depends on us thinking of this as a low rate of change set: if this repo is accepting a new icon every week it will get very taxing.
- So i just put a couple icons to just show the process but the goal would be to decide now which icon we want to have there and add new ones if really needed but it'll be rare i think
- Yeah the goal, is to remove the prefix and use default like speaker and so on
Cool! I think as long as it's roughly that scale we can go with a manual approach for now (we'll just download the SVGs for a release tag whenever we want to update).
On 2, do you think we can just remove the aliases and mdi properties from the manifest then? Better to remove them now than have to maintain those forever I reckon.
Guys, how do we deal with beta server?
We're getting issues related to beta like several days a week.
With the way MA server is, I can't run both stable and beta simultaneously. For that I would need to have another Docker server, which I don't want. And I use stable, because my family is listening music often.
So switching to beta sometimes is okayish - but not having it permanently (which required for development)
Something that's confusing me is that it appears we're getting breaking changes introduced to the API by 2.10 - I'd usually expect an API for a server to maintain backwards compatibility between minor versions, but that doesn't seem to be the case. I see the API also has a schema version that versions separately from the server version, but 2.10's minimum supported version is the same as 2.9's (28).
I use HAOS so can swap between them very easily, but like you, I don't want to be using beta day to day.
I can swap easily (I even got switch in HA for this, it spins down main stack and spins up beta) - but that means interruption for everyone.
2.9 is 28, 2.9.9 is 31. 😉
Yes, schema is changing rapidly.
But the minimum support version hasn't changed (min_supported_schema_version). Maybe that doesn't mean what I think it means?
Nah, for us makes sense MAX supported schema version...
Our app usually is older than server.
What is min_supported_schema_version for then? My guess would have been that the server would support clients using that schema version (so it's backwards compatible to that).
I guess the question is: what version (for the server) changing indicates that a breaking change could occur in the API? Should we expect that on:
server_versionschema_versionmin_supported_schema_version
The current experience seems to be that it's schema_version, but I'd like to confirm that's intended and not just mistakes slipping through.
Well MAX version belongs to the app code, it's not server value 🙂
we bump the schema version if we make breaking changes to non-important api commands or ADD new api commands
we bump the min_supported_schema_version if there is a backwards incompatible change to major api functions. Ofcourse we try to keep that at a minimal - last time was with the introduction of mandatory auth
I'm merging related PR
So what we do on frontend is we have a nightly, beta and stable channel there as well
I'm proposing the app should do the same.
stable version (on app store) can only work with MA stable
beta version only compatible with beta version of MA
otherwise you need a lot of "if schema version blah" hacks in your code
2.10 has a lot of changes (more than usual) but also a lot of that came more or less from discussion with you guys 😉
- serverside translations
- optimized search
- optimized recomendations
- new audio pipelines
Ah, so we want to check OUR schema against server min, instead of current server schema against our max supported.
But we're still getting troubles pretty often with beta
More often than it would be comfortable
Yes you should raise an error if your support schema version is older than the server minimal
We're not in that mature state to provide 3 tracks 🙂
Then just do 2
Yes.
Or accept some schema version checks in your code if you want one app version to be compatible with multiple schema versions but imo that can get ugly fast
We're not yet in full release. We basically have one
We just have beta now. But it's app beta, not correspondence to server beta
Why not only accept compatibility with server beta then (and best effort on stable) while the app is not yet released ?
Since we're moving fast with changes, would be nice to have something that we can release quicker than once per 2 weeks... GPlay is awful.
Because we can't move that fast.
The betas of MA server are rather stable (as they are first tested on nightly)
And my guess is that people willing to use a beta of the app are also the ones that run MA server beta
yeah but that is because in this release we are making some foundational changes that were long due
Since now there's no stable, I think you're not quire right 🙂
It will always be like this 😉
All my dev experience talks for it.
so that is why you need 2 release chnanels
Well we do need stable - but we need Play Store listing for this, and I'm asking you guys to check Privacy Policy for a month 😉
And iOS TestFlight is still on Teancom's private account.
Yes, I will help with the privacy policy - we need one for every app / user facing component published.
Needs to be published on our public website. I have a template ready and I'll merge your concept in.
Yes, we are ready to set that one up. @brave sentinel is aware.
Ok so just to recap here: we shouldn't expect breaking changes without min_supported_schema_version also incrementing (which is what I'd hoped from the property name). If we do get breaking changes without it incrementing, we should raise an issue with the server rather than trying to work around it in the client.
Not really
we did make some breaking changes to non critical commands
without bumping "min_supported_schema_version"
but ofc that is kept to a minimal - we always try to keep it backwards compatibel since we also have to deal with HA
so it will more be a behavioral change then
search is a good example - it accepts some additional filter parameters now but the old command still works
the only one that is breaking is recommendations
server side translations are also backwards compatible
So the python client (used by HA) has logic where it handles a command per schema version
So does schema_version always increment whenever a breaking change is made or just whenever anything changes?
anything
again, breaking changes are very rare
the schema is bumped when a new feature is added or an existing one modified
so a client can check against that schema version to know if a feature is supported
I merged some changes and adding more. The behaviour will be: if app schema is < minimal - terminal doalog is shown, app is closed. Otherwise if app schema < server schema - warning dialog shown, app works.
@abstract trench right that works nicely for the current scenario. I do think for API consumers, it'd be best to always increment min_support_schema_version for any breaking change (even if the team feels it's "non-critical") though @fast grove. That way, clients can be confident that things will work as long as they speak min_support_schema_version and only need to care about schema_version for if the server supports a newer feature or not.
Later we can move user to the app distribution channel (GPlay/GHub) together with closing the app.
uhh, if it's breaking
only
That's what I'm saying. "for any breaking change".
Just wanted to double-stress
That is not going to happen sorry - then we will break HA compatibility even with a non relevant change
Like I said, we don't do breaking changes and the only one we allow without that minimal schema change are non critical ones or small behavioral changes. Recommendations is the best example. We HAD to change it as it was killing performance but nobody will die if they aren't there.
Models / api responses are always backwards compatible to avoid breaking clients btw - they contain logic for that
I can still boot a 3 months old HA instance and happily connect it to a nightly MA server
The one time where everything was breaking was with the auth support
So maybe instead of talking in theory we can zoom in on what you experience in the app ?
Because as far as I'm aware an app created for stable api schema schould connect and work just fine with a MA beta/nightly - it will just miss out on functionality
So we've had a couple of problems. https://github.com/music-assistant/mobile-app/issues/765 for example. I'm going to have to run in a sec, but I think @abstract trench has more. It actually sounds like from what you're saying that we can usually rely on min_supported_schema_version to be the flag for breaking changes and so I think the intention is where I'd personally like it.
I think the main problem is in fields nullability
Case is that our deserialization is strict - it's Json, so if there's no field that isn't marked as nullable with default value null - the request will fail.
So we need to scrap through the server DTO, and check what is nullable.
hmmm that is a big difference then between python and javascript
most of our fields are nullable/optional
Our schema is still pretty casual actually.
Do you use the models repo ? Because that is where all the models live
Or the api-docs ofc
I can't use models repo - it's incompatible with Kotlin 😉
haha yeah I know - I mean as reference
We use api mostly
Yes
We just make some minimal objects, omitting fields we don't need.
Ok well, when we do a change to a model its always additions. And if there is a field deprecated, it will still be sent for backwards compat
Basically saving on deserialization and memory, and code 🙂
Just recently, with "lean" metadata, there happened to be field that was never null before - and it started to be null. Although I guess it was nullable in schema before...
(probably)
This leads to app crash
So this considered breaking change.
probably it has always been nullable then
big change in server is that a large listing now does not send the optional fields anymore to slim down the listing.
we were sending multi megabyte messages to get one page of tracks for example
Yeah, I reckon
Maybe good if we write a good parser that can do a sanity check of the models repo to kotlin and flag issues
Still question stands - as a dev, I probably HAVE to keep beta server running.
If we align on nullable fields, that will be enough I guess.
If field is nullable in model, we should treat it and handle null by design. There will be no troubles.
Until there's field that was non-null and became null 🙂
that won't happen because then also our (de)serializer will crash
And if there's nullable field that becomes non-null on server, we will be getting errors in calls. No crashes, just errors.
But It's not the case as well I guess.
correct
So I just need to sweep on schema to make sure it corresponds to nullability in models.
how do you handle nullable fields that are omited from the response ?
because that is something we do
Other things should be fine.
If field is nullable and not in Json - it will be null.
That's fine.
OK all good then
Then its a case of going over the models defined in Kotlin and correct the cases where now is not nullable
For example, all metadata fields are optional so they are nullable
Just catching up and this confused me as it sounds like you're describing min_supported_schema_version as exactly what I'm asking for elsewhere (the server defining the oldest schema it is backwards compatible with). I realize I didn't define what I meant by "breaking changes" though: I'd meant "is not backwards compatible" rather than "happens to break a client". The difference is subtle, but an example like us having an optional parameter as non-null breaking things is the latter, not the former: the actual API contract hasn't been broken.
Anyway, looks like @abstract trench has now made sure the app doesn't launch when we end up with a min_supported_schema_version we don't expect, so that should solve any future big changes from breaking the app, and hopefully all of the issues we ran into were just nullable/non-null mismatches rather than actual changes in the API contract (I see @abstract trench's also reviewed and updated those).
Yes, the only exception to this rule is the changes on recommendations - on the 2.10 path you simply do not get the items anymore when you fetch them (but api still returns items as an empty array) - you actually need to fetch the items for each recommendation row. It's not a breaking change in terms of API/schema but a behavior change we had to make due to the performance issues caused by fetching all these recommendations in one single call.
Yeah, not breaking, but ui-breaking. 🙂
We will need to support this.
Guess there will be "if" on schema.
Are fetch methods for recommendations available on 2.9 (schema 31)? Or is it new thing?
no this is added in 2.10
the old command still returns all recommendations that are available (but with items set to an empty array)
you have a dedicated command to fetch the recommendation row items you are actually interested in (or what the user has enabled)
frontend uses userpreferences to store enabled recomemndation rows, would make sense if mobile app uses the same ?
So there should be "if schema > 31".
schema 41
We do have that (for filtering). Of course, so far it's still fetching everything, but showing only chosen.
I'm curious how we display those "hidden" rows now in the filter view... So far they have content anyways, so in edit mode they show the content. Guess we'll have to fetch them once user goes to edit mode.
frontend shows some placeholders now for disabled rows
2.9.9 is 31, right?
yes
Yeah or that. We don't have placeholders yet.
So "if schema >= 41 then
recommendations change was done in api schema 39
then something else in 40
something else in 41
40 = for the config flow changes (not relevant to the mobile app)
39 then...
How do we track these? Apart from asking?
41 is for the ai radio plugin
Follow server PR's ?
Or setup some sort of automated ping when the schema is bumped
an automated ping would be the best I guess
if a PR has a schema bump, there is a notification (or we simply auto append a label)
Let's setup some github actions to keep track of this
I would have to be on payroll for this :)))
@mellow widget I think you may lay the foundation of loading home page on schema 39+ with your current PR. Probably worth making some abstract multi-row loader?
Ah interesting! Yeah I can keep that in mind. Maybe we'll want some kind of generic state object that can be used easily with 'CategoryRow`. I'll think about how loading and empty states etc can all be more general purpose as well as I'm working on it.
Yeah also Marcel mentioned placeholders - would be cool for those rows that don't have data.
Yes I definitely think we need that for the artist page so was going to build it out a skeleton/placeholder loading mode for CategoryRow.
You've probably noticed me indulging in a bit of microframework building, so you'll this is music to my ears 😂
@fast grove recently there were changes to the WebRTC, that also brought breaking changes to the mobile client.
Even if we will have enough enthusiasm to read every server PR in order to check if that brings something breaking to the client - corresponding changes to the mobile should be developed, merged and tested. Meanwhile bold part of our users (using beta) will have bad experience.
Not to brag, just to support the point that server and clients should move together - or we just accept that users using beta will experience troubles pretty often.
As far as I'm aware @mortal wadi had communicated those changes to you guys.
And yes, we will keep hitting this until we change our workflow.
Yep I talked to @brave sentinel about this. But, looking at my communication, I could have been a bit clearer about this. So that's my bad, sorry. Do you guys need a hand pointing you in the right direction?
We need to synchronize breaking changes release. 🙂
Or admit that we don't support beta server.
I mean, depends on the definition of 'support'. Generally we should always be compatible with stable and try to be compatible with beta. But the whole point of running beta is to run into these issue now rather than after a stable release.
Well, the reality is that lots of users use beta as their day-to-day instance. And frustration is all on mobile side 🙂
I'm using my nightly and beta instances a lot less now (thus testing less), because I want to be able to use my mobile app 🙂
Users issue.
@abstract trench Do you need me to submit a draft PR to the mobile repo based on my frontend changes?
Teancom fixed it yesterday.
Which results in constant tickets in mobile repo. 🙂
Make a bot, which checks if beta is selected then auto-close it
That's what I'm talking about: are we intended to discard any issue with beta server, or do we synchronize changes... Looks like it's former.
We should synchronize changes better, 100%. Like I said above, I should have let you guys know when this hit nightly. But still, if you guys are on holiday or what not and we cut a beta, then there is still a risk of mobile breaking in beta..
On the upside. Web RTC has been ROCK solid since we moved to the new lib and chunked responses, so hope you have the same experience on mobile
I understand both sides but where do you see a problem with issues at a GitHub repo?
My proposal would be:
- mobile app should not be supported by nightly (all issues are closed)
- Mobile App should work with RC candidates beta may break things, and those issues should be auto-commented that this is a beta version and should get closed or marked as stale as long as release candidate is not available yet
- The mobile app should reject to work with nightly and should warn the user that he’s using a beta version
- the user should be more chilled when using a OPEN SOURCE MOBILE APP without a big corporate company behind!!!!
Well, for good sync we would have to make some meetings, walkthroughs etc. Also this cadence means - mobile should have always-available maintainers. Second job basically 🙂
We close those issues, as we don't support beta currently.
We already added (yesterday) warning if server schema version is newer than app has.
But all things "should" just don't work with open source:)
I think there's another way to look at this. Beta becomes stable eventually, so closing issues on beta is not the solution in my opinion, because, something is not working well. We also have plenty of issues reported on the beta channel, which help us solve issues together with our beta testers before we release anything to stable.
That said, once we reach v1.0, it might be an idea to split the mobile app in stable / nonstable and do the same thing we do with MA: At release time, promote nonstable > stable and do a release
scroll a bit above, that is exactly the discussion we had 😉
Ah I missed that.
Well yeah, but there's difference. You have tickets induced by server changes (basically bugs). Mobile app, on the other hand, has tickets induced by server changes in addition to our own bugs. This creates strain. IDK about @mellow widget or @brave sentinel , but for me it adds stress and feeling of "have to". 🙂
what we need is a bot that flags when the schema is changed and reports what was changed - and regular schema changes may only be additionals or behavioral changes. if breaking: bump the minimal schema version
I'm going to repeat what I said earlier - there will never be breaking changes without that "minimal supported schema" version being bumped
We also have to account for API stability with HA so it's not only mobile app
In my latest example it wasn't schema change - it was breaking change with WebRTC.
yes but that was;
- unavoidable (and also communicated by Marvin)
- not related to api schema
Yeah, I'm talking here about changes like this - not just schema, but any breaking change. We need to have better way to support these.
I work with big team (on my daily job), which has synced BE and FE releases. Basically, if server has it - then app has it too.
I realize that it's too much changes to keep another product in the loop. Mobile app releases could pull server back, which is more than inconvenience - because vast majority of users is using server through web, so it's automatically available to them with release.
Just want to come to common denominator here.
I think somebody implementing something in the backend that hits core/models (so it impacts the api) should also open a draft PR on all affected projects - we already do this for frontend but we often forget to update the python client and mobile app is completely forgotten.
Or at least create a message here with the changes.
we could have the AI Code Review process try to make a pass and flag items that it thinks would have an impact on the mobile client?
Just to clarify, that hypothetical change should result in a minimum_supported_schema_version right? Or, are you just meaning for any change to the API at all (just an addition for example)?
just an addition will only bump the schema (because it stays backwards compatible)
If we ever need to a breaking change we bump minimum_supported_schema_version
so far that has only happened a few times and the last time was the auth support
I'm not expecting that we need to do it again (but never say never)
Right, so I'm not sure client teams need a notification any time there's any change as it shouldn't be a problem.
I'm not familiar with the WebRTC changes, but maybe a good improvement to communicating changes would be for there to be a published changelog (ideally actually before they're out in the wild) of changes to just the API (breaking protocol changes or minimum_supported_schema_version increments) that client developers can follow? MA is a single server/multi client ecosystem as far as I can see, so it'll be a lot of work (and I'd imagine more and more as time goes on) for the server team to individually keep every client updated on coming changes.
Yeah, that is where we have the changelog for but it is also a lot of work for you guys to treverse it and hunt for things you also like to implement in the app
So that is where the idea of a bot comes in:
as soon as the api schema is bumped and/or a command was added/changed, a notification here or somewhere else
I think that'd work well
But I'd also be happy with a changelog just for the API that can be easily followed. That might be more work I guess!
Example: today I added a "queue_ended" field on the PlayerQueue object.
it is a signal towards frontend to show a pretty message that the whole queue was consumed (and auto play is not enabled) instead of doing guesswork based on the index.
Not an important change but might be interesting to know about
Could the bot post to a dedicated channel, so other clients could follow it as well? I think posting here makes it less visible for others building different clients.
I mean yeah, I'm already like "oh cool I can write a wee issue about using that to polish up the queue finishing experience"
Yes, I think it would be wise if we add a channel/thread here in the devs-support area about any change.
I think that's a really nice next step - if problems then come up down the line due to some change in the API not being dealt with, we can discuss again.
And @abstract trench, I don't think we should discard issues about the beta as they'll (usually) be things we do want to deal with. I don't think we should think about them as "stop everything and fix this" production issues though. We support stable and one previous version, we'd only need to fix issues with the beta in preparation for it being the next stable.
Ideally if this idea of the changelog/bot does its job, we'd hopefully be closing these issues as duplicates to things we'd already have written ourselves though I guess!
Yeah that's what I want ideally.
Just a note that yes, Marvin had pinged me directly that some WebRTC changes were coming soon but, because he's human, didn't follow up with a "BTW, that's happening now, here's the PRs with the actual change". And (as format has mentioned) I don't read all the server changes, so when I updated my dev server a week later I'd completely forgotten about that and was confused. 😅
Thanks for the discussion folks, this is how we learn and improve!
Just a heads-up that Android Studio Quail 3 upgrades the included JVM to 25 which will break development for us 😒
We'll need to at least upgrade detekt which looks like it only looks like it supports 25 in its new 2.0.0 alpha. For now, you'll need to install and configure a 21 JDK separately.
Hmm I'm on latest (?) Quail 3 (upgraded yesterday) 2026.1.3:
Build #AI-261.26222.65.2613.15948027, built on July 27, 2026
Runtime version: 25.0.2+-15348964-b329.117 aarch64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Nothing bad happened...
True, detekt fails
I was just doing a quick thing in between other work, so didn't have a chance to look updating detekt. For now, I've just got my JAVA_HOME pointing at OpenJDK 21 and then have Android Studio set to use JAVA_HOME.
But we'll need to either upgrade detekt, or just update our dev env docs.
@fast grove Let's add a label mobile-app-impact (maybe even a second mobile-app-breaking) to the server repo that we either set manually (or via the checklist) on every PR. That way the mobile folks can easily filter (and even get notifiied) about PRs that have direct impact. As a bonus, let's add in to our CLAUDE.md file that Claude will automatically scan for impact and set this checkbox.
meh, this is not only about mobile app. its about ANY client.
I am more a fan of posting api changes in a dedicated channel here automatically
Then let's change the label to client-impact and let the musicassistant bot post in a channel after merge. I think it's nice to let Claude automatically scan for client impact when it creates a PR so the chance is smaller we forget something
Is this limitation where volume control is disabled still the case? Is it possible to allow the volume control if “Hide this player in the user interface” is unchecked? Or perhaps another check that is on by default called “Disable volume control in the user interface”?
I use iPads as touch screen controls for my various sound systems around the house. I would like to be able to use the iPads as the audio source as well, but cannot currently as I am unable to adjust the volume without walking over to each device and manually adjusting.
Nope, we don't plan this.
Is it a matter of development effort or explicitly considered out-of-scope? I’d be happy to contribute the effort if that’s the hold up
The latter, deliberate choice and out of scope.
Bummer. It is also the only solution I have found for connecting Bluetooth speakers that require a passcode to Music Assistant. I use an old Android phone sitting on a shelf, connected to my hot tub speakers (that require a passcode to connect)
For support I suggest usting the #mobile-app channel, current channel is for mobile app devs support 🙂 You can discuss your options in there 🙂
Didn’t realize there was another channel! Thanks for the redirect
For Android there's SendspinDroid that uses Sendspin client more complete. Check it.
Yeah, that’s how I currently do it. Was just hoping to support the official app
Sorry. We positioned the app as management tool with personal experience, not as network player. Decided it from start.