feat(pebble): Add full Pebble smartwatch integration (library browse + playback)#1597
feat(pebble): Add full Pebble smartwatch integration (library browse + playback)#1597tannermeade wants to merge 4 commits into
Conversation
|
Hi @tannermeade, thanks for the PR! Could you please record a video and add some screenshots of how the app looks and works on the pebble? Also, from a quick glance I saw you using a switch block that matches integers representing various commands. Could you please define those via an enum? |
HeroBrine1st
left a comment
There was a problem hiding this comment.
Pretty interesting. I also like that the changes are isolated.
Outside of structured concurrency violation and UX issue it should be okay I think
| channel.setMethodCallHandler(this) | ||
|
|
||
| // === Grant permission for Core Pebble app to talk to us === | ||
| CoroutineScope(Dispatchers.Main).launch { |
There was a problem hiding this comment.
Why not GlobalScope? This is essentially GlobalScope which is delicate API, because both undermine structured concurrency
runBlocking is a viable alternative, too, without such problems but yeah, blocking
| import android.content.Intent | ||
| import android.util.Log | ||
| import io.flutter.embedding.engine.plugins.FlutterPlugin | ||
| import io.flutter.plugin.common.MethodCall // ← This was missing |
There was a problem hiding this comment.
I'm not gatekeeping but I'd like those comments to be deleted because this is a simple agent communication with developer
|
|
||
| Log.d("🪨", "Pebble command received → cmd: $cmd, index: $index, text: $receivedText") | ||
|
|
||
| // Fixed: safe null check (no more backing-field error) |
There was a problem hiding this comment.
Especially this. This can actually be a worthy comment because e.g. to me it's not obvious why this can be null (note I'm reviewing without IDE) so this can explain why such late-binding happens at all
| // Give the background task a moment to update the queue before skipping | ||
| await Future.delayed(const Duration(milliseconds: 150)); |
There was a problem hiding this comment.
Looks like race condition in QueueService? It's possible (I even found one in the past), I'm probably gonna look there
There was a problem hiding this comment.
I have glanced into queue_service.dart file and found that it suspends until _audioHandler.insertFinampQueueItems is complete (it is awaited and everything in it is awaited too), so there should be no race condition requiring this delay. The same audioHandler is used below (skipToNext), so this should not be an issue and this delayed is then not needed.
Was there an actual issue? If so, it's most probably an upstream issue in just-audio
| final track = await jellyfinApiHelper.getItemById(BaseItemId(songId)); | ||
| if (track == null) return; | ||
|
|
||
| await queueService.addNext(items: [track]); |
There was a problem hiding this comment.
I see this function is called in response to CMD_PLAY_SONG, but in Finamp itself a similar action replaces the queue with either one track only (e.g. from tracks page if there's no mix) or with its queue (an album, playlist or a mix). Is this probably something Pebble UX related that it adds a track to current queue?
| if (state.playing) { | ||
| await audioHandler.skipToNext(); | ||
| } else { | ||
| await audioHandler.play(); | ||
| } |
There was a problem hiding this comment.
Oh, and looks like this logic is flawed. As far as I understand from just-audio reference, playing is true if player is actually playing. In this case, when player is paused skipToNext is not called and current track is played instead.
However, if I'm wrong and playing means something else (e.g. if it is true when paused too), the first branch indeed skips to just added track yet doesn't unpause.
I can't fully understand this even though I quickly skimmed into reference, so take this with a grain of salt especially considering that this response was completely different until I realized the just-audio method I referenced is different to one I read, so I rewrote this, although "if I'm wrong" part is based on the rewritten text.
|
@tannermeade are you still around? :) |
Ya, practically speaking I think some more changes need to be made to make this feature reliable and useful enough for me. Those changes will be a little more involved. If this PR needs to be closed for cleanliness of the repo feel free to close it and I'll reopen or make a new PR whenever I get around to making the changes that need to happen for the feature to work well enough that the phone doesn't need to be fiddled with ever. |
|
@tannermeade no worries, I'll just convert it to a draft PR, and you can make any changes you feel are necessary. Once you feel it's ready to be merged, just ping one of us. And let us know if you have any questions about the code or how best do achieve something specific! |
What does this PR do?
Adds complete support for the Pebble smartwatch to control Finamp from your wrist.
You can now:
The implementation is stateless on the Dart side and follows the exact patterns used in the redesign branch (
getItems,genreFilter,QueueItemSource.rawId,addNext, etc.).Files changed
New / modified:
lib/services/pebble_service.dart← main Dart integration (all logic)android/app/src/main/kotlin/com/unicornsonlsd/finamp/PebbleHandler.kt← messaging between pebble/finampandroid/app/src/main/kotlin/com/unicornsonlsd/finamp/PebbleListenerService.kt← intent/background communication between pebble/finampHow to test
pebble build && pebble install).Notes for reviewers
CMD_OPEN_SONGS,CMD_LOAD_MORE_SONGS,CMD_PLAY_ALL,CMD_PLAY_SONG) are clearly defined and stateless.Ready for review and merge into the
redesignbranch.