feat(player): add gapless queue handoff and SPA navigation#248
Open
tui2019 wants to merge 50 commits into
Open
feat(player): add gapless queue handoff and SPA navigation#248tui2019 wants to merge 50 commits into
tui2019 wants to merge 50 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds tighter WebView/PlayerService coordination to improve playback continuity: native YouTube Music queue injection for gapless transitions, router-based navigation to reduce full reloads, and startup autoplay suppression/restoration handling.
Changes:
- Inject the expected next track into YouTube Music’s native “Up Next” queue and advance locally when YTM auto-advances.
- Add startup autoplay blocking knobs and restoration flow that waits for a server-restored track before falling back.
- Prefer in-page router navigation and preload the YTM home shell once logged in.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/Kaset/Views/SingletonPlayerWebView+QueueInjection.swift | Adds JS-based “Play next” injection via DOM + JSON.stringify interception. |
| Sources/Kaset/Views/SingletonPlayerWebView+PlaybackControls.swift | Adds a JS toggle to block/unblock autoplay at runtime. |
| Sources/Kaset/Views/SingletonPlayerWebView+ObserverScript.swift | Honors the autoplay-block flag in playback observers. |
| Sources/Kaset/Views/MiniPlayerWebView.swift | Adds home preload, router navigation, updated autoplay bootstrap flags. |
| Sources/Kaset/Views/MiniPlayerViews.swift | Allows the persistent player view to exist without an immediate videoId. |
| Sources/Kaset/Views/MainWindow.swift | Keeps the persistent web view alive while logged in (even without pending video). |
| Sources/Kaset/Services/Player/PlayerService.swift | Tracks restoration wait state and last injected web-queue videoId. |
| Sources/Kaset/Services/Player/PlayerService+WebQueueSync.swift | Implements web-queue sync/injection and restoration metadata application. |
| Sources/Kaset/Services/Player/PlayerService+Queue.swift | Re-syncs web queue when persisting queue/session changes. |
| Sources/Kaset/Services/Player/PlayerService+PlaybackRestoration.swift | Defers restored playback until server-restored track is observed (or times out). |
| Sources/Kaset/Services/Player/PlayerService+PlaybackControls.swift | Syncs web queue after playback start; changes resume/next/previous autoplay behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1 task
Signed-off-by: Sertac Ozercan <sozercan@gmail.com> # Conflicts: # Sources/Kaset/Services/Player/PlayerService+PlaybackControls.swift # Sources/Kaset/Services/Player/PlayerService+Queue.swift # Sources/Kaset/Views/MiniPlayerWebView.swift # Sources/Kaset/Views/SingletonPlayerWebView+PlaybackControls.swift
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
sozercan
force-pushed
the
feature/implement-queues
branch
from
July 7, 2026 07:01
db07ab2 to
1e4f415
Compare
sozercan
force-pushed
the
feature/implement-queues
branch
from
July 7, 2026 07:23
1e4f415 to
695f39c
Compare
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
sozercan
force-pushed
the
feature/implement-queues
branch
from
July 7, 2026 18:19
695f39c to
f49ee0b
Compare
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
sozercan
force-pushed
the
feature/implement-queues
branch
from
July 8, 2026 02:31
2a10062 to
b2f23e3
Compare
sozercan
approved these changes
Jul 12, 2026
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Reject outgoing media observations during a receipt-time-bounded navigation grace, while allowing persistent mismatches to reach recovery. Wait for YouTube Music's selected source queue row before invoking Play Next so its empty-queue fallback cannot start the target immediately. Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR adds best-effort gapless playback by mirroring Kaset's expected next queue occurrence into YouTube Music's native Up Next queue. It also keeps the YouTube Music app shell warm and prefers the web player's SPA router before falling back to a full
watchpage load.Kaset's local queue remains the source of truth. Manual Next/Previous actions—including media keys—advance Kaset's queue through deterministic
play(song:)/loadVideo(videoId:)navigation. Natural track-end playback uses the native queue only after the expected source/target relationship has been confirmed. Kaset commits the handoff only when the media-bound observer reports the expected target; stale events, wrong tracks, queue edits, failed injection, or timeout all fall back to deterministic loading.AI Prompt (Optional)
🤖 AI Prompt Used
AI Tools: Gemini CLI for the initial implementation; Codex for follow-up diagnosis, hardening, tests, and documentation.
Type of Change
Related Issues
Fixes #241
Fixes #245
Changes Made
endedevents cannot move the queue backward or double-advance it.watchEndpointrouter navigation, and generation-guards full-load fallbacks.api-explorer queue-probefor inspecting YouTube Musicnext/radio queue response shapes.docs/playback.md, and expands the playback WebView debugging skill with the new queue/handoff components.Testing
swift buildswiftlint --strict --quietswift test --skip KasetUITests --disable-xctest --no-parallelChecklist
swiftlint --strict && swiftformat .Screenshots
Bildschirmaufnahme.2026-05-09.um.8.35.21.PM.mov
Additional Notes
This is best-effort gapless playback through YouTube Music's WebView player, not sample-perfect native audio-engine gapless playback. Real-world seamlessness still depends on YouTube Music buffering, WebKit timing, network state, and YouTube's internal player behavior. When native queue state cannot be proven correct, Kaset deliberately prioritizes queue correctness over gaplessness.