Shareable saved playlist restore and browser - #5
Open
NickKhunapoj wants to merge 15 commits into
Open
Conversation
Add a 💾 Save control that stores the active queue as an owner-bound, expiring JSON playlist. Add /music queue restore <code> to consume a saved playlist, replace the current queue, reconnect when needed, and begin normal playback automatically.
Make saved playlist codes shareable while retaining creator metadata, consume codes after restoration, and move restoration to `/music restore <code>`. Restore `/music queue` as the queue-view command. Prevent stale playback callbacks and retries from draining a newly restored queue, so it can be saved again reliably.
Add /music restore list to browse saved playlists with Queue-style pagination, page navigation, refresh, and numbered detail lookup. Show each playlist’s code, creator, creation and expiry times, track count, and ordered tracks in a private paginated details view.
Publish /music restore list and playlist detail pages to the channel so everyone can view saved playlist metadata and use the pagination, refresh, and detail controls.
- Add JSON-backed, expiring saved playlists with shareable single-use codes. - Save the active track, queued order, duration, and current playback position. - Add `/music restore <code>` and public `/music restore list` with pagination, details, load controls, and confirmation dialogs. - Restore playlists by replacing the active queue, reconnecting to voice when needed, and starting playback automatically. - Add safe restore race handling, concise DEBUG logging, persistence validation, and focused tests. - Update player controls with an icon-only save button and document the restore workflow.
- Add `🗑️` deletion controls to `/music restore list` and playlist details. - Let users choose a playlist number to delete from the public restore list. - Add private confirm/cancel popups for deletion, matching the restore confirmation flow. - Disable confirmation controls immediately after deletion begins to prevent duplicate actions. - Delete playlists atomically from JSON storage and handle unavailable, expired, malformed, and storage-error cases. - Add deletion logs and focused tests for persistence and confirmation behavior.
- Scope saved playlists, list results, restores, loads, and deletions to their originating Discord guild. - Keep playlist codes shareable only within the server where they were created. - Persist and validate `guild_id` in the JSON playlist datastore. - Exclude `/music local` attachment tracks from saved playlists. - Preserve only YouTube tracks and their playback position when saving mixed queues. - Reject local-only queues to prevent unusable restore codes. - Add guild-isolation and local-track save coverage.
Co-Authored-By: Codex <267193182+codex@users.noreply.github.com>
NickKhunapoj
marked this pull request as ready for review
August 12, 2026 06:00
- Split music background work into dedicated worker pools: - yt-dlp metadata extraction - FFmpeg decoder prefill and cleanup - Prevent metadata extraction from delaying decoder readiness and audio startup. - Limit crossfade hydration to one active yt-dlp job at a time. - Skip new crossfade preloads while a cancelled/stale extraction is still running, preventing queued worker buildup after skips or track changes. - Replace the audio buffer’s 500 ms blocking read with immediate 20 ms silence frames during temporary underruns. - Add rate-limited DEBUG diagnostics for sustained audio-buffer underruns. - Add cleanup of both music worker pools when the cog unloads. - Restore legacy saved-playlist claiming for records created before guild-scoped playlist metadata. - Add focused coverage for stale crossfade hydration and non-blocking audio-buffer underruns. - Verify with compile checks, diff checks, and 24 passing tests. Co-Authored-By: Codex <267193182+codex@users.noreply.github.com>
…ossfade successors - refresh the player controller after a crossfade fully completes so seek buttons are re-enabled once playback returns to a single active deck - keep seeking disabled during the active overlap to avoid mutating two simultaneously mixed streams - tighten seek readiness checks to require a connected, active player with a fully started current track - recalculate pending crossfade preloads when loop mode changes from either player controls or `/music loop` - support self-crossfade for single-track loop mode - support queue-loop wraparound by preloading the first queued track when the current track is the last one - preserve active crossfades during loop-mode changes; only obsolete pending preloads are discarded and rebuilt - add coverage for post-crossfade seek readiness, seek blocking during an active mix, track-loop self-crossfade selection, queue-loop successor selection, and loop-change preload refresh Co-Authored-By: Codex <267193182+codex@users.noreply.github.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.
Summary
Adds a lightweight, durable saved-playlist system for music queues.
Users can save YouTube tracks from the active queue, browse saved playlists in their server, inspect details, load or delete playlists with confirmation popups, and restore a playlist through the existing music playback pipeline.
New features
Save active queue
Added an icon-only
💾Save button beside Refresh on the player’s second control row.Saves YouTube tracks in their intended playback order:
Excludes
/music localattachment tracks, preventing unusable saved playlists.Rejects empty or local-only queues.
Stores each track’s title, YouTube URL/query, and duration.
Stores the current position only when the active track is saved.
Sends the generated restore code privately to the user.
Saving the active music queue and receiving a restore code.
Guild-private, shareable restore codes
xxxx-xxxxplaylist codes.Legacy records created before guild scoping are hidden from lists. Using an old code directly claims that record for the guild where it is first restored, preserving recovery while enforcing guild isolation afterward.
Restore command
Restoring a playlist and automatically starting playback.
Public playlist browser
Displays a public, paginated browser for playlists saved in the current server.
Controls:
◀️Previous,▶️Next,🔢Jump to page,🔄Refresh📄Details,♻️Load,🗑️DeleteLoad and Delete accept a playlist number through a modal, then show a private confirmation popup. The confirmation can only be completed by the user who opened it.
Public browser for saved playlists in the current server.
Playlist details
Details are publicly visible within the current server and include:
♻️Load and🗑️Delete buttonsPaginated playlist details in restored playback order.
Modified behavior
/music queueremains the active-queue viewer.LOG_LEVEL=DEBUG.Storage
Saved playlists are persisted at:
The directory is created automatically and is ignored by Git.
Each record stores:
Save, browse, and restore flow
flowchart LR User["Discord user"] -->|💾 Save| Player["Music player state"] Player --> Snapshot["Current + crossfade-next + queue"] Snapshot --> Filter["Keep YouTube tracks only"] Filter --> Store["Guild-scoped JSON datastore"] Store --> Code["Private xxxx-xxxx code"] User -->|/music restore list| Browser["Public server playlist browser"] Browser -->|📄| Details["Playlist details"] Browser -->|♻️ or 🗑️| Number["Playlist number modal"] Details -->|♻️ or 🗑️| Confirm["Private confirmation popup"] Number --> Confirm User -->|/music restore code| Validate["Validate code and guild"] Validate --> Confirm Confirm -->|Load| Consume["Consume playlist atomically"] Consume --> Queue["Replace active queue"] Queue --> Playback["Existing playback pipeline"] Playback --> Voice["Voice channel audio"]Restore lifecycle
flowchart TD A["Save YouTube queue"] --> B["Validate non-empty saved track list"] B --> C["Persist guild-scoped playlist JSON"] C --> D["Send restore code privately"] E["Open /music restore list"] --> F["Public paginated server list"] F --> G["Choose details, load, or delete by number"] G --> H["Private confirmation popup"] I["Run /music restore code"] --> J["Validate code, expiry, and guild"] J --> H H -->|Load| K["Validate voice channel and consume playlist"] K --> L["Replace queue and invalidate stale callbacks"] L --> M["Hydrate and start first track"] M --> N["Resume saved position when available"] H -->|Delete| O["Atomically delete playlist"]