Skip to content

Implement Seekbar sync for now playing#237

Merged
sameerasw merged 3 commits into
sameerasw:developfrom
Mudit200408:develop
May 21, 2026
Merged

Implement Seekbar sync for now playing#237
sameerasw merged 3 commits into
sameerasw:developfrom
Mudit200408:develop

Conversation

@Mudit200408
Copy link
Copy Markdown
Contributor

All Conflicts FIXED!!
Although do a test before for confirmation, its working fine for me

- Guarded behind button called 'Sync Android playback seekbar'
- Also added a tooltip
- Added the seekbar in Menu bar widget and also the phone display
- Requires changes in android app
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements initial Android playback seekbar synchronization by extending the device music status payload with duration/position/buffering state, adding a seekbar UI, and introducing a macOS Now Playing publisher (MPNowPlayingInfoCenter) so third-party UI (e.g., Control Center / boringNotch) can display Android media state.

Changes:

  • Added a new opt-in setting for “Sync Android playback seekbar” and wired it into the media UI.
  • Extended DeviceStatus.Music and WebSocket/BLE plumbing to carry duration/position/buffering, plus outgoing “seekTo” control.
  • Added NowPlayingPublisher to publish Android media into macOS Now Playing and forward system media commands back to Android.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
airsync-mac/Screens/Settings/SettingsFeaturesView.swift Adds the new seekbar-sync toggle and clears now-playing state when disabled.
airsync-mac/Screens/HomeScreen/PhoneView/MediaPlayerView.swift Adds an in-app seekbar UI bound to centralized AppState.mediaPosition.
airsync-mac/Model/DeviceStatus.swift Extends music model with duration/position/buffering fields.
airsync-mac/Model/Device.swift Updates mock music data to include the new fields.
airsync-mac/Core/WebSocket/WebSocketServer+Outgoing.swift Adds outgoing seekTo plus a helper for forwarding system media commands to Android actions.
airsync-mac/Core/WebSocket/WebSocketServer+Handlers.swift Parses duration/position/buffering; corrects position using timestamp; updates AppState and publishes to Now Playing when enabled.
airsync-mac/Core/Util/MacInfo/MacInfoSyncManager.swift Prevents feedback loops by ignoring AirSync’s own published Now Playing entry.
airsync-mac/Core/Storage/UserDefaults.swift Adds a persisted syncAndroidPlaybackSeekbar setting with documentation.
airsync-mac/Core/Media/NowPlayingPublisher.swift New publisher that drives MPNowPlayingInfoCenter + silent audio + MPRemoteCommandCenter forwarding.
airsync-mac/Core/BLE/BLETransportBridge.swift Populates new music fields with “not available” defaults for BLE.
airsync-mac/Core/AppState.swift Adds centralized seekbar state + timer-driven position progression + seek handling.
airsync-mac/airsync_macApp.swift Starts NowPlayingPublisher during app initialization.
Comments suppressed due to low confidence (2)

airsync-mac/Core/WebSocket/WebSocketServer+Handlers.swift:332

  • This base64 decode runs on the status-update hot path and (given WebSocketServer dispatch behavior) is likely on the main thread. Decoding artwork every update can be expensive and cause UI hitching. Consider caching the last albumArt string/hash and only decoding when it changes, and/or doing the decode off the main thread before publishing to MPNowPlayingInfoCenter.
                if let data = Data(base64Encoded: albumArt) {
                    npInfo.artworkData = data
                }

airsync-mac/Core/WebSocket/WebSocketServer+Handlers.swift:340

  • Duration/position are already parsed, corrected (positionTimestamp compensation), and clamped into durationSec/positionSec above, but MPNowPlayingInfoCenter is being fed fresh values from the raw JSON again. This likely makes the system seekbar lag/overshoot vs the UI slider. Reuse durationSec/positionSec when setting npInfo.duration/elapsedTime so the published now-playing state matches the corrected seekbar state.
                if let nsNum = music["duration"] as? NSNumber, nsNum.doubleValue > 0 {
                    npInfo.duration = nsNum.doubleValue / 1000.0
                }
                if let pMs = music["position"] as? NSNumber, pMs.doubleValue >= 0 {
                    npInfo.elapsedTime = pMs.doubleValue / 1000.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread airsync-mac/Screens/Settings/SettingsFeaturesView.swift
Comment on lines +29 to +31
// Initialize NowPlayingPublisher for MPNowPlayingInfoCenter integration
NowPlayingPublisher.shared.start()

Comment on lines +42 to +50
/// Call once at app startup. Sets up remote commands and starts silent audio.
func start() {
registerRemoteCommands()
// Start silent audio immediately so the app is ALWAYS audio-eligible.
// If we wait until the first play command, macOS sees us publish
// MPNowPlayingInfoCenter data without backing audio and fires a pauseCommand
// to "correct" the state — which is the root cause of the glitch loop.
startSilentAudio()
}
Comment on lines +52 to +70
/// Update now-playing with Android media info.
/// During the 1-second window after the user clicks a button, we ignore incoming
/// status updates. This protects our instant optimistic UI from being overwritten
/// by stale network packets that Android dispatched before the command took effect.
func update(info: NowPlayingInfo) {
let timeSinceCommand = Date().timeIntervalSince(lastCommandSentAt)
if timeSinceCommand < 1.0 {
return
}

currentInfo = info

// Always publish metadata on the main thread (MPNowPlayingInfoCenter requirement)
DispatchQueue.main.async {
self.lastStateUpdateAt = Date()
self.publishToNowPlayingInfoCenter(info: info)
}
// Silent audio is always running (started in start()), nothing to do here.
}
if UserDefaults.standard.syncAndroidPlaybackSeekbar {
var npInfo = NowPlayingInfo()
npInfo.title = title
npInfo.artist = artist
//

import SwiftUI
import Combine
@sameerasw
Copy link
Copy Markdown
Owner

@Mudit200408
I checked this PR out but no change or new feature was to be found on both mac and Android side. Can you double check?

CleanShot-Xcode-AirSync — BLECentralManager swift-20260521-11  54 43@2x

@Mudit200408
Copy link
Copy Markdown
Contributor Author

@Mudit200408 I checked this PR out but no change or new feature was to be found on both mac and Android side. Can you double check?

CleanShot-Xcode-AirSync — BLECentralManager swift-20260521-11  54 43@2x

Hey, Sorry about that i think i had accidentally missed out some refactors in the commit, can u check it out again the seekbar changes in both android as well as mac, i have force pushed some changes

@sameerasw
Copy link
Copy Markdown
Owner

Awesome! It works now!

There's one thing I'd like to have which is to separate the seekbar controls and the silent audio trick to advertise the playback for macOS. Currently it seems both are controls with one toggle.

But I don't want to delay this PR anymore so I'll separate it.

Thanks! <3

@sameerasw sameerasw changed the title [SQUASH] feat: Implement Seekbar sync for now playing [1/2] Implement Seekbar sync for now playing May 21, 2026
@sameerasw sameerasw merged commit f62fc10 into sameerasw:develop May 21, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to Done in AirSync Features and Bugs May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants