mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(playlists): suggestion-row preview UX (#365)
* feat(playlists): suggestion-row preview UX (30s preview, double-click play-next, scroll keep)
Reworks the interaction on the suggestion rows below a playlist so users
can audition songs before deciding what to do with them.
- New "Preview" pill button left of each track title plays a 30-second
mid-song sample via a parallel HTML5 audio element. The main player
pauses on the first preview and auto-resumes when the preview ends.
Switching previews chains without re-resuming. External main-player
playback (spacebar, mediakey) cancels the preview without resuming.
Animated underline shows the 30 s progress.
- Double-click the row to insert the song at queueIndex + 1 and skip to
it ("Play next"). Single-click is intentionally inert so a stray click
next to the preview button no longer drops a song into the queue by
accident. Tooltip on the row makes the affordance discoverable.
- The + button still adds to the playlist, and now restores the
.main-content scroll position so the page no longer jumps to the top
after pressing it.
- i18n keys in all 8 locales: playlists.preview / previewStop / previewShort
/ previewStopShort / suggestionDoubleClickPlayNext.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(playlists): WebKitGTK NULL-instance crash on preview teardown
cucadmuh hit GLib-GObject-CRITICAL: invalid (NULL) pointer instance /
g_signal_connect_data assertion failed, with the UI freezing after
clicking a preview button. Root cause was the audio-element lifecycle:
- `audio.src = ''` to "stop" leaves WebKitGTK's GStreamer playbin in a
half-initialized state. The next signal_connect dereferences NULL.
- Creating `new Audio()` per click stacked half-torn-down playbins
during rapid switches.
- `loadedmetadata` listeners on orphaned audio elements could call
play() on an already-discarded instance.
Fix: reuse one <audio> element per component, tear down the source
via removeAttribute('src') + load() (which resets the playbin
cleanly), and gate async listeners behind a session counter so stale
metadata events on switched-away previews are ignored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(playlists): icon-based action buttons + add-on-doubleclick + hint
- Replace text-pill Preview button with circular icon button + animated
SVG progress ring (Play/Square icon swap when active).
- New Play-next button (filled accent circle, white triangle) sits
left of the Preview button — explicit affordance for the action that
used to be a hidden double-click.
- Double-click on a suggestion row now triggers Add-to-playlist (the
same action as the + button on the right) — discoverable shortcut
for mouse users.
- Subtitle under the Suggested Songs header announces the add-on-
doubleclick affordance, in all 8 locales.
- Drop the now-obsolete previewShort / previewStopShort short-label
keys (text replaced by icons); rename suggestionDoubleClickPlayNext
→ playNextSuggestion to match its new role on the Play-next button.
- Keep the session-counter guard from the prior commit so async
loadedmetadata handlers from a switched-away preview can't play()
on a discarded element.
Note: header column labels visually drift from the data columns when
suggestion rows have the action buttons; left as-is for now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(playlists): apply LUFS pre-analysis attenuation to suggestion previews
When the main player is on loudness normalization, analysed tracks come
out reduced toward target while the unanalysed preview <audio> blasts
the file at its natural level — cucadmuh reported the previews are
audibly louder than the playlist playback.
Apply the user's stored pre-analysis attenuation (the slider value, not
the target-offset effective form) as a linear gain on the preview's
audio.volume. Default −4.5 dB lands the preview at ~60% of the player
volume, which roughly tracks how aggressively the Rust engine pulls
naturally-loud tracks toward target.
If the engine is off, behavior is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(playlists): use chevron icon for suggestion preview button
Distinguishes the preview button from the adjacent play-next button
by mirroring the Play Next chevron from the context menu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
70ff025ece
commit
2bea55bedd
@@ -1287,6 +1287,10 @@ export const deTranslation = {
|
||||
titleBadge: 'Playlist',
|
||||
refreshSuggestions: 'Neue Vorschläge',
|
||||
addSong: 'Zur Playlist hinzufügen',
|
||||
preview: 'Vorschau (30 s)',
|
||||
previewStop: 'Vorschau stoppen',
|
||||
playNextSuggestion: 'Als Nächstes spielen',
|
||||
suggestionsHint: 'Doppelklick auf eine Zeile fügt sie zur Playlist hinzu',
|
||||
addSelected: 'Ausgewählte hinzufügen',
|
||||
cacheOffline: 'Playlist offline speichern',
|
||||
offlineCached: 'Playlist gecacht',
|
||||
|
||||
@@ -1293,6 +1293,10 @@ export const enTranslation = {
|
||||
titleBadge: 'Playlist',
|
||||
refreshSuggestions: 'New suggestions',
|
||||
addSong: 'Add to playlist',
|
||||
preview: 'Preview (30s)',
|
||||
previewStop: 'Stop preview',
|
||||
playNextSuggestion: 'Play next',
|
||||
suggestionsHint: 'Double-click a row to add it to the playlist',
|
||||
addSelected: 'Add selected',
|
||||
cacheOffline: 'Cache playlist offline',
|
||||
offlineCached: 'Playlist cached',
|
||||
|
||||
@@ -1280,6 +1280,10 @@ export const esTranslation = {
|
||||
titleBadge: 'Lista',
|
||||
refreshSuggestions: 'Nuevas sugerencias',
|
||||
addSong: 'Agregar a lista',
|
||||
preview: 'Vista previa (30 s)',
|
||||
previewStop: 'Detener vista previa',
|
||||
playNextSuggestion: 'Reproducir a continuación',
|
||||
suggestionsHint: 'Doble clic en una fila para añadirla a la lista',
|
||||
addSelected: 'Agregar seleccionados',
|
||||
cacheOffline: 'Guardar lista offline',
|
||||
offlineCached: 'Lista guardada',
|
||||
|
||||
@@ -1275,6 +1275,10 @@ export const frTranslation = {
|
||||
titleBadge: 'Playlist',
|
||||
refreshSuggestions: 'Nouvelles suggestions',
|
||||
addSong: 'Ajouter à la playlist',
|
||||
preview: 'Aperçu (30 s)',
|
||||
previewStop: 'Arrêter l\'aperçu',
|
||||
playNextSuggestion: 'Lire ensuite',
|
||||
suggestionsHint: "Double-cliquez sur une ligne pour l'ajouter à la playlist",
|
||||
addSelected: 'Ajouter la sélection',
|
||||
cacheOffline: 'Mettre la playlist hors ligne',
|
||||
offlineCached: 'Playlist en cache',
|
||||
|
||||
@@ -1274,6 +1274,10 @@ export const nbTranslation = {
|
||||
titleBadge: 'Spilleliste',
|
||||
refreshSuggestions: 'Nye forslag',
|
||||
addSong: 'Legg til i spilleliste',
|
||||
preview: 'Forhåndsvisning (30 s)',
|
||||
previewStop: 'Stopp forhåndsvisning',
|
||||
playNextSuggestion: 'Spill som neste',
|
||||
suggestionsHint: 'Dobbeltklikk på en rad for å legge den til i spillelisten',
|
||||
addSelected: 'Legg til valgte',
|
||||
cacheOffline: 'Bufre spilleliste offline',
|
||||
offlineCached: 'Spilleliste bufret',
|
||||
|
||||
@@ -1274,6 +1274,10 @@ export const nlTranslation = {
|
||||
titleBadge: 'Playlist',
|
||||
refreshSuggestions: 'Nieuwe suggesties',
|
||||
addSong: 'Toevoegen aan playlist',
|
||||
preview: 'Voorbeeld (30 s)',
|
||||
previewStop: 'Voorbeeld stoppen',
|
||||
playNextSuggestion: 'Hierna afspelen',
|
||||
suggestionsHint: 'Dubbelklik op een rij om hem aan de afspeellijst toe te voegen',
|
||||
addSelected: 'Geselecteerde toevoegen',
|
||||
cacheOffline: 'Playlist offline opslaan',
|
||||
offlineCached: 'Playlist gecached',
|
||||
|
||||
@@ -1359,6 +1359,10 @@ export const ruTranslation = {
|
||||
titleBadge: 'Плейлист',
|
||||
refreshSuggestions: 'Обновить подборку',
|
||||
addSong: 'В плейлист',
|
||||
preview: 'Превью (30 с)',
|
||||
previewStop: 'Остановить превью',
|
||||
playNextSuggestion: 'Играть следующим',
|
||||
suggestionsHint: 'Двойной клик по строке — добавить в плейлист',
|
||||
addSelected: 'Добавить выбранные',
|
||||
cacheOffline: 'Сохранить плейлист офлайн',
|
||||
offlineCached: 'Плейлист сохранён',
|
||||
|
||||
@@ -1269,6 +1269,10 @@ export const zhTranslation = {
|
||||
titleBadge: '播放列表',
|
||||
refreshSuggestions: '新建议',
|
||||
addSong: '添加到播放列表',
|
||||
preview: '试听 (30 秒)',
|
||||
previewStop: '停止试听',
|
||||
playNextSuggestion: '播放下一首',
|
||||
suggestionsHint: '双击行可添加到播放列表',
|
||||
addSelected: '添加所选',
|
||||
cacheOffline: '离线缓存播放列表',
|
||||
offlineCached: '播放列表已缓存',
|
||||
|
||||
Reference in New Issue
Block a user