Skeuomorphic Music Player
A skeuomorphic CD player featuring an animated rotating vinyl/CD, custom audio seeking, animated sound waves, and a morphing fullscreen track-transition animation.
Playground Config
Installation
Option A: Via Sora UI CLI (Recommended)
If your project uses `shadcn/ui`, you can install this component and its dependencies automatically:
npx soraui-cli add music-playerOption B: Manual Copy-Paste
components/music-player.tsx.npm install framer-motion lucide-react clsx tailwind-mergeAPI Reference
All customization is done through standard React props. No internal state management, no context providers — just pass the props you need and the component handles the rest.
| Prop | Type | Default | Description |
|---|---|---|---|
spotifyToken | string | — | Spotify Web API Bearer Token. Used to fetch track metadata and album art. Get one from the Spotify Developer Dashboard. |
trackIds | string[] | — | Array of Spotify Track IDs. Each track's name, artist, cover art, and 30s audio preview will be fetched. Falls back to built-in demo tracks when omitted. |
timelineColor | string | "#5e6ad2" | Hex color for the progress bar fill. Changes the seek timeline accent to match your brand. |
componentColor | string | "#0f1011" | Hex color for the player card background. Controls the overall body color of the skeuomorphic shell. |
titleOverride | string | — | Override the displayed song title. When set, this text replaces the track name fetched from Spotify. |
artistOverride | string | — | Override the displayed artist name. When set, this text replaces the artist fetched from Spotify. |
defaultVolume | number | 80 | Initial audio volume from 0 to 100. Maps directly to HTMLAudioElement.volume. |
playbackSpeed | number | 1.0 | Audio playback rate multiplier. Supports 0.5, 1.0, 1.5, and 2.0. |
loop | boolean | true | When true, the playlist loops back to the first track after the last one ends. When false, playback stops after the final track. |
🎵 Spotify Integration
The player fetches track names, artist names, album cover art (used as the spinning CD banner), and 30-second audio previews directly from the Spotify Web API.
Create a Spotify App
Go to developer.spotify.com/dashboard → Create App → Copy your Client ID and Client Secret.
Get an Access Token
Exchange your credentials for a Bearer token using the included server-side API route /api/spotify/token or your own backend. See the code example below.
Pass the token and track IDs
Pass the token as spotifyToken and your chosen track IDs as trackIds. The component will auto-fetch all metadata.
// app/api/spotify/token/route.ts (included in Sora UI)
//
// POST { clientId, clientSecret }
// Returns { access_token, expires_in }
async function getSpotifyToken() {
const res = await fetch("/api/spotify/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
}),
});
const { access_token } = await res.json();
return access_token;
}import { MusicPlayer } from "@/components/ui/music-player";
// 1. Get your Spotify credentials:
// → https://developer.spotify.com/dashboard
// → Create an app → Copy Client ID & Secret
//
// 2. Exchange credentials for an Access Token
// (use our included API route or your own backend):
const SPOTIFY_TOKEN = "BQDj7g0...your_token_here";
const MY_TRACKS = [
"0V3wPSX3ygHQwZ21K47gOO", // Shape of You
"7qiZRhU7tZ2XG6xBvDUz6r", // Blinding Lights
"5PjdY0CKDHMDzPyvl44df6", // Levitating
];
export default function SpotifyPlayer() {
return (
<MusicPlayer
spotifyToken={SPOTIFY_TOKEN}
trackIds={MY_TRACKS}
/>
);
}Customization Examples
Progress Timeline Color
Change the seek bar accent with any hex color value. Great for matching your app's brand palette.
// Pink progress bar
<MusicPlayer timelineColor="#ec4899" />
// Green neon progress bar
<MusicPlayer timelineColor="#22c55e" />
// Orange warm progress bar
<MusicPlayer timelineColor="#f97316" />Component Background Color
Customize the skeuomorphic card body. Supports hex and rgba for glass-like transparency effects.
// Deep navy shell
<MusicPlayer componentColor="#0a1628" />
// Warm charcoal
<MusicPlayer componentColor="#1c1917" />
// Transparent glass (pair with backdrop-blur)
<MusicPlayer componentColor="rgba(15, 16, 17, 0.6)" />🔁 Playlist: Loop vs Static
Control whether the playlist loops continuously or stops after the last track. Perfect for background music vs. curated listening.
// Loop mode — restarts playlist after last track
<MusicPlayer
spotifyToken={token}
trackIds={["id1", "id2", "id3"]}
loop={true}
/>
// Static mode — stops after the last track finishes
<MusicPlayer
spotifyToken={token}
trackIds={["id1", "id2", "id3"]}
loop={false}
/>🔊 Volume & Playback Speed
Set the initial volume (0–100) and speed multiplier (0.5x, 1.0x, 1.5x, 2.0x). Both map directly to the HTML Audio API.
// Quiet background music at half speed
<MusicPlayer
defaultVolume={30}
playbackSpeed={0.5}
/>
// Full volume, double speed
<MusicPlayer
defaultVolume={100}
playbackSpeed={2.0}
/>✏️ Song Name & Artist Override
Override the displayed track title and artist name. Useful for custom branding or when you want a fixed label regardless of which track is playing.
// Display a custom title and artist
<MusicPlayer
titleOverride="My Custom Mix"
artistOverride="DJ Sora"
/>Full Kitchen Sink Example
Here's a complete example combining Spotify integration with every customization option:
import { MusicPlayer } from "@/components/ui/music-player";
const TOKEN = "BQDj7g0...";
export default function FullCustom() {
return (
<MusicPlayer
spotifyToken={TOKEN}
trackIds={[
"0V3wPSX3ygHQwZ21K47gOO",
"7qiZRhU7tZ2XG6xBvDUz6r",
"5PjdY0CKDHMDzPyvl44df6",
]}
timelineColor="#a855f7"
componentColor="#0c0a12"
defaultVolume={60}
playbackSpeed={1.0}
loop={true}
/>
);
}Dependencies
| Package | Purpose |
|---|---|
framer-motion | CD spin animation, cover morph transitions, sound wave bars |
clsx + tailwind-merge | Conditional class name merging via the cn() utility |
tailwindcss | Utility-first CSS framework for all layout and styling |
npm install framer-motion clsx tailwind-merge