Sync playback progress locally between Apple Watch and phone on pause#4535
Sync playback progress locally between Apple Watch and phone on pause#4535dericleeyy wants to merge 7 commits into
Conversation
PCIOS-196: playback progress made via the Apple Watch (Watch play source) did not appear on the phone — and the reverse — without manually tapping "Refresh now" on both devices. On pause, each device now pushes its playback position directly to the other over WatchConnectivity and applies it to the local player/DB (last-write-wins on playedUpToModified, skipping the actively-playing device), so Now Playing/progress updates without a manual refresh. The existing server sync still propagates to other devices. Gated behind the new watchPlaybackProgressLocalSync feature flag (default on). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Version |
There was a problem hiding this comment.
Pull request overview
This PR adds a WatchConnectivity “fast-path” to sync paused playback progress locally between Apple Watch and iPhone (in both directions), so Now Playing/progress updates without requiring a manual “Refresh now” or a server round-trip.
Changes:
- Introduces a
watchPlaybackProgressLocalSyncfeature flag (default on) to gate the behavior. - Adds a new WatchConnectivity message (
PlaybackProgressUpdate) plus a new now-playing field (playedUpToModified) to support last-write-wins conflict resolution. - Implements watch→phone progress push on pause, and phone→watch progress application when the phone’s now-playing state updates.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| podcasts/WatchConstants.swift | Adds message keys for playback progress updates and a now-playing playedUpToModified field key. |
| podcasts/Watch Communication/WatchManager.swift | Receives/apply watch→phone progress updates and includes playedUpToModified in the phone→watch now-playing payload. |
| Pocket Casts Watch App/UI/WatchSyncManager.swift | Sends watch→phone progress update on pause when the feature flag is enabled. |
| Pocket Casts Watch App/Data & Communication/WatchDataManager.swift | Adds accessor for now-playing playedUpToModified from the phone-sent state. |
| Pocket Casts Watch App/Data & Communication/SessionManager+Send.swift | Adds sendPlaybackProgress(...) message with guaranteed-delivery fallback. |
| Pocket Casts Watch App/Data & Communication/SessionManager.swift | Applies phone→watch paused progress to the local watch player using playedUpToModified last-write-wins. |
| Modules/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift | Adds the watchPlaybackProgressLocalSync feature flag with default enabled. |
| CHANGELOG.md | Adds release note entry for instant watch/phone progress sync on pause. |
SergioEstevao
left a comment
There was a problem hiding this comment.
I tested this today by installing the branch on my Watch and Phone.
[x] Update from Watch to Phone: where working correctly, when moving the position of the now play episode on the watch source and pausing after a couple of seconds the phone updated.
[x] Update from Phone to Watch: this didn't work for me. I move around the phone play position, paused. The Watch only updated when I force a refresh on the home screen on the watch.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Was this on a physical device? Updating from phone to watch on the SIM seemed to work for me. Unfortunately, I dont have a physical Apple Watch to test on. |
applyPhoneNowPlayingProgress() previously bailed out of every precondition with a silent return, so when the phone's paused position failed to apply on the watch there was no way to tell which guard rejected it. Split the compound guards and log the specific reason (with the relevant values) on each reject path, while keeping the behaviour identical. The feature-off case stays silent as an intentional no-op.
|
Quick context on what's changed since your review, @SergioEstevao: the commit after your review ("Fix last-write-wins behavior") addressed a Copilot comment on the watch→phone path (preserving the payload's I traced the phone→watch path and the phone side looks correct: on pause it bumps I've just pushed a commit that splits those guards and logs the specific reason (with values) on each reject path. Could you re-run the phone→watch test with this build and grab the watch logs? We're looking for a One thing I want to confirm on that note: the whole apply path is gated behind the |
Fixes PCIOS-196
Playback progress made on the Apple Watch (Watch play source) didn't appear on the iPhone — and changes on the phone didn't appear on the watch — without manually tapping "Refresh now" on both devices, because the two apps only reconciled through a server sync/refresh.
Currently the Watch source only syncs to the cloud server, with a 2 minute throttle limit update. When the phone receives the update, it does not reflect it immediately and require an app restart. The idea now is for the watch to also send the update to the phone locally, without depending on cloud. The cloud sync is untouched.
On pause, each device now pushes its playback position directly to the other over WatchConnectivity and applies it to the local player/DB, so Now Playing and episode progress update without a manual refresh. Conflicts use last-write-wins on
playedUpToModified, the actively-playing device is never overridden, and the existing server sync still propagates to other devices (web, iPad). Gated behind a newwatchPlaybackProgressLocalSyncfeature flag (default on).Changes
watchPlaybackProgressLocalSyncfeature flag (FeatureFlag.swift).PlaybackProgressUpdateWatchConnectivity message and anowPlayingPlayedUpToModifiednow-playing key (WatchConstants.swift).WatchSyncManager.playbackPaused→SessionManager.sendPlaybackProgress); the phone applies it and moves the live mini-player viaseekToFromSync(WatchManager.handlePlaybackProgressUpdate).playedUpToModifiedin its now-playing state, and the watch applies the phone's paused position to its local player inSessionManager.handleStateUpdate(newWatchDataManageraccessor).To test
Use a paired iPhone + Apple Watch (or simulators) signed into the same Pocket Casts Plus account, with the
watchPlaybackProgressLocalSyncflag enabled.Watch → phone:
Phone → watch:
Flag off:
Phone to Watch local sync
Screen.Recording.2026-06-17.at.12.07.06.AM.mov
Watch to Phone local sync
Screen.Recording.2026-06-16.at.11.31.32.PM.mov
Checklist
CHANGELOG.mdif necessary.