Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ShelfSync

A Garmin Connect IQ watch app for listening to your Audiobookshelf library, with playback progress kept in sync between the watch and your phone automatically.

How syncing works

There's no separate "companion phone app" to install - syncing between your watch and phone happens two ways, both driven by your Audiobookshelf server itself:

  1. Setup (watch ⇄ phone). You configure the server URL and API key from the Garmin Connect Mobile app on your phone (Settings for this watch app → see "Setup" below). That's the one place all configuration happens; the watch just receives it.
  2. Listening progress (watch ⇄ server ⇄ any other Audiobookshelf app). While you listen on the watch, it pushes your position back to the server. Whenever the watch's sync runs, it also pulls the latest position from the server. Since Audiobookshelf's official phone app and web player read/write that exact same position, listening on your phone and then picking up on the watch (or vice versa) resumes from the right place either way - the same mechanism the official apps already use to sync with each other.

Downloading books and audio playback itself go through Garmin's Music widget, using the same system framework Spotify/Amazon Music's Garmin apps use (an "audio content provider app") rather than a custom home screen - see "Using it on the watch" below.

Setup

  1. Generate an API key on your Audiobookshelf server: Settings → Users → API Keys → add one for your account, with no expiry. (Requires Audiobookshelf ~v2.26+. On an older server, use your account's non-expiring login token from POST /login instead - same field.)
  2. Install ShelfSync on your watch (see "Before you build" below - this repo is source, not a built app).
  3. Open the Garmin Connect Mobile app → Garmin Devices → your watch → installed apps → ShelfSync → App Settings, and enter:
    • Server URL - e.g. https://abs.example.com (must be reachable from your phone/watch - a local-network-only address won't work unless you're on that network or have a VPN)
    • API Key - from step 1

Using it on the watch

ShelfSync doesn't have its own icon in the watch's app list - like Spotify/Amazon Music on Garmin, it's reached through the Music glance/ widget:

  1. Open Music on the watch → select ShelfSync as the source.
  2. Choose manage content (wording varies by device/firmware) to browse your library and check off books to download. Unchecking a downloaded book removes it from the watch.
  3. Choose play to pick which downloaded book is active, which starts playback and hands you Garmin's normal music-player controls (play/ pause, next/previous track, skip forward/back).

Downloads and progress sync both happen through the same "Sync" action in the Music widget - the OS decides when to run it (typically triggered by opening the widget, charging, or connecting to WiFi, same as any other music-provider app).

What "chapter" navigation means here

Audiobookshelf items are one or more audio files. ShelfSync treats each file as one track, so Next/Previous on the watch moves between files. For a library split into per-chapter files (common for most audiobooks), that's real chapter navigation. For a book stored as a single .m4b file, there's only one track, and no finer-grained seek - Garmin's audio content provider API doesn't expose an absolute-seek call, so partway through a single-file book you can't jump to an arbitrary timestamp beyond what "skip forward/back" gives you. If your library has single-file audiobooks and you want per-chapter navigation on the watch, re-encode/split them by chapter server-side (a common Audiobookshelf library-management step).

Known limitations

  • No playback speed control. Garmin's audio-content-provider framework (Toybox.Media) doesn't expose a speed-control API as far as could be confirmed against the current SDK/samples - unlike Audiobookshelf's own phone/web app. If a later Connect IQ SDK adds this, it isn't wired up yet.
  • One audiobook library. If your server has more than one library with mediaType: book, only the first one found is browsable for now.
  • Unsupported audio formats are skipped. The Connect IQ Media API only documents MP3, M4A/M4B, WAV, and AAC/ADTS. FLAC/OGG tracks are silently dropped rather than downloaded (a whole book in an unsupported format never finishes syncing - it's simply skipped, not stuck retrying forever).
  • Absolute resume-to-timestamp isn't guaranteed within a track. See "What 'chapter' navigation means" above - the watch resumes at the right file, matched against the latest known progress, but not necessarily the exact second, because the OS (not this app) owns a track's internal playback position.

Before you build

This was written and reviewed without access to a Connect IQ SDK/simulator in the environment it was authored in - it has not been compiled or run on-device. Before you rely on it:

  1. Install the Connect IQ SDK and the VS Code Monkey C extension (or Eclipse-based tooling).
  2. Regenerate manifest.xml's <iq:products> list. The device ids in this repo are a placeholder set of watches that have historically supported music-provider apps (fenix 7 family, epix 2, venu 2/3, some Forerunners) - device support for this app type and the exact id strings both depend on your installed SDK version. Use the SDK's project properties / device picker to regenerate this list rather than trusting it as-is, or the build will fail on an unknown device id.
  3. Build and run in the simulator against a real (or locally-run) Audiobookshelf server, then sideload to a physical watch to verify downloads and playback - the simulator's music-provider support is limited for testing actual audio playback.
  4. Re-check the things flagged as unverified in the code comments (see AbsContentIterator.mc, AbsSyncDelegate.mc) against the current Toybox.Media API docs for your SDK version, since Garmin's docs site wasn't reachable while this was written and the implementation is based on reading Garmin's own official sample app (garmin/connectiq-apps/audio-provider/monkeymusic) source directly rather than the rendered API reference.

Sideloading to your watch

Once you've built it (see "Before you build" above), getting it onto a physical watch doesn't require the Connect IQ Store - IQ apps install by copying a compiled .prg file straight onto the device over USB.

There's no Bluetooth equivalent for a local build: Garmin Connect Mobile's Bluetooth install path only talks to the Connect IQ Store's servers, not to arbitrary files on your computer. The only way to get Bluetooth installs for a non-public build is to register a (free) Connect IQ developer account, upload the app to the Store dashboard as unpublished/in review, and add your own Garmin account as a tester - after that one-time upload, Connect Mobile installs and updates it over Bluetooth like any Store app. Worth it only if you're iterating without physical access to a cable; otherwise USB below is simpler.

  1. Get a developer key (one-time, any Connect IQ project needs this to produce a signed build):

    • In VS Code with the Monkey C extension: Command Palette → Monkey C: Generate a Developer Key, save it somewhere outside this repo (e.g. ~/.garmin/developer_key.der), and point the extension at it (monkeyC.developerKeyPath in VS Code settings, or it'll prompt you the first time you build).
    • Or via OpenSSL directly:
      openssl genrsa -out developer_key.pem 4096
      openssl pkcs8 -topk8 -inform PEM -outform DER -in developer_key.pem -out developer_key.der -nocrypt
      

    Keep this key - you'll reuse the same one for every future build of this app; a different key produces a different app id and Garmin Connect Mobile will treat it as a brand new, separately-installed app.

  2. Regenerate the device list in manifest.xml for your specific watch model first (see "Before you build" step 2) - building against an unknown/unsupported device id fails outright.

  3. Build for your device:

    • VS Code: Command Palette → Monkey C: Build for Device, pick your watch model. Output lands at bin/<app>.prg.
    • CLI: monkeyc -d <deviceId> -f monkey.jungle -o bin/ShelfSync.prg -y /path/to/developer_key.der (monkeyc is in the SDK's bin/ directory - add it to your PATH, or use the full path).
  4. Copy it to the watch:

    • Plug the watch in with a USB cable. It mounts as a normal drive named GARMIN.
    • Copy bin/ShelfSync.prg into GARMIN/GARMIN/APPS/ on that drive.
    • On Windows, if Garmin Express is installed and running, it will usually offer to install a .prg automatically when you double-click it with the watch connected - the manual copy above always works too and doesn't need Garmin Express at all.
    • Safely eject the drive, then unplug. The watch will index the new app - this can take a few seconds, occasionally a restart.
  5. Configure it: open Garmin Connect Mobile on your phone → your watch → Connect IQ apps (or "More" → the app list) → ShelfSync → App Settings → enter your Audiobookshelf server URL and API key (see "Setup" above). This works the same for a sideloaded app as a Store-installed one - Connect Mobile reads the settings straight off the installed app package on the watch over Bluetooth.

  6. Use it: on the watch, open the Music widget/glance and select ShelfSync as the source (see "Using it on the watch" above).

Debugging tip: System.println() output (used throughout this codebase for error logging) is only visible in the Connect IQ Simulator's console, not on a physical device - there's no on-device log viewer. Reproduce a problem in the simulator against a real or locally-run Audiobookshelf server first if something isn't working, since a sideloaded build on hardware is effectively a black box otherwise.

Project layout

manifest.xml                    App metadata, permissions, target devices
monkey.jungle                   Build config
resources/
  strings/strings.xml           All user-facing text
  settings/{properties,settings}.xml   Phone-configured settings (server URL, API key)
  drawables/                    Launcher/provider icon
source/
  ShelfSyncApp.mc                App entry point (AudioContentProviderApp)
  AbsContentDelegate.mc          Playback event hook -> progress push
  AbsContentIterator.mc          Per-book track playlist for the player
  AbsSyncDelegate.mc             Download/delete + progress sync on each OS-triggered sync
  ConfigureSyncView.mc / ConfigureSyncMenuDelegate.mc     "Manage content" (choose books to download)
  ConfigurePlaybackView.mc / ConfigurePlaybackMenu(.mc/Delegate.mc)   "Choose what to play"
  AbsApi.mc                      Audiobookshelf REST client
  ProgressSync.mc                Bidirectional progress push/pull logic
  HttpClient.mc / JsonResponseHandler.mc / AudioDownloadHandler.mc   Shared networking
  LocalStore.mc / AppConstants.mc   On-device state (object store)
  AppSettings.mc                 Phone-configured settings reader
  ItemMeta.mc                    Defensive Audiobookshelf JSON parsing
  ErrorView.mc / ErrorMessages.mc

Audiobookshelf API reference used

Verified by reading the live advplyr/audiobookshelf server source (v2.36.0) rather than the (now-unmaintained) hosted API docs:

  • Auth: POST /api/api-keys (admin) to mint a long-lived key; bearer token on every request.
  • GET /api/libraries, GET /api/libraries/:id/items, GET /api/items/:id?expanded=1 for browsing.
  • GET /api/me/progress for the current progress of every item (mediaProgress[]).
  • POST /api/session/local-all for reporting local progress - the same "offline-first" endpoint Audiobookshelf's own apps use to report playback sessions recorded while offline. The server only applies an entry if its own copy isn't already newer (PlaybackSessionManager.syncLocalSession compares updatedAt before writing), so a push can never clobber a fresher value written by another device in the meantime. Chosen over the simpler-looking PATCH /api/me/progress/* endpoints for two reasons: those are a blind overwrite with no reconciliation at all, and Connect IQ's Communications module doesn't document an HTTP PATCH method (GET/POST/PUT/DELETE only).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages