Skip to content

fix(airplay): preserve routes and anchor device pickers - #474

Open
sozercan wants to merge 4 commits into
mainfrom
airplay
Open

fix(airplay): preserve routes and anchor device pickers#474
sozercan wants to merge 4 commits into
mainfrom
airplay

Conversation

@sozercan

@sozercan sozercan commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Description

AirPlay could disconnect when track navigation or queue recovery replaced the YouTube Music playback document. YouTube could also leave the requested track unstarted and advance its own radio queue, so Kaset showed the selected song while different media played. The eventual full-page fallback then lost the route.

Keep recoverable track changes in the existing document and retry a stalled AirPlay target once. Both playback windows also use WebKit's picker, anchored at the invoking button. Relates to #42.

Changes made

  • Use SPA navigation for different-track recovery and same-ID queue drift. Coalesce recovery while the target is pending, preserving its confirmation deadline. Missing or failed routing falls back immediately; unconfirmed media retains a 15-second full-page fallback.
  • Retry an AirPlay target once after it remains unstarted and unloaded for one second. Observe state events and delayed identity/readiness updates. Buffering, paused, loaded, and advertisement media do not qualify. Playback occurrence and navigation generation guards prevent stale retries and confirmations from affecting newer skips.
  • Anchor the picker through a native mouse event, accounting for flipped SwiftUI content views. Transfer the existing playback WebView between the main and mini-player windows, including minimize and restore.
  • Report connection state from the current media element and clear it on document replacement, teardown, or process loss. Update ADR-0010 with the measured failures and remaining limits.

Testing

  • KASET_SIGNING=adhoc Scripts/build-app.sh debug and Scripts/verify-release-app.sh .build/app/Kaset.app passed. The clean local bundle identifies commit 598e9bf0.
  • swift test --skip KasetUITests --no-parallel: all 3,344 Swift tests and 14 XCTest cases passed across the app and supporting targets.
  • swift test --skip KasetUITests --filter 'AirPlayNavigationScriptTests|AirPlayNavigationTests': 27 tests passed, including delayed identity/readiness, missing state callbacks, rapid skips, advertisements, cancellation, and the full-page timeout.
  • swiftlint --strict and swiftformat --lint . passed.
  • Earlier packaged checks with an Apple TV on macOS 27 covered both picker positions, manual Next, automatic queue recovery, and main/mini window transfers. A source-error prototype recovered the requested media in the same document, with WebKit reporting wireless playback and matching player-response and Media Session metadata through the next natural transition.

Remaining verification and limits

The final state-based retry still needs a packaged receiver check. The prototype's receiver audio and display were not independently confirmed, and the minimize/restore receiver handoff has unit coverage only.

A stalled router can take 12 seconds longer to fall back than the previous three-second deadline. Full document or process recovery and receiver disappearance can still require reopening the picker. Route retention does not guarantee gapless audio; other receivers and macOS versions need runtime verification.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI balanced review requested due to automatic review settings September 6, 2026 06:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T17:08:23.637339Z 598e9bf New commits
🔒 Security Review Completed 2026-09-06T06:58:30.737401Z 0073ebd PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Miniaturizing the auxiliary mini player leaves the playback WebView in a non-visible window, preventing reliable picker presentation from the still-visible main player.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity Sources/​Kaset/​Views/​MainWindow.swiftisMiniPlayerVisible remains true when the custom Minimize action calls NSWindow.miniaturize, so…
What changed in this PR

Improves AirPlay continuity, picker positioning, mini-player routing, and connection-state tracking for WebKit playback.

Changes:

  • Preserves playback documents through SPA navigation with a longer recovery window.
  • Anchors WebKit’s picker to main and mini-player controls.
  • Adds AirPlay lifecycle and regression coverage.
File Description
Tests/​KasetTests/​SingletonPlayerNavigationStateTests.swift Tests status clearing on valid commits.
Tests/​KasetTests/​PlaybackWebViewTeardownTests.swift Tests teardown and process-loss resets.
Tests/​KasetTests/​MusicPlaybackBridgeGenerationTests.swift Updates bridge payload expectations.
Tests/​KasetTests/​Helpers/​MusicPlaybackObserverTestContext.swift Supports observer setup customization.
Tests/​KasetTests/​AirPlayStatusTests.swift Tests connection-state observation.
Tests/​KasetTests/​AirPlayPickerTests.swift Tests picker coordinates and event order.
Tests/​KasetTests/​AirPlayNavigationTests.swift Tests SPA and full-page routing decisions.
Sources/​Kaset/​Views/​SingletonPlayerWebView+PlaybackControls.swift Anchors the WebKit picker.
Sources/​Kaset/​Views/​SingletonPlayerWebView+ObserverScript.swift Tracks current media AirPlay state.
Sources/​Kaset/​Views/​SingletonPlayerWebView+NavigationState.swift Clears status on document replacement.
Sources/​Kaset/​Views/​PlayerBar.swift Supplies the main-button anchor.
Sources/​Kaset/​Views/​MiniPlayerWebView+Coordinator.swift Simplifies AirPlay bridge handling.
Sources/​Kaset/​Views/​MiniPlayerWebView.swift Revises navigation recovery and lifecycle resets.
Sources/​Kaset/​Views/​MiniPlayerViews.swift Hosts playback and picker controls in the mini player.
Sources/​Kaset/​Views/​MiniPlayerGlassControls.swift Removes the ineffective AVKit picker.
Sources/​Kaset/​Views/​MainWindow.swift Transfers WebView ownership to auxiliary windows.
Sources/​Kaset/​Views/​AirPlayPickerAnchor.swift Adds native button-position tracking.
Sources/​Kaset/​Services/​Player/​PlayerService+PlaybackControls.swift Forwards anchored picker requests.
Sources/​Kaset/​Services/​Player/​PlayerService.swift Removes obsolete request state.
docs/​adr/​0010-airplay-fix.md Documents findings, design, and limitations.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/Kaset/Views/MainWindow.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0073ebd149

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Kaset/Views/MainWindow.swift Outdated
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings September 6, 2026 07:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

It changes WebKit navigation, native event anchoring, and cross-window ownership, while runtime validation covers only one receiver and macOS version.

Review tier: Balanced
Findings: None

Issues resolved since last review (1)
Severity Finding
Medium severity Sources/​Kaset/​Views/​MainWindow.swiftisMiniPlayerVisible remains true when the custom Minimize action calls NSWindow.miniaturize, so… View resolved comment

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings September 6, 2026 15:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings September 6, 2026 17:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@Write

Write commented Sep 6, 2026

Copy link
Copy Markdown

Works perfectly for me, including picker's location ! Thanks a lot.

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.

3 participants