feat(player): disable metadata actions for ephemeral tracks
Library-bound actions (favorite, add-to-playlist, track info) require a DB row to operate; ephemeral tracks live only in memory and would 404 on use. Surface them disabled rather than removed so the affordance stays discoverable in the chrome — users still see the icon, the tooltip explains why it's inert. Surfaces wired - NowPlayingBar: + and ♥ disabled when active track id >= 2^48. - QueuePanel: now-playing ♥ + per-track Add to Playlist + Track Info in both queue and history menus disabled per-row by id range. - ImmersiveControls + PlayerControlsCompact: ♥ disabled. - ImmersivePlayer: passthrough prop to PlayerControlsCompact. - MiniPlayerFooter / MiniPlayerWindowControls: no metadata actions in these surfaces, nothing to gate. Detection is by id range (>= EPHEMERAL_ID_FLOOR), not by source string. The setQueueForLocalTracks mapping rewrites `source: 'ephemeral'` to `'local'` before the track reaches the queue, so a string-equality check would never fire — id range matches the rest of the ephemeral codebase (gapless carve-out, CUE boundary, seek offset, queue/now- playing wipe). Tooltip on the disabled state uses the new `actions.unavailableForEphemeral` key, translated for en, es, de, fr, pt. cc
This commit is contained in:
11 files changed
+112
-29
No files matched your search
@@ -95,6 +95,14 @@
|
|||||||
normalizationGain?: number | null;
|
normalizationGain?: number | null;
|
||||||
onToggleNormalization?: () => void;
|
onToggleNormalization?: () => void;
|
||||||
controlsDisabled?: boolean;
|
controlsDisabled?: boolean;
|
||||||
|
/**
|
||||||
|
* Hide the favorite + add-to-playlist buttons when the active track
|
||||||
|
* can't be persisted to library structures (today: ephemeral folder
|
||||||
|
* playback). Playback controls stay enabled — the user can still
|
||||||
|
* play / pause / skip / seek / volume; only the metadata-bound
|
||||||
|
* actions disappear.
|
||||||
|
*/
|
||||||
|
metadataActionsDisabled?: boolean;
|
||||||
explicit?: boolean;
|
explicit?: boolean;
|
||||||
qconnectSessionSnapshot?: QconnectSessionSnapshot | null;
|
qconnectSessionSnapshot?: QconnectSessionSnapshot | null;
|
||||||
onToggleQconnectConnection?: () => void | Promise<void>;
|
onToggleQconnectConnection?: () => void | Promise<void>;
|
||||||
@@ -150,6 +158,7 @@
|
|||||||
normalizationGain = null,
|
normalizationGain = null,
|
||||||
onToggleNormalization,
|
onToggleNormalization,
|
||||||
controlsDisabled = false,
|
controlsDisabled = false,
|
||||||
|
metadataActionsDisabled = false,
|
||||||
explicit = false,
|
explicit = false,
|
||||||
qconnectSessionSnapshot = null,
|
qconnectSessionSnapshot = null,
|
||||||
onToggleQconnectConnection,
|
onToggleQconnectConnection,
|
||||||
@@ -491,23 +500,25 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
class:disabled={controlsDisabled}
|
class:disabled={controlsDisabled || metadataActionsDisabled}
|
||||||
disabled={controlsDisabled}
|
disabled={controlsDisabled || metadataActionsDisabled}
|
||||||
onclick={controlsDisabled ? undefined : onAddToPlaylist}
|
onclick={controlsDisabled || metadataActionsDisabled ? undefined : onAddToPlaylist}
|
||||||
title={$translateStore('actions.addToPlaylist')}
|
title={metadataActionsDisabled ? $translateStore('actions.unavailableForEphemeral') : $translateStore('actions.addToPlaylist')}
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
class:active={isFavorite && !controlsDisabled}
|
class:active={isFavorite && !controlsDisabled && !metadataActionsDisabled}
|
||||||
class:disabled={controlsDisabled}
|
class:disabled={controlsDisabled || metadataActionsDisabled}
|
||||||
disabled={controlsDisabled}
|
disabled={controlsDisabled || metadataActionsDisabled}
|
||||||
onclick={controlsDisabled ? undefined : onToggleFavorite}
|
onclick={controlsDisabled || metadataActionsDisabled ? undefined : onToggleFavorite}
|
||||||
title={isFavorite ? $translateStore('actions.removeFromFavorites') : $translateStore('actions.addToFavorites')}
|
title={metadataActionsDisabled
|
||||||
|
? $translateStore('actions.unavailableForEphemeral')
|
||||||
|
: (isFavorite ? $translateStore('actions.removeFromFavorites') : $translateStore('actions.addToFavorites'))}
|
||||||
>
|
>
|
||||||
<Heart size={16} fill={isFavorite ? 'currentColor' : 'none'} />
|
<Heart size={16} fill={isFavorite && !metadataActionsDisabled ? 'currentColor' : 'none'} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,18 @@
|
|||||||
parental_warning?: boolean;
|
parental_warning?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirrors EPHEMERAL_ID_FLOOR (1 << 48) in src-tauri/src/ephemeral_library.
|
||||||
|
// Track ids at or above this threshold come from the in-memory ephemeral
|
||||||
|
// cache, not the library DB. Favorites / playlists / track-info actions
|
||||||
|
// require a DB row and don't make sense for ephemeral tracks; the
|
||||||
|
// helper below gates the corresponding UI off so users don't get
|
||||||
|
// dead-end clicks.
|
||||||
|
const EPHEMERAL_ID_FLOOR = 1 << 48;
|
||||||
|
function isEphemeralQueueTrack(id: string | number | null | undefined): boolean {
|
||||||
|
if (id == null) return false;
|
||||||
|
return Number(id) >= EPHEMERAL_ID_FLOOR;
|
||||||
|
}
|
||||||
|
|
||||||
interface IndexedQueueTrack {
|
interface IndexedQueueTrack {
|
||||||
track: QueueTrack;
|
track: QueueTrack;
|
||||||
originalIndex: number;
|
originalIndex: number;
|
||||||
@@ -349,11 +361,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="np-favorite"
|
class="np-favorite"
|
||||||
class:active={currentTrackFavorite}
|
class:active={currentTrackFavorite && !isEphemeralQueueTrack(currentTrack.id)}
|
||||||
onclick={toggleCurrentTrackFavorite}
|
class:disabled={isEphemeralQueueTrack(currentTrack.id)}
|
||||||
title={currentTrackFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites')}
|
disabled={isEphemeralQueueTrack(currentTrack.id)}
|
||||||
|
onclick={isEphemeralQueueTrack(currentTrack.id) ? undefined : toggleCurrentTrackFavorite}
|
||||||
|
title={isEphemeralQueueTrack(currentTrack.id)
|
||||||
|
? $t('actions.unavailableForEphemeral')
|
||||||
|
: (currentTrackFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites'))}
|
||||||
>
|
>
|
||||||
<Heart size={18} fill={currentTrackFavorite ? 'currentColor' : 'none'} />
|
<Heart size={18} fill={currentTrackFavorite && !isEphemeralQueueTrack(currentTrack.id) ? 'currentColor' : 'none'} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -438,11 +454,23 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button class="menu-item" onclick={(e) => handleAddToPlaylist(e, queueTrack.id)}>
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
class:disabled={isEphemeralQueueTrack(queueTrack.id)}
|
||||||
|
disabled={isEphemeralQueueTrack(queueTrack.id)}
|
||||||
|
onclick={(e) => { if (!isEphemeralQueueTrack(queueTrack.id)) handleAddToPlaylist(e, queueTrack.id); }}
|
||||||
|
title={isEphemeralQueueTrack(queueTrack.id) ? $t('actions.unavailableForEphemeral') : ''}
|
||||||
|
>
|
||||||
<ListPlus size={14} />
|
<ListPlus size={14} />
|
||||||
<span>{$t('actions.addToPlaylist')}</span>
|
<span>{$t('actions.addToPlaylist')}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="menu-item" onclick={(e) => handleShowTrackInfo(e, queueTrack.id)}>
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
class:disabled={isEphemeralQueueTrack(queueTrack.id)}
|
||||||
|
disabled={isEphemeralQueueTrack(queueTrack.id)}
|
||||||
|
onclick={(e) => { if (!isEphemeralQueueTrack(queueTrack.id)) handleShowTrackInfo(e, queueTrack.id); }}
|
||||||
|
title={isEphemeralQueueTrack(queueTrack.id) ? $t('actions.unavailableForEphemeral') : ''}
|
||||||
|
>
|
||||||
<Info size={14} />
|
<Info size={14} />
|
||||||
<span>{$t('player.trackInfo')}</span>
|
<span>{$t('player.trackInfo')}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -497,11 +525,23 @@
|
|||||||
</button>
|
</button>
|
||||||
{#if openHistoryMenuId === track.id}
|
{#if openHistoryMenuId === track.id}
|
||||||
<div class="track-context-menu">
|
<div class="track-context-menu">
|
||||||
<button class="menu-item" onclick={(e) => handleAddToPlaylist(e, track.id)}>
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
class:disabled={isEphemeralQueueTrack(track.id)}
|
||||||
|
disabled={isEphemeralQueueTrack(track.id)}
|
||||||
|
onclick={(e) => { if (!isEphemeralQueueTrack(track.id)) handleAddToPlaylist(e, track.id); }}
|
||||||
|
title={isEphemeralQueueTrack(track.id) ? $t('actions.unavailableForEphemeral') : ''}
|
||||||
|
>
|
||||||
<ListPlus size={14} />
|
<ListPlus size={14} />
|
||||||
<span>{$t('actions.addToPlaylist')}</span>
|
<span>{$t('actions.addToPlaylist')}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="menu-item" onclick={(e) => handleShowTrackInfo(e, track.id)}>
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
class:disabled={isEphemeralQueueTrack(track.id)}
|
||||||
|
disabled={isEphemeralQueueTrack(track.id)}
|
||||||
|
onclick={(e) => { if (!isEphemeralQueueTrack(track.id)) handleShowTrackInfo(e, track.id); }}
|
||||||
|
title={isEphemeralQueueTrack(track.id) ? $t('actions.unavailableForEphemeral') : ''}
|
||||||
|
>
|
||||||
<Info size={14} />
|
<Info size={14} />
|
||||||
<span>{$t('player.trackInfo')}</span>
|
<span>{$t('player.trackInfo')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -30,6 +30,11 @@
|
|||||||
onToggleFavorite: () => void;
|
onToggleFavorite: () => void;
|
||||||
onVolumeChange: (volume: number) => void;
|
onVolumeChange: (volume: number) => void;
|
||||||
volumeLocked?: boolean;
|
volumeLocked?: boolean;
|
||||||
|
/** Disable the favorite button when the active track lives outside
|
||||||
|
* the library (today: ephemeral folder playback). Heart stays visible
|
||||||
|
* but inert so the user understands the action exists; it just isn't
|
||||||
|
* meaningful for transient tracks. */
|
||||||
|
metadataActionsDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -52,7 +57,8 @@
|
|||||||
onToggleRepeat,
|
onToggleRepeat,
|
||||||
onToggleFavorite,
|
onToggleFavorite,
|
||||||
onVolumeChange,
|
onVolumeChange,
|
||||||
volumeLocked = false
|
volumeLocked = false,
|
||||||
|
metadataActionsDisabled = false
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -121,13 +127,17 @@
|
|||||||
<div class="right-section">
|
<div class="right-section">
|
||||||
<button
|
<button
|
||||||
class="action-btn"
|
class="action-btn"
|
||||||
class:active={isFavorite}
|
class:active={isFavorite && !metadataActionsDisabled}
|
||||||
onclick={onToggleFavorite}
|
class:disabled={metadataActionsDisabled}
|
||||||
title={isFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites')}
|
disabled={metadataActionsDisabled}
|
||||||
|
onclick={metadataActionsDisabled ? undefined : onToggleFavorite}
|
||||||
|
title={metadataActionsDisabled
|
||||||
|
? $t('actions.unavailableForEphemeral')
|
||||||
|
: (isFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites'))}
|
||||||
>
|
>
|
||||||
<Heart
|
<Heart
|
||||||
size={18}
|
size={18}
|
||||||
fill={isFavorite ? 'currentColor' : 'none'}
|
fill={isFavorite && !metadataActionsDisabled ? 'currentColor' : 'none'}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
onPlayHistoryTrack?: (trackId: string) => void;
|
onPlayHistoryTrack?: (trackId: string) => void;
|
||||||
// Content flags
|
// Content flags
|
||||||
explicit?: boolean;
|
explicit?: boolean;
|
||||||
|
/** Disable metadata-bound actions (favorite, etc) when the active
|
||||||
|
* track lives outside the library. Passed down to ImmersiveControls
|
||||||
|
* and PlayerControlsCompact. */
|
||||||
|
metadataActionsDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -150,7 +154,8 @@
|
|||||||
onQueueClear,
|
onQueueClear,
|
||||||
historyTracks = [],
|
historyTracks = [],
|
||||||
onPlayHistoryTrack,
|
onPlayHistoryTrack,
|
||||||
explicit = false
|
explicit = false,
|
||||||
|
metadataActionsDisabled = false
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
@@ -778,6 +783,7 @@
|
|||||||
{onToggleShuffle}
|
{onToggleShuffle}
|
||||||
{onToggleRepeat}
|
{onToggleRepeat}
|
||||||
{onToggleFavorite}
|
{onToggleFavorite}
|
||||||
|
{metadataActionsDisabled}
|
||||||
{isInfinitePlay}
|
{isInfinitePlay}
|
||||||
{onToggleInfinitePlay}
|
{onToggleInfinitePlay}
|
||||||
{onVolumeChange}
|
{onVolumeChange}
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
onMinimize?: () => void;
|
onMinimize?: () => void;
|
||||||
onToggleFullscreen?: () => void;
|
onToggleFullscreen?: () => void;
|
||||||
onToggleMaximize?: () => void;
|
onToggleMaximize?: () => void;
|
||||||
|
/** Disable favorite when active track lives outside the library
|
||||||
|
* (today: ephemeral folder playback). Heart stays visible but
|
||||||
|
* inert. */
|
||||||
|
metadataActionsDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -76,7 +80,8 @@
|
|||||||
onClose,
|
onClose,
|
||||||
onMinimize,
|
onMinimize,
|
||||||
onToggleFullscreen,
|
onToggleFullscreen,
|
||||||
onToggleMaximize
|
onToggleMaximize,
|
||||||
|
metadataActionsDisabled = false
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let progressRef: HTMLDivElement | null = $state(null);
|
let progressRef: HTMLDivElement | null = $state(null);
|
||||||
@@ -220,11 +225,15 @@
|
|||||||
<div class="controls-group left">
|
<div class="controls-group left">
|
||||||
<button
|
<button
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
class:active={isFavorite}
|
class:active={isFavorite && !metadataActionsDisabled}
|
||||||
onclick={onToggleFavorite}
|
class:disabled={metadataActionsDisabled}
|
||||||
title={isFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites')}
|
disabled={metadataActionsDisabled}
|
||||||
|
onclick={metadataActionsDisabled ? undefined : onToggleFavorite}
|
||||||
|
title={metadataActionsDisabled
|
||||||
|
? $t('actions.unavailableForEphemeral')
|
||||||
|
: (isFavorite ? $t('actions.removeFromFavorites') : $t('actions.addToFavorites'))}
|
||||||
>
|
>
|
||||||
<Heart size={12} fill={isFavorite ? 'currentColor' : 'none'} />
|
<Heart size={12} fill={isFavorite && !metadataActionsDisabled ? 'currentColor' : 'none'} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"addToPlaylist": "Zur Playlist hinzufügen",
|
"addToPlaylist": "Zur Playlist hinzufügen",
|
||||||
"addToFavorites": "Zur Bibliothek hinzufügen",
|
"addToFavorites": "Zur Bibliothek hinzufügen",
|
||||||
"removeFromFavorites": "Aus Bibliothek entfernen",
|
"removeFromFavorites": "Aus Bibliothek entfernen",
|
||||||
|
"unavailableForEphemeral": "Nicht verfügbar für temporäre Ordner",
|
||||||
"removeFromPlaylist": "Aus Playlist entfernen",
|
"removeFromPlaylist": "Aus Playlist entfernen",
|
||||||
"selectedTracks": "{count, plural, one {# Titel ausgewählt} other {# Titel ausgewählt}}",
|
"selectedTracks": "{count, plural, one {# Titel ausgewählt} other {# Titel ausgewählt}}",
|
||||||
"select": "Auswählen",
|
"select": "Auswählen",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"addToPlaylist": "Add to Playlist",
|
"addToPlaylist": "Add to Playlist",
|
||||||
"addToFavorites": "Add to Library",
|
"addToFavorites": "Add to Library",
|
||||||
"removeFromFavorites": "Remove from Library",
|
"removeFromFavorites": "Remove from Library",
|
||||||
|
"unavailableForEphemeral": "Not available for ephemeral folders",
|
||||||
"removeFromPlaylist": "Remove from Playlist",
|
"removeFromPlaylist": "Remove from Playlist",
|
||||||
"selectedTracks": "{count, plural, one {# track selected} other {# tracks selected}}",
|
"selectedTracks": "{count, plural, one {# track selected} other {# tracks selected}}",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"addToPlaylist": "Agregar a Lista",
|
"addToPlaylist": "Agregar a Lista",
|
||||||
"addToFavorites": "Agregar a Biblioteca",
|
"addToFavorites": "Agregar a Biblioteca",
|
||||||
"removeFromFavorites": "Quitar de Biblioteca",
|
"removeFromFavorites": "Quitar de Biblioteca",
|
||||||
|
"unavailableForEphemeral": "No disponible para carpetas efímeras",
|
||||||
"removeFromPlaylist": "Quitar del Playlist",
|
"removeFromPlaylist": "Quitar del Playlist",
|
||||||
"selectedTracks": "{count, plural, one {# canción seleccionada} other {# canciones seleccionadas}}",
|
"selectedTracks": "{count, plural, one {# canción seleccionada} other {# canciones seleccionadas}}",
|
||||||
"select": "Seleccionar",
|
"select": "Seleccionar",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"addToPlaylist": "Ajouter à la playlist",
|
"addToPlaylist": "Ajouter à la playlist",
|
||||||
"addToFavorites": "Ajouter à la bibliothèque",
|
"addToFavorites": "Ajouter à la bibliothèque",
|
||||||
"removeFromFavorites": "Retirer de la bibliothèque",
|
"removeFromFavorites": "Retirer de la bibliothèque",
|
||||||
|
"unavailableForEphemeral": "Non disponible pour les dossiers éphémères",
|
||||||
"removeFromPlaylist": "Retirer de la playlist",
|
"removeFromPlaylist": "Retirer de la playlist",
|
||||||
"selectedTracks": "{count, plural, one {# titre sélectionné} other {# titres sélectionnés}}",
|
"selectedTracks": "{count, plural, one {# titre sélectionné} other {# titres sélectionnés}}",
|
||||||
"select": "Sélectionner",
|
"select": "Sélectionner",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"addToPlaylist": "Adicionar à Playlist",
|
"addToPlaylist": "Adicionar à Playlist",
|
||||||
"addToFavorites": "Adicionar à biblioteca",
|
"addToFavorites": "Adicionar à biblioteca",
|
||||||
"removeFromFavorites": "Remover da biblioteca",
|
"removeFromFavorites": "Remover da biblioteca",
|
||||||
|
"unavailableForEphemeral": "Indisponível para pastas efêmeras",
|
||||||
"removeFromPlaylist": "Remover da Playlist",
|
"removeFromPlaylist": "Remover da Playlist",
|
||||||
"selectedTracks": "{count, plural, one {# faixa selecionada} other {# faixas selecionadas}}",
|
"selectedTracks": "{count, plural, one {# faixa selecionada} other {# faixas selecionadas}}",
|
||||||
"select": "Selecionar",
|
"select": "Selecionar",
|
||||||
|
|||||||
@@ -6727,6 +6727,7 @@
|
|||||||
{isFavorite}
|
{isFavorite}
|
||||||
onToggleFavorite={toggleFavorite}
|
onToggleFavorite={toggleFavorite}
|
||||||
onAddToPlaylist={openAddToPlaylistModal}
|
onAddToPlaylist={openAddToPlaylistModal}
|
||||||
|
metadataActionsDisabled={currentTrack != null && currentTrack.id >= (1 << 48)}
|
||||||
onOpenQueue={toggleQueue}
|
onOpenQueue={toggleQueue}
|
||||||
onOpenMiniPlayer={() => {
|
onOpenMiniPlayer={() => {
|
||||||
void enterMiniplayerMode();
|
void enterMiniplayerMode();
|
||||||
@@ -6832,6 +6833,7 @@
|
|||||||
onToggleRepeat={toggleRepeat}
|
onToggleRepeat={toggleRepeat}
|
||||||
{isFavorite}
|
{isFavorite}
|
||||||
onToggleFavorite={toggleFavorite}
|
onToggleFavorite={toggleFavorite}
|
||||||
|
metadataActionsDisabled={currentTrack != null && currentTrack.id >= (1 << 48)}
|
||||||
lyricsLines={lyricsLines}
|
lyricsLines={lyricsLines}
|
||||||
lyricsActiveIndex={lyricsActiveIndex}
|
lyricsActiveIndex={lyricsActiveIndex}
|
||||||
lyricsActiveProgress={lyricsActiveProgress}
|
lyricsActiveProgress={lyricsActiveProgress}
|
||||||
|
|||||||
Reference in new issue
Block a user