Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion changelog/v0.1.0-beta3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ MUSIC
- The full player answers the same keys as Flow for Android: Space or K to play and pause, J, L and the arrow keys to seek, Up and Down for volume, M to mute, Shift+N and Shift+P to skip, 0 to 9 to jump through the song, S, R and Q for shuffle, repeat and the queue. Escape closes an open menu before the player.
- The full player has its own volume button, the mouse wheel changes volume over either volume button, and Shift with the wheel seeks over the seek bar. Hovering Next shows the song that plays next, and the top shows what you are playing from.
- The song menu in the player adds Start radio, Equalizer, Not interested and Don't recommend this artist.
- New Immersive Artwork background for the full player: the cover fills the top, or the left side of a wide window, and fades into the player. The blurred backgrounds now load the cover through the same copy as the artwork, so they no longer stay black when the cover is slow.
- New Immersive Artwork background for the full player: the cover fills the top, the left side of a wide window beside Up next or the lyrics, or the middle of one with that pane hidden, and fades into the player. The blurred backgrounds now load the cover through the same copy as the artwork, so they no longer stay black when the cover is slow.
- "Watch video" in the player's song menu opens the song's official music video at the same moment and pauses the song. The video is matched the same way Flow for Android does, so live takes, remixes and audio uploads are skipped.
- The full player's seek bar glides with the song instead of stepping, and dragging it seeks once when you let go instead of on every move. The page behind the full player stops drawing while it is covered.
- Mood and genre pages keep their shelves with titles instead of one long grid, album cards start playing without loading the album first, and playlist cards show their song count instead of the word Playlist.
Expand Down
59 changes: 38 additions & 21 deletions src/components/music/AmbientBackdrop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CSSProperties } from "react";
import { AnimatePresence, motion } from "framer-motion";

import { IS_LINUX_RUNTIME } from "../../lib/platform";
import type { PlayerBackgroundStyle } from "../../lib/playerColors";
Expand Down Expand Up @@ -45,25 +46,33 @@ const ARTWORK_OPACITY: Partial<Record<PlayerBackgroundStyle, string>> = {
immersive: "opacity-[0.85]",
};

/** Where immersive's cover sits: across the top, on the start side, or behind a centred song. */
type ImmersiveArtworkPlacement = "top" | "start" | "center";

/**
* Immersive's full-bleed cover (Android issue #954): it holds the top of the
* player, or the start side of a wide one, then melts into its own blurred
* continuation underneath.
* player, the start side of a wide one beside Up next or the lyrics, or the
* middle of a wide one with the side pane hidden, then melts into its own
* blurred continuation underneath.
*/
const IMMERSIVE_MASK = {
top: "linear-gradient(to bottom, black 42%, transparent 85%)",
start: "linear-gradient(to right, black 50%, transparent 100%)",
} as const;
const IMMERSIVE_COVER: Record<ImmersiveArtworkPlacement, { className: string; mask: string }> = {
top: { className: "left-0 w-full object-top", mask: "linear-gradient(to bottom, black 42%, transparent 85%)" },
start: { className: "left-0 w-[62%] object-center", mask: "linear-gradient(to right, black 50%, transparent 100%)" },
center: {
className: "left-[19%] w-[62%] object-center",
mask: "radial-gradient(ellipse 50% 50% at 50% 50%, black 45%, transparent 100%)",
},
};

interface AmbientBackdropProps {
/** The cover as the artwork card loads it, so both share one cached copy. */
imageSrc?: string | null;
style?: PlayerBackgroundStyle;
/** Wide layouts put the immersive cover on the start side, where the controls are not. */
artworkAtStart?: boolean;
/** Wide layouts put the immersive cover beside the side pane, or behind the song when it is hidden. */
artworkPlacement?: ImmersiveArtworkPlacement;
}

export function AmbientBackdrop({ imageSrc, style = "blur_gradient", artworkAtStart = false }: AmbientBackdropProps) {
export function AmbientBackdrop({ imageSrc, style = "blur_gradient", artworkPlacement = "top" }: AmbientBackdropProps) {
const artworkOpacity = ARTWORK_OPACITY[style];
const wash = washFor(style);

Expand All @@ -77,18 +86,26 @@ export function AmbientBackdrop({ imageSrc, style = "blur_gradient", artworkAtSt
className={`absolute inset-0 h-full w-full object-cover ${artworkOpacity}`}
/>
)}
{style === "immersive" && imageSrc && (
<img
src={imageSrc}
alt=""
draggable={false}
className={`absolute inset-y-0 left-0 h-full object-cover ${artworkAtStart ? "w-[62%] object-center" : "w-full object-top"}`}
style={{
maskImage: IMMERSIVE_MASK[artworkAtStart ? "start" : "top"],
WebkitMaskImage: IMMERSIVE_MASK[artworkAtStart ? "start" : "top"],
}}
/>
)}
{/* The cover crossfades to its new place as the side pane opens or closes. */}
<AnimatePresence initial={false}>
{style === "immersive" && imageSrc && (
<motion.img
key={artworkPlacement}
src={imageSrc}
alt=""
draggable={false}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
className={`absolute inset-y-0 h-full object-cover ${IMMERSIVE_COVER[artworkPlacement].className}`}
style={{
maskImage: IMMERSIVE_COVER[artworkPlacement].mask,
WebkitMaskImage: IMMERSIVE_COVER[artworkPlacement].mask,
}}
/>
)}
</AnimatePresence>
{wash && <div className={`${EASE} absolute inset-0`} style={wash} />}
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion src/components/music/MusicOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export function MusicOverlay() {
const fullBleedLyrics = isLyrics && (!wide || lyricsExpanded);
const sideTab: SidePaneTab = isLyrics ? "lyrics" : "queue";
const showPane = wide && paneVisible;
// Immersive's cover sits beside the side pane, and moves behind the song once the pane is hidden.
const artworkPlacement = !wide ? "top" : showPane && !fullBleedLyrics ? "start" : "center";

// Asking for the queue or the lyrics from elsewhere (a key, the dock) brings a hidden pane back.
// It follows the view only, so hiding the pane is never undone by the pane's own state.
Expand Down Expand Up @@ -197,7 +199,7 @@ export function MusicOverlay() {
style={{ opacity: innerOpacity, scale: innerScale, transformOrigin: "50% 100%" }}
className="relative flex min-h-0 flex-1 flex-col"
>
<AmbientBackdrop imageSrc={accentSrc} style={playerColors.style} artworkAtStart={wide} />
<AmbientBackdrop imageSrc={accentSrc} style={playerColors.style} artworkPlacement={artworkPlacement} />

{/* TOP BAR */}
<div className="absolute top-0 z-20 grid w-full grid-cols-[1fr_auto_1fr] items-center gap-4 p-6">
Expand Down
Loading