Skip to content

feat(player): add gapless queue handoff and SPA navigation - #248

Open
tui2019 wants to merge 75 commits into
sozercan:mainfrom
tui2019:feature/implement-queues
Open

feat(player): add gapless queue handoff and SPA navigation#248
tui2019 wants to merge 75 commits into
sozercan:mainfrom
tui2019:feature/implement-queues

Conversation

@tui2019

@tui2019 tui2019 commented May 9, 2026

Copy link
Copy Markdown

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 watch page 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
Implement gapless playback by intercepting YouTube Music's native "Up Next" queue and injecting Kaset's local queue items via JavaScript. Update the track navigation to use the web player's SPA router (`navigateEndpoint`) instead of fully reloading the page.

AI Tools: Gemini CLI for the initial implementation; Codex for follow-up diagnosis, hardening, tests, and documentation.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📚 Documentation update
  • 🎨 UI/UX improvement
  • ♻️ Refactoring (no functional changes)
  • 🧪 Test update
  • 🔧 Build/CI configuration

Related Issues

Fixes #241
Fixes #245

Changes Made

  • Native queue handoff: Injects the exact expected next queue occurrence through YouTube Music's own Play next command, then verifies the rendered queue before trusting it.
  • Best-effort gapless playback: Lets natural track end use YouTube Music's native auto-advance only after confirmed injection; a wrong target or three-second timeout falls back to deterministic Kaset navigation.
  • Occurrence-scoped playback identity: Binds bridge events to document, observer epoch, media generation, and media video ID so stale metadata or duplicate ended events cannot move the queue backward or double-advance it.
  • Deterministic manual navigation: Keeps Next/Previous buttons and media keys on Kaset's local queue path, with bounded/coalesced recovery when stale WebView metadata conflicts with the selected target.
  • Queue maintenance hardening: Cancels stale radio/mix maintenance after external queue replacement, preserves task-local mutations, handles queue edits during handoff, and follows ephemeral Smart Shuffle entries even when the persisted signature is unchanged.
  • SPA routing and preload: Keeps the singleton playback WebView mounted when needed, preloads the YouTube Music app shell, prefers watchEndpoint router navigation, and generation-guards full-load fallbacks.
  • Restoration and autoplay safety: Restores deferred sessions and saved seek positions without hidden playback, while explicit Resume/Next/Previous actions unblock autoplay.
  • API exploration: Adds api-explorer queue-probe for inspecting YouTube Music next/radio queue response shapes.
  • Regression coverage: Adds focused tests for queue injection, native handoff, playback identity, persistence synchronization, navigation recovery, repeat/duplicate tracks, and stale async work; serializes CI unit tests and replaces an equalizer timing sleep with an explicit persistence await.
  • Documentation and agent guidance: Adds ADR-0026, updates docs/playback.md, and expands the playback WebView debugging skill with the new queue/handoff components.

Testing

  • swift build
  • swiftlint --strict --quiet
  • swift test --skip KasetUITests --disable-xctest --no-parallel
  • GitHub Actions build, API Explorer, lint/format, unit-test, and UI-test checks pass
  • Structured autoreview/adversarial review passed with no accepted/actionable findings
  • Manual playback testing performed on macOS 26+

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat .
  • I have added tests that prove my fix/feature works
  • New and existing unit tests pass locally
  • I have updated documentation if needed
  • I have checked for any performance implications
  • My changes generate no new warnings

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.

Copilot AI balanced review requested due to automatic review settings May 9, 2026 18:35

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.

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.

Comment thread Sources/Kaset/Services/Player/PlayerService+PlaybackControls.swift Outdated
Comment thread Sources/Kaset/Services/Player/PlayerService+PlaybackControls.swift
Comment thread Sources/Kaset/Services/Player/PlayerService+WebQueueSync.swift Outdated
Comment thread Sources/Kaset/Views/MiniPlayerWebView.swift Outdated
Comment thread Sources/Kaset/Views/SingletonPlayerWebView+PlaybackControls.swift
Comment thread Sources/Kaset/Views/MiniPlayerWebView.swift Outdated
Comment thread Sources/Kaset/Views/MiniPlayerWebView.swift
sozercan added 2 commits July 6, 2026 20:19
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>
Copilot AI review requested due to automatic review settings July 7, 2026 06:22

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.

Review details

  • Files reviewed: 14/14 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread Sources/Kaset/Services/Player/PlayerService+WebQueueSync.swift Outdated
@sozercan
sozercan force-pushed the feature/implement-queues branch from db07ab2 to 1e4f415 Compare July 7, 2026 07:01
Copilot AI review requested due to automatic review settings July 7, 2026 07:23
@sozercan
sozercan force-pushed the feature/implement-queues branch from 1e4f415 to 695f39c Compare July 7, 2026 07:23

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.

Review details

  • Files reviewed: 15/15 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
@sozercan
sozercan force-pushed the feature/implement-queues branch from 695f39c to f49ee0b Compare July 7, 2026 18:19
Copilot AI review requested due to automatic review settings July 7, 2026 19: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.

Review details

  • Files reviewed: 21/21 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

sozercan added 2 commits July 7, 2026 19:23
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings July 8, 2026 02:31
@sozercan
sozercan force-pushed the feature/implement-queues branch from 2a10062 to b2f23e3 Compare July 8, 2026 02:31

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.

🟡 Not ready to approve

Unresolved media identity can silently discard the only terminal event, leaving playback or end-of-queue suppression unhandled.

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

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

Sources/Kaset/Views/SingletonPlayerWebView+ObserverScript.swift:814

  • When media identity stays unresolved through this deadline, this returns without delivering any usable terminal event. The initial TRACK_ENDED was marked uncertain and is discarded by PlaybackBridgeMultiplexer, so PlayerService.handleTrackEnded never runs: a failed native auto-advance can leave playback stuck, and end-of-queue autoplay suppression is never applied. On deadline, route the occurrence through a deterministic Swift recovery (or otherwise emit a terminal signal that Swift accepts) rather than silently dropping it.
                if (retryNow > identityRetryDeadline) return;

Tests/KasetTests/Helpers/MusicPlaybackObserverTestContext.swift:8

  • This newly added force unwrap violates the repository's explicit no-force-unwrap policy. Handle the optional JSContext initializer with a guard so a context creation failure produces a clear test failure instead of an unconditional unwrap.
        let context = JSContext()!
  • Files reviewed: 52/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

sozercan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Addressed the suppressed Copilot findings from the prior head in 35d7e429:

  • MusicPlaybackObserverTestContext.make() is now throwing and uses try #require(JSContext()); all callers propagate try, so the newly extracted helper has no force unwrap.
  • A deferred changed identity resolved by the live playing fast path now advances mediaGeneration, allowing Swift to accept the queue-entry transition. The advance is deliberately limited to non-ended media with no pending ended occurrence, so bounded terminal identity recovery retains its original generation/provenance.
  • ADR-0032 now documents the normal 3-second handoff fallback plus the advertisement-only 15-second bounded grace (18 seconds maximum from handoff start).

Final local gate on this head: strict lint clean, swift build --build-tests successful, 52 focused observer tests passed, structured autoreview clean, and 2,926 tests in 216 suites passed. No UI tests were run locally.

@codex review

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.

🟡 Human review recommended

The extensive asynchronous WebView, undocumented DOM, restoration, and queue-state interactions warrant final human runtime review.

Review details
  • Files reviewed: 52/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@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: 35d7e42938

ℹ️ 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/SingletonPlayerWebView+ObserverScript.swift
Comment thread Sources/Kaset/Views/MiniPlayerWebView.swift
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

sozercan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Addressed the remaining suppressed Copilot finding in a3741d7c.

When identity is still unresolved at the five-second deadline, JavaScript now emits a distinct TRACK_ENDED_IDENTITY_DEADLINE event with the original frozen document/native/media generations, timestamp, observer epoch, document ID, and ad state, plus explicit empty logical and physical IDs. The bridge strictly validates that identityless non-ad shape and consumes the occurrence once. Swift then:

  • resolves an existing native handoff through the existing deterministic fallback;
  • otherwise disables/re-clears native injection across async maintenance and applies normal deterministic queue, repeat, or end-of-queue handling;
  • rejects contradictory identity-bearing occurrences and duplicate terminal callbacks.

Poll-based identity repair remains on the pending ended occurrence, while live playing identity refresh still advances generation.

Final local gate: strict lint clean, swift build --build-tests successful, 266 focused tests passed, structured autoreview clean, and 2,932 tests in 216 suites passed. No UI tests were run locally.

@codex review

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.

🟡 Human review recommended

It substantially changes timing-sensitive WebView, queue, restoration, and concurrency behavior using undocumented YouTube Music internals.

Review details
  • Files reviewed: 52/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: a3741d7cc1

ℹ️ 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".

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

sozercan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The latest Codex redirect finding is addressed and resolved in eb92b4fc: same-token redirects now refresh scripts and then commit the refreshed document ID only to the active navigation token; stale tokens cannot adopt it.

Latest local gate: strict lint clean, swift build --build-tests successful, 34 focused navigation/document-generation tests passed, structured autoreview clean, and 2,934 tests in 216 suites passed. No UI tests were run locally.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: eb92b4fc2f

ℹ️ 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".

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.

🟡 Human review recommended

The extensive WebView, queue, restoration, and concurrency state-machine changes require final human runtime review, and one claimed synchronization hook remains unused.

Review details

Suppressed comments (1)

Sources/Kaset/Services/Audio/EqualizerService.swift:304

  • This synchronization hook has no call sites, so it does not replace the equalizer persistence wait as claimed in the PR description. Please either use it from the persistence test that needs deterministic completion or remove the unused API.
    func awaitPendingPersistence() async {
        let task = self.persistTask
        await task?.value
    }
  • Files reviewed: 52/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

sozercan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Addressed the final suppressed Copilot note in f3405d88: the EqualizerService persistence round-trip test now directly awaits awaitPendingPersistence() instead of polling revived services, so the synchronization hook is exercised and the test is deterministic.

Verification: strict lint clean, swift build --build-tests successful, and all 24 EqualizerServiceTests passed. The preceding production head already passed the full 2,934-test non-UI suite and clean structured autoreview.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: f3405d88da

ℹ️ 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".

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.

🟡 Human review recommended

The 53-file WebView playback state-machine change requires final human runtime validation despite strong regression coverage.

Review details
  • Files reviewed: 53/53 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@AnthonyLenglet

Copy link
Copy Markdown
Contributor

trying this version of the app locally, load time between song has been greatly reduced, however I am noticing that the start of the next song gets cut off (I'd say its about half a second, small but noticeable on songs that are meant to be continuous)

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

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.

@sozercan

sozercan commented Aug 7, 2026

Copy link
Copy Markdown
Owner

@AnthonyLenglet do you mind testing the latest build when you get a chance?

@AnthonyLenglet

Copy link
Copy Markdown
Contributor

@AnthonyLenglet do you mind testing the latest build when you get a chance?

no luck with this one unfortunately

for reference, I'm currently using this album to test it, specifically the transition from song 9 to 10, since song 10 immediately starts with a kick
https://music.youtube.com/playlist?list=OLAK5uy_mSaapDjAk90i-1RlJ_kSz4xZn9J5EHBwU

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 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-05T22:21:57.464130Z 944e479 New commits
ℹ️ 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

🔵 Needs a closer look

The large, timing-sensitive WebKit and queue-state redesign warrants final human validation despite its extensive regression coverage.

Review tier: Balanced
Findings: None

Keep music audio output active across short transitions with bounded
cleanup. Validate navigation object lifetimes, clear restored autoplay
blocks on explicit Play, and wait while advertisement playback advances.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

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

The queue probe exposes raw personalized identifiers and metadata despite the endpoint’s established redaction contract.

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

Review tier: Balanced
Findings: 2 High severity

New issues introduced by this change (2)
Severity Finding
High severity Sources/​APIExplorer/​main.swift — Mandatory: this allowlist exposes raw videoId/playlist/browse identifiers and free-form text in…
High severity Sources/​APIExplorer/​main.swift — Mandatory: the normal (non-verbose) queue probe prints decoded next response values, including…

Comment on lines +1690 to +1701
private let queueProbeSafeDiagnosticStringKeys: Set<String> = [
"browseid",
"icontype",
"label",
"musicvideotype",
"pagetype",
"playlistid",
"simpletext",
"text",
"videoid",
"webpagetype",
]
Comment on lines +1822 to +1842
let songs = parseQueueProbeSongs(from: data)
let ids = songs.map(\.videoId)
let seedPositions = ids.enumerated().compactMap { index, id in id == videoId ? index : nil }
let firstPlayable = songs.first
let nextPlayable = songs.dropFirst().first
print("Queue summary:")
print(" • Parsed songs: \(songs.count)")
print(" • Seed positions: \(seedPositions.isEmpty ? "none" : seedPositions.map(String.init).joined(separator: ", "))")
print(" • First parsed id: \(firstPlayable?.videoId ?? "none")")
print(" • Second parsed id: \(nextPlayable?.videoId ?? "none")")
print(" • Autoplay overlay id: \(queueProbeAutoplayVideoId(in: data) ?? "none")")
print(" • Has continuation: \(queueProbeContinuationToken(in: data) == nil ? "no" : "yes")")

if !songs.isEmpty {
print("\nFirst songs:")
for (index, song) in songs.prefix(10).enumerated() {
let marker = song.videoId == videoId ? " ← seed" : ""
let artistSuffix = song.artists.isEmpty ? "" : " — \(song.artists)"
print(" [\(index)] \(song.videoId) :: \(song.title)\(artistSuffix)\(marker)")
}
}
Preserve queue handoffs and restored playback while integrating main's ad detection and seek protection.

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

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

The new queue probe exposes decoded personalized response values despite the established redacted-inspection contract.

Review tier: Balanced
Findings: 2 High severity

Pre-existing issues (2)
Severity Finding
High severity Sources/​APIExplorer/​main.swift — Mandatory: the normal (non-verbose) queue probe prints decoded next response values, including… View comment
High severity Sources/​APIExplorer/​main.swift — Mandatory: this allowlist exposes raw videoId/playlist/browse identifiers and free-form text in… View comment
Suppressed comments (2)

Sources/APIExplorer/main.swift:1701

  • The queue-probe “sanitizer” still emits raw response strings for generic keys such as text, simpleText, label, and videoId. Those fields can contain personalized queue data, so queue-probe -v/-o bypasses the existing next-endpoint contract that raw decoded values stay hidden (Sources/APIExplorer/main.swift:2475-2489). Keep the JSON shape, but redact every scalar value.
private let queueProbeSafeDiagnosticStringKeys: Set<String> = [
    "browseid",
    "icontype",
    "label",
    "musicvideotype",
    "pagetype",
    "playlistid",
    "simpletext",
    "text",
    "videoid",
    "webpagetype",
]

Sources/APIExplorer/main.swift:1840

  • The default command output prints decoded next response values (video IDs, titles, and artists), even without --verbose. Authenticated queues can be personalized, and the existing next inspection path explicitly promises that raw response values remain hidden (Sources/APIExplorer/main.swift:2475-2489). Report only structural facts such as counts, positions, and presence flags.
        print("  • Seed positions: \(seedPositions.isEmpty ? "none" : seedPositions.map(String.init).joined(separator: ", "))")
        print("  • First parsed id: \(firstPlayable?.videoId ?? "none")")
        print("  • Second parsed id: \(nextPlayable?.videoId ?? "none")")
        print("  • Autoplay overlay id: \(queueProbeAutoplayVideoId(in: data) ?? "none")")
        print("  • Has continuation: \(queueProbeContinuationToken(in: data) == nil ? "no" : "yes")")

        if !songs.isEmpty {
            print("\nFirst songs:")
            for (index, song) in songs.prefix(10).enumerated() {
                let marker = song.videoId == videoId ? " ← seed" : ""
                let artistSuffix = song.artists.isEmpty ? "" : " — \(song.artists)"
                print("  [\(index)] \(song.videoId) :: \(song.title)\(artistSuffix)\(marker)")

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.

[Bug]: Next track does not auto-play [Feature]: Seamless switching between songs

4 participants