A sleek, iPhone-inspired music player that floats at the top of your screen like a dynamic island notch. Features an expandable mini-player with animated waveform visualization, full playback controls, progress seeking, and volume adjustment.
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { SongPlayer } from '@/components/atomixui/song-player';
const DEFAULT_THUMBNAIL = '/images/starboy.jpg';
function extractVideoId(url: string): string | null {
const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
/youtube\.com\/shorts\/([^&\n?#]+)/,
];
for (const pattern of patterns) {
const match = url.match(pattern);
if (match) return match[1];
}
return null;
}
export default function SongPlayerDemo() {
const [inputUrl, setInputUrl] = useState('');
const [error, setError] = useState('');
const [videoId, setVideoId] = useState<string | null>(null);
const [title, setTitle] = useState('No song playing');
const [artist, setArtist] = useState('Insert a YouTube link below');
const [thumbnail, setThumbnail] = useState(DEFAULT_THUMBNAIL);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(100);
const iframeRef = useRef<HTMLIFrameElement>(null);
const currentTimeRef = useRef(0);
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
currentTimeRef.current = currentTime;
}, [currentTime]);
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
if (typeof e.data !== 'string') return;
try {
const data = JSON.parse(e.data);
if (data.event === 'infoDelivery' && data.info) {
if (typeof data.info.currentTime === 'number') {
setCurrentTime(data.info.currentTime);
}
if (typeof data.info.duration === 'number' && data.info.duration > 0) {
setDuration(data.info.duration);
}
if (typeof data.info.playerState === 'number') {
setIsPlaying(data.info.playerState === 1);
}
}
if (data.event === 'onStateChange') {
setIsPlaying(data.info === 1);
if (data.info === 0) {
setCurrentTime(0);
setIsPlaying(false);
}
}
} catch {}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
useEffect(() => {
if (pollingRef.current) clearInterval(pollingRef.current);
if (videoId && isPlaying) {
pollingRef.current = setInterval(() => {
iframeRef.current?.contentWindow?.postMessage(JSON.stringify({ event: 'listening' }), '*');
}, 500);
}
return () => {
if (pollingRef.current) clearInterval(pollingRef.current);
};
}, [videoId, isPlaying]);
const sendCommand = useCallback((func: string, args: unknown[] = []) => {
iframeRef.current?.contentWindow?.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
}, []);
const togglePlay = useCallback(() => {
if (!videoId) return;
if (isPlaying) {
sendCommand('pauseVideo');
setIsPlaying(false);
} else {
sendCommand('playVideo');
setIsPlaying(true);
}
}, [isPlaying, videoId, sendCommand]);
const seekForward = useCallback(() => {
const newTime = currentTimeRef.current + 10;
sendCommand('seekTo', [newTime, true]);
setCurrentTime(newTime);
}, [sendCommand]);
const seekBackward = useCallback(() => {
const newTime = Math.max(0, currentTimeRef.current - 10);
sendCommand('seekTo', [newTime, true]);
setCurrentTime(newTime);
}, [sendCommand]);
const seekTo = useCallback(
(time: number) => {
const newTime = Math.max(0, Math.min(time, duration));
sendCommand('seekTo', [newTime, true]);
setCurrentTime(newTime);
},
[sendCommand, duration],
);
useEffect(() => {
if (videoId) {
sendCommand('setVolume', [volume]);
}
}, [videoId, volume, sendCommand]);
const handleLoad = async () => {
setError('');
const id = extractVideoId(inputUrl.trim());
if (!id) {
setError('Invalid YouTube URL. Try youtube.com/watch?v=... or youtu.be/...');
return;
}
try {
const res = await fetch(
`https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${id}&format=json`,
);
if (!res.ok) throw new Error('Could not fetch video info');
const data = await res.json();
setTitle(data.title || 'Unknown Title');
setArtist(data.author_name || 'Unknown Artist');
setThumbnail(`https://img.youtube.com/vi/${id}/mqdefault.jpg`);
} catch {
setTitle('Unknown Title');
setArtist('Unknown Artist');
setThumbnail(`https://img.youtube.com/vi/${id}/mqdefault.jpg`);
}
setVideoId(id);
setCurrentTime(0);
setDuration(0);
setIsPlaying(true);
setInputUrl('');
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') handleLoad();
};
const iframeUrl = videoId
? `https://www.youtube.com/embed/${videoId}?enablejsapi=1&autoplay=1&origin=${
typeof window !== 'undefined' ? encodeURIComponent(window.location.origin) : ''
}`
: null;
return (
<div className="relative w-full min-h-screen flex flex-col items-center justify-center gap-6">
<SongPlayer
title={title}
artist={artist}
thumbnail={thumbnail}
isPlaying={isPlaying}
videoId={videoId}
currentTime={currentTime}
duration={duration}
volume={volume}
togglePlay={togglePlay}
seekForward={seekForward}
seekBackward={seekBackward}
seekTo={seekTo}
setVolume={setVolume}
/>
<div className="flex flex-col gap-3 w-full max-w-md px-4">
<div className="w-full">
<div className="bg-linear-to-b from-[#282727] to-[#1B1B1B] px-4 py-2 border border-black rounded-lg shadow-[0px_0.75px_0px_0px_rgba(255,252,252,0.3)_inset,0px_1px_5px_0px_rgba(0,0,0,0.75)]">
<input
type="text"
value={inputUrl}
onChange={(e) => {
setInputUrl(e.target.value);
setError('');
}}
onKeyDown={handleKeyDown}
placeholder="Paste a YouTube URL..."
className="w-full text-white placeholder:text-white/30 text-xs outline-none transition-all duration-200"
/>
</div>
</div>
{error && (
<p className="text-red-400 text-xs pl-1">
<span className="font-medium">Error</span>: {error}
</p>
)}
{!videoId && (
<p className="text-white/20 text-xs text-center">
Supports youtube.com/watch · youtu.be · youtube.com/shorts
</p>
)}
</div>
{iframeUrl && (
<iframe
ref={iframeRef}
src={iframeUrl}
className="sr-only pointer-events-none"
allow="autoplay; encrypted-media"
title="YouTube player"
/>
)}
</div>
);
}
Supports youtube.com/watch · youtu.be · youtube.com/shorts