Skip to content

Comments

fix: iOS audio playback, background handling, and clock sync fixes#102

Open
hayupadhyaya wants to merge 2 commits intomusic-assistant:mainfrom
hayupadhyaya:fix/ios-audio-and-playback-fixes
Open

fix: iOS audio playback, background handling, and clock sync fixes#102
hayupadhyaya wants to merge 2 commits intomusic-assistant:mainfrom
hayupadhyaya:fix/ios-audio-and-playback-fixes

Conversation

@hayupadhyaya
Copy link
Contributor

  • Fix no-audio bug (clock sync time-base mismatch)MessageDispatcher and AudioStreamManager each had independent TimeSource.Monotonic.markNow() epochs.
    ClockSynchronizer.serverLoopOriginLocal was calibrated using MessageDispatcher's time domain, but AudioStreamManager.getCurrentTimeMicros() used a different epoch, causing all chunk
    timestamps to appear perpetually early and no audio to play. Fixed by moving the shared startMark into ClockSynchronizer and having both classes delegate to
    clockSynchronizer.getCurrentTimeMicros().

  • Fix iOS background audio — Added AVAudioSession interruption and route-change NotificationCenter observers to NativeAudioController. Playback now auto-resumes after phone calls,
    Siri interruptions, and handles headphone/Bluetooth disconnects correctly.

  • Fix iOS volume controlgetCurrentSystemVolume() was returning a hardcoded value. Now reads the real system volume via AVAudioSession.sharedInstance().outputVolume.

  • Efficient Kotlin→Swift PCM data transfer — Added writeRawPcmNSData(NSData) to NativeAudioController and PlatformAudioPlayer. Kotlin side now uses usePinned/addressOf bulk copy
    to convert ByteArrayNSData in a single memcpy instead of a per-byte Swift interop loop.

  • DataChannelWrapper binary fast path — Replaced full decodeToString() on every incoming message with a first-byte check ({/[) to distinguish text vs binary frames. Audio chunks
    arrive at 50–100/sec so this avoids a significant amount of unnecessary UTF-8 decoding.

  • Fix OAuthHandler — Was throwing UnsupportedOperationException. Now opens the OAuth URL in Safari via UIApplication.openURL.

  • Fix SystemAppearance — Was a no-op stub. Now correctly applies overrideUserInterfaceStyle to all windows for dark/light mode support.

  • Fix compose-resources not bundled — Compose Multiplatform assets (images, fonts) were not being copied into the .app bundle, causing a MissingResourceException crash on launch. Added
    a shell script step to the Xcode build phase to copy from composeApp/build/kotlin-multiplatform-resources/aggregated-resources/ into the app bundle.

  • Fix Config.xcconfig — Renamed BUNDLE_ID to PRODUCT_BUNDLE_IDENTIFIER to match the variable name expected by the pbxproj.

  • Docs — Updated README with iOS features and building-from-source section. Updated .claude/ status docs to reflect resolved issues (pending end-to-end device tests).

Test plan

  • Build and run on iOS Simulator — verify app launches without MissingResourceException
  • Connect to a Music Assistant server and start Sendspin playback — verify audio plays
  • Trigger a phone call or Siri while audio is playing — verify playback resumes after
  • Unplug headphones during playback — verify app handles route change gracefully
  • Verify dark/light mode toggle applies correctly across all screens
  • Verify OAuth login opens Safari correctly

Hay Upadhyaya and others added 2 commits February 20, 2026 02:30
- Replace synchronized() with Mutex.withLock{} in AudioStreamManager.kt
  (synchronized is JVM-only; suspend functions use withLock, close() uses runBlocking)
- Wire updateNowPlaying() in MainDataSource to push track metadata, artwork,
  elapsed time and playback rate to the iOS Now Playing center on every
  local player state change (~500 ms cadence via position tracker loop)
- Embed WebRTC.framework into the app bundle in the Xcode build phase script
  and sign it with $EXPANDED_CODE_SIGN_IDENTITY so dyld can find it at
  runtime (without this the app crashes immediately on launch)
- Fix no-audio bug: MessageDispatcher and AudioStreamManager each had
  independent TimeSource.Monotonic.markNow() epochs; ClockSynchronizer
  now owns the shared startMark and exposes getCurrentTimeMicros() so
  serverLoopOriginLocal and currentLocalTime are always in the same domain
- Add AVAudioSession interruption + route-change handlers to
  NativeAudioController; auto-resumes playback after phone calls/Siri
- Add writeRawPcmNSData(NSData) for efficient Kotlin→Swift PCM transfer
  via usePinned bulk copy (avoids per-byte Swift interop loop)
- Fix DataChannelWrapper binary fast path: first-byte check instead of
  full decodeToString() on every audio chunk at 50-100/sec
- Fix OAuthHandler: open OAuth URLs in Safari instead of throwing
  UnsupportedOperationException
- Fix SystemAppearance: apply overrideUserInterfaceStyle to all windows
- Fix Config.xcconfig: rename BUNDLE_ID to PRODUCT_BUNDLE_IDENTIFIER
- Fix project.pbxproj: copy compose-resources into app bundle so
  Compose Multiplatform assets (images, fonts) resolve at runtime
- Update README: iOS features section + building from source
- Update .claude docs: mark iOS volume, background playback, and
  no-audio bug as resolved (pending end-to-end tests)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@hayupadhyaya hayupadhyaya changed the title Fix/ios audio and playback fixes fix(ios): audio playback, background handling, and clock sync fixes Feb 20, 2026
@hayupadhyaya hayupadhyaya changed the title fix(ios): audio playback, background handling, and clock sync fixes fix: iOS audio playback, background handling, and clock sync fixes Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant