Skip to content

Fix/playback history sync#780

Open
adam-adrian wants to merge 10 commits into
ArchiveTuneApp:devfrom
adam-adrian:fix/playback-history-sync
Open

Fix/playback history sync#780
adam-adrian wants to merge 10 commits into
ArchiveTuneApp:devfrom
adam-adrian:fix/playback-history-sync

Conversation

@adam-adrian

@adam-adrian adam-adrian commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Summary

Fix YouTube Music playback history not syncing for third-party clients. The root cause was twofold: (1) player requests lacked valid BotGuard PoToken and signatureTimestamp, causing YouTube to return "decoy" tracking URLs that accept requests but don't record history; (2) the metadata player request gated cookie sending on dataSyncId availability, meaning unauthenticated requests produced invalid tracking URLs. The fix adds a WebView-based BotGuard PoToken generator, passes PoToken + signatureTimestamp to all metadata player requests, and ensures cookies are always sent regardless of login state.

Linked Work

  • Closes: (link to the playback history sync issue if one exists)

Change Type

  • Feature
  • Bug fix
  • UI / UX
  • Performance / memory
  • Playback / Media3
  • Lyrics / provider integration
  • Search / YouTube / network data
  • Local library / Room database
  • Widgets / notification / shortcuts
  • Settings / preferences / DataStore
  • Discord / Last.fm / ListenBrainz / external integration
  • Localization / strings / fastlane metadata
  • Build / Gradle / CI / release packaging
  • Dependency update
  • Documentation only

Affected Surfaces

  • :app
  • :innertube
  • :betterlyrics
  • :kugou
  • :lrclib
  • :lastfm
  • :simpmusic
  • :paxsenix
  • :unison
  • :canvas
  • :shazamkit
  • :spotifycore
  • server
  • fastlane
  • .github
  • Other:

Screenshots / Recordings

Before After
Songs appear in local history only, never in YouTube Music history Songs appear in both local and YouTube Music history after 30s of playback

Behavior Notes

  • Playback history sync: After playing a song for ≥30 seconds (configurable), the song is registered in the user's YouTube Music history via the s.youtube.com/api/stats/playback endpoint.
  • PoToken generation: A headless WebView boots YouTube's BotGuard challenge to generate cryptographically valid PoTokens. First call takes 2-5s (cold start); subsequent calls are <100ms (engine reused).
  • Session token caching: The BotGuard engine and session token are cached and reused across videos until the engine expires (~50 min) or the session changes.
  • WebView renderer crash handling: onRenderProcessGone is handled gracefully — the app survives WebView renderer OOM kills instead of crashing.
  • Remote history auto-refresh: The remote history screen silently refreshes after playback sync (retry 3x with 3s/6s/9s backoff) without showing a loading spinner.
  • Fallback behavior: If PoToken generation fails (WebView unavailable, timeout, broken WebView), the player request proceeds without PoToken — playback still works but history won't sync.

Architecture Checklist

  • The change preserves UDF flow: UI → ViewModel → UseCase/domain → Repository/data.
  • Screen state is represented structurally with Loading, Success, Refreshing, Empty, and Error where this PR introduces or changes screen state.
  • Composables receive immutable, UI-specific domain models instead of raw Room, network, or service entities.
  • Business work is not triggered directly from composition.
  • Exceptions are surfaced through explicit state or result types instead of being swallowed.
  • No runBlocking is introduced in app execution paths. (BotGuardTokenGenerator.mintToken is a suspend function.)

Compose / Material Checklist

  • UI state is hoisted out of composable layout code.
  • Reactive state is collected with collectAsStateWithLifecycle().
  • New UI models are annotated with @Immutable or @Stable. (No new UI models)
  • Lazy layouts use stable key values and explicit contentType.
  • Non-primitive constants, structural lambdas, and allocation-heavy objects in hot paths are remembered.
  • Rapidly changing inputs use derivedStateOf where appropriate. (No new rapidly changing inputs)
  • UI strings come from stringResource() and duplicated visible strings on the same screen are avoided.
  • Interactive elements keep a minimum 48dp touch target.
  • Material 3 tokens are used for color, typography, shape, and motion.
  • Edge-to-edge layouts handle WindowInsets correctly.

Concurrency / Performance Checklist

  • Business coroutines are scoped to viewModelScope or an existing lifecycle-owned scope.
  • Disk, database, network, parsing, and heavy mapping work is dispatched to Dispatchers.IO.
  • Cancellation is handled explicitly where long-running work is involved.
  • The change avoids blocking the main thread.
  • The change avoids unnecessary allocations in recomposition loops.
  • Large lists, images, lyrics payloads, and network responses are bounded, streamed, cached, paged, or mapped off the main thread.

Data / Persistence Checklist

  • Room entity, DAO, or database changes include a schema update under app/schemas. (No schema changes)
  • Database migrations preserve existing user data. (No migrations)
  • DataStore or preference-key changes are backward compatible. (No preference changes)
  • Network DTOs remain isolated from UI models.
  • Provider responses handle missing, malformed, regional, or rate-limited data without crashing.

Playback / Integration Checklist

  • Media3 playback, queue, cache, and service behavior remains stable across foreground, background, notification, and process recreation paths.
  • Audio focus, notification controls, widgets, shortcuts, and media session actions are considered when playback behavior changes. (No playback behavior changes)
  • External integrations handle absent credentials, revoked auth, network failure, and API changes.
  • Native, AAR, or ABI-sensitive changes account for mobile/tv and universal variants. (No ABI-sensitive changes)

Localization / Assets Checklist

  • User-facing strings are added to the base resources. (No new strings)
  • Unused string resources, drawables, and metadata are removed when replaced. (None removed)
  • Image assets have explicit display dimensions. (No new images)
  • Fastlane metadata updated. (No store-facing changes)

Privacy / Security Checklist

  • No secrets, keys, tokens, keystores, signing files, private certificates, or local machine paths are committed.
  • Logs do not expose access tokens, cookies, auth headers, user identifiers, listening history, or local file paths.
  • New network calls are justified by the feature and use existing client, proxy, timeout, and error-handling patterns.
  • User data remains local. (History sync uses existing YouTube auth — no new data collection)

Verification

  • Android Studio sync: Builds successfully
  • Manual device/emulator verification: Tested on POCO X7 Pro (Android 16)
  • UI screenshot/recording attached: (add screenshot of YouTube history showing synced song)
  • Accessibility or touch-target review: No new interactive elements
  • Regression areas checked: Playback service lifecycle, WebView OOM handling, auth state management
  • CI expectation: All checks pass

Reviewer Focus

  1. BotGuardTokenGenerator.kt — WebView-based PoToken generation. Pay attention to:

    • Thread safety (Mutex usage for suspend functions)
    • WebView lifecycle (onRenderProcessGone handler)
    • Memory management (engine reuse, LRU cache)
  2. YTPlayerUtils.kt — Player request changes:

    • setLogin = true on all metadata requests (previously gated on dataSyncId)
    • poToken and signatureTimestamp passed to player requests
    • sessionId = authState.visitorData (always visitorData, not dataSyncId)
  3. InnerTube.ktparameterIfMissingparameter in registerPlayback

  4. HistoryScreen.kt — Silent remote history refresh with retry backoff

Release Notes

YouTube Music playback history now syncs correctly when songs are played for 30+ seconds. Previously, songs appeared in local history but never in YouTube Music history.

@sang765 sang765 added the BUG Something isn't working label Jun 12, 2026
@adam-adrian adam-adrian marked this pull request as ready for review June 12, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BUG Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants