Conversation
- Point flutter_carplay at our fork (upstream v1.6.3 plus Now Playing buttons, sfsymbol, and a shuffle state shim) - Drop sectionIndexEnabled from the Home template, the only 1.2.11 to 1.6.3 API break (param moved to CPListSection) - Hide favourite and mix while offline or without a current track Closes finamp-app#1588
- Fetch through the same home screen path as the main UI - Show a collage of covers from each queue's next albums, falling back to the current track's artwork then a placeholder so tap indices stay aligned - Hide the row when there is no queue history
Replace the Recently Added list rows with a single art row of album covers, matching the Recent Queues presentation. The fetch limit rises from 3 to 6 albums to fill the wider row, clamped to the runtime image limit.
Finamp would lose last-playing status to other applications such as Apple Podcasts even when it was the last playing application. Set that it was last playing in more spots and handle play requests earlier in app lifecycle.
- Render icon font glyphs to cached PNGs so CarPlay buttons can show the same Tabler icons as the main UI - Use the main UI's radio, heart, and playback order glyphs for the mix, favourite, and shuffle buttons, with SF Symbols fallbacks - Replace the native shuffle button with an image button so the icon matches the main UI's linear and shuffled states
Show Recently Added, then Recently Played, then Recent Queues below the quick actions, and reuse the main UI home's Shuffle Tracks and Surprise Me translation strings for the quick actions.
- audio_service now sets MPNowPlayingInfoCenter.playbackState on iOS, merged upstream via ryanheise/audio_service#1140 (0.18.20) - Rebase the audio_service fork onto the latest upstream, dropping our copy of that fix and keeping the command registration and iosKeepNowPlayingOnStop commits - Remove the playback_state method channel and its Dart caller Closes finamp-app#1590
Albums and queues without artwork showed a bare SF symbol, which CarPlay renders as a tiny glyph in the corner of the tile. Render the main UI's album glyph placeholder onto a card coloured tile instead.
Lists push 30 items straight away then fill in the background up to the head unit's cap, replacing one blocking 250-item fetch. Oversized views get a letter picker, except online Tracks, see _enableOnlineTracksLetterPicker. Needs flutter_carplay cf997048.
|
This looks pretty sweet, thanks @APIUM! We'll save this PR for the 1.1 update of Finamp, so that we can get any critical changes in before end of August and the Jellyfin 12 release, I hope that's fine with you! |
Yep no worries, definitely need to test in car. It’s super annoying not being able to do it even with a dev account! Maybe with the changes @UnicornsOnLSD did for CI builds we can extend to dev builds. |
|
Making a build now, I foolishly upgraded my Flutter and now it's freaking out about our deployment target being below iOS 15 (the new minimum :( ), currently resurrecting |
e9fbad1 to
9a84374
Compare
I accidentally left the letter picker part out :/ Everything else is working well. Would you be able to do another build please? |
9a84374 to
7cfce1f
Compare
|
With the latest build this is working well for me on hardware too, same as the sim - ready for post 1.0.0 review and merge. |
| // whichever app had it before. Requires the audio_service fork | ||
| // overrides in pubspec.yaml (the flag spans audio_service and | ||
| // audio_service_platform_interface). | ||
| iosKeepNowPlayingOnStop: true, |
There was a problem hiding this comment.
Is this actually needed/desirable? As far as I can tell, the stopService command this is intercepting only occurs when we actually call stop() against just_audio, and we only do that on explicit user request. I would imagine if the user has stopped and cleared the queue, they don't actually want us to be taking the now-playing status like this. Or is there something more complex going on?
There was a problem hiding this comment.
This was because I kept finding Finamp would lose the now playing/last playing status to other apps which are grabby such as Apple's Podcast app. It appears that audio_service calls stopService on transitions to idle, so then we lose the resume. This change did fix it for me, and I've not really noticed any issues in use. I might otherwise be able to just intercept remote stops and make them a pause or something?
| return sceneConfig | ||
| } | ||
|
|
||
| // UIApplicationSupportsMultipleScenes is required for CarPlay, but it |
There was a problem hiding this comment.
This doesn't seem to have been implemented before. Why do we need it now?
There was a problem hiding this comment.
I've looked at this again, still working in the sim without it so I've removed
| final track = _queueService.getCurrentTrack()?.baseItem; | ||
| if (track == null) return; | ||
| try { | ||
| _carPlayLogger.info("Mix button pressed, starting an instant mix from '${track.name}'"); |
There was a problem hiding this comment.
Instant mix and similar radio mode are not quite the same thing.
| break; | ||
| } | ||
| scanned++; | ||
| final track = await providerRef.read(itemByIdProvider(id).future); |
There was a problem hiding this comment.
If we're only using 20 tracks, it might be faster to just batch lookup them all instead of trying to do them one at a time.
| if (albumId == null || !seenAlbumIds.add(albumId)) { | ||
| continue; | ||
| } | ||
| final image = await _resolveCollageTileImage(track!); |
There was a problem hiding this comment.
I believe ui.Image has native resources, so we need to make sure we reliably dispose of them once we're done. You might also want to double check if any of the other image resolving objects also need disposal.
There was a problem hiding this comment.
Now done, thanks for the pickup - I did
ui.Picture
ui.Image
ImageInfo
TextPainter
| /// queue, falling back to the current track's own artwork, then to a | ||
| /// placeholder icon, so a missing track or missing artwork doesn't shift | ||
| /// indices out of alignment with the queue list. | ||
| Future<String> _getRecentQueueImage(FinampStorableQueueInfo info) async { |
There was a problem hiding this comment.
It might be worth moving all these image helper methods to a dedicated file 'carplay_image_helper.dart' or something to help keep the size of this file down.
| "play() received with no current item; awaiting saved-queue restore before starting playback", | ||
| ); | ||
| try { | ||
| await queueService.performInitialQueueLoad(); |
There was a problem hiding this comment.
I don't particularly like the idea of performInitialQueueLoad being called from other places, and I think the saved queue state should remain internal to the queue service. My recommendation would be creating an ensureQueueLoaded function in the queue service which can wait for the initial load via a completer and then load the latest queue itself if appropriate.
| /// current track or while offline. | ||
| /// | ||
| /// Overlapping calls are ignored. | ||
| Future<void> _updateNowPlayingButtons() async { |
There was a problem hiding this comment.
Have you put any thought into getting the now playing buttons code upstreamed so we don't need to maintain a fork?
There was a problem hiding this comment.
Yeah, they've been receptive in the past, I was planning to confirm we're happy with everything here, get it upstreamed, then come back and update to point back to them
| @@ -18,11 +17,6 @@ let flutterEngine = FlutterEngine(name: "SharedEngine", project: nil, allowHeadl | |||
| flutterEngine.run() | |||
| GeneratedPluginRegistrant.register(with: flutterEngine) | |||
There was a problem hiding this comment.
Do you know anything about the UIScene migration? The carplay code seems to have left us in a strange, partially migrated state, and I'd like to try to bring us closer to the flutter guidelines.
There was a problem hiding this comment.
I'm fairly weak on it. I've had a look now, and I see I did make it a bit worse last time. I can do a follow up commit later to combine the plists and get this cleaned up. Keen to not expand this MR too much more - I'll make a ticket for it.
| /// Performs the one-time startup queue restore, loading the last "latest" | ||
| /// queue into the player, paused, per [FinampSettings.autoloadLastQueueOnStartup]. | ||
| /// Every caller awaits the same [Future]. | ||
| Future<void> performInitialQueueLoad() { |
There was a problem hiding this comment.
I'd really prefer if we keep performInitialQueueLoad being exclusively called in main(), and just add a completer if other functions want be sure it has already ran.
The radio button replaced the queue at once, so the user never saw the tracks first. Now it shows the suggested tracks in a list. Start Radio replaces the queue with them. Back keeps the queue and the radio mode setting unchanged. If CarPlay refuses the push because its template stack is full, the radio starts at once with the same tracks.
Start Radio replaced the whole queue, so the playing track restarted. Now the radio tracks replace only the tracks after the current one. The old queue is saved to Recent Queues first. The queue source becomes the radio, and later refills use the track as the seed.
Make the queue service the only owner of the startup queue restore. Other callers wait for it with ensureQueueLoaded or initialQueueLoaded. Fetch the collage tracks in one request and read the cached collage before the covers are decoded. Release each decoded image, picture and text painter after use. Move the CarPlay image code to carplay_image_helper.dart.
f95e694 to
7b7dbcb
Compare
Changes
Updates to Carplay including:
<img width="800" height="480" alt="finamp-home-imagerow" src="https://github.com/user-attachments/assets/5d3594ba-49aa-4a3a

-bb4c-81ea7e5d122c" />
Todo before merging
I've forked audio_service and flutter_carplay and moved them into finamp org. I will try to get these changes upstreamed but they are OK where they are for now while that occurs until the next update.
Related Issues