anycoder-1a417a72 / index.html
Multimedix's picture
Upload folder using huggingface_hub
26f9e28 verified
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DE Radio Stream | Ultimate Player</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- FontAwesome für Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-color: #0f172a;
--card-bg: rgba(30, 41, 59, 0.7);
--accent-color: #3b82f6;
--accent-glow: rgba(59, 130, 246, 0.5);
--text-main: #f8fafc;
--text-secondary: #94a3b8;
--success: #10b981;
--danger: #ef4444;
--glass-border: 1px solid rgba(255, 255, 255, 0.1);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Outfit', sans-serif;
}
body {
background-color: var(--bg-color);
background-image:
radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.15) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%);
color: var(--text-main);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* --- Header --- */
header {
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(10px);
position: sticky;
top: 0;
z-index: 100;
border-bottom: var(--glass-border);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand i {
font-size: 1.8rem;
color: var(--accent-color);
filter: drop-shadow(0 0 10px var(--accent-glow));
}
.brand h1 {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.5px;
}
.anycoder-link {
font-size: 0.9rem;
color: var(--text-secondary);
text-decoration: none;
background: rgba(255, 255, 255, 0.05);
padding: 8px 16px;
border-radius: 20px;
transition: var(--transition);
border: var(--glass-border);
}
.anycoder-link:hover {
color: var(--text-main);
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
}
/* --- Main Layout --- */
main {
flex: 1;
display: grid;
grid-template-columns: 1fr 400px;
gap: 2rem;
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
width: 100%;
}
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
padding: 1rem;
}
}
/* --- Player Section --- */
.player-section {
display: flex;
flex-direction: column;
gap: 2rem;
}
.visualizer-card {
background: var(--card-bg);
border-radius: 24px;
padding: 2rem;
border: var(--glass-border);
backdrop-filter: blur(20px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
min-height: 400px;
box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.5);
}
canvas#audioVisualizer {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
opacity: 0.6;
}
.now-playing-info {
z-index: 2;
text-align: center;
margin-bottom: 2rem;
}
.station-logo {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
margin-bottom: 1.5rem;
border: 4px solid rgba(255,255,255,0.1);
animation: rotateDisk 10s linear infinite;
animation-play-state: paused;
}
.station-logo.playing {
animation-play-state: running;
border-color: var(--accent-color);
}
@keyframes rotateDisk {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.station-name {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
.station-meta {
color: var(--text-secondary);
font-size: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.live-badge {
background: var(--danger);
color: white;
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 4px;
font-weight: 700;
text-transform: uppercase;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.controls {
z-index: 2;
display: flex;
align-items: center;
gap: 2rem;
background: rgba(0,0,0,0.2);
padding: 1rem 2rem;
border-radius: 50px;
backdrop-filter: blur(10px);
}
.btn-control {
background: none;
border: none;
color: var(--text-main);
font-size: 1.5rem;
cursor: pointer;
transition: var(--transition);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.btn-control:hover {
background: rgba(255,255,255,0.1);
color: var(--accent-color);
}
.btn-play {
background: var(--accent-color);
color: white;
font-size: 1.8rem;
width: 70px;
height: 70px;
box-shadow: 0 0 20px var(--accent-glow);
}
.btn-play:hover {
transform: scale(1.1);
background: #2563eb;
color: white;
}
.volume-container {
display: flex;
align-items: center;
gap: 10px;
z-index: 2;
margin-top: 1rem;
width: 100%;
max-width: 300px;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--text-main);
cursor: pointer;
margin-top: -6px;
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255,255,255,0.2);
border-radius: 2px;
}
/* --- Playlist Section --- */
.playlist-section {
background: var(--card-bg);
border-radius: 24px;
border: var(--glass-border);
backdrop-filter: blur(20px);
display: flex;
flex-direction: column;
overflow: hidden;
height: calc(100vh - 140px);
position: sticky;
top: 100px;
}
@media (max-width: 900px) {
.playlist-section {
height: 600px;
position: static;
}
}
.playlist-header {
padding: 1.5rem;
border-bottom: var(--glass-border);
}
.search-box {
position: relative;
width: 100%;
}
.search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
}
.search-box input {
width: 100%;
padding: 12px 12px 12px 45px;
border-radius: 12px;
border: var(--glass-border);
background: rgba(0,0,0,0.2);
color: var(--text-main);
font-size: 1rem;
outline: none;
transition: var(--transition);
}
.search-box input:focus {
border-color: var(--accent-color);
background: rgba(0,0,0,0.4);
}
.station-list {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
/* Scrollbar Styling */
.station-list::-webkit-scrollbar {
width: 6px;
}
.station-list::-webkit-scrollbar-track {
background: transparent;
}
.station-list::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.2);
border-radius: 3px;
}
.station-item {
display: flex;
align-items: center;
padding: 1rem;
border-radius: 12px;
cursor: pointer;
transition: var(--transition);
margin-bottom: 0.5rem;
border: 1px solid transparent;
}
.station-item:hover {
background: rgba(255,255,255,0.05);
}
.station-item.active {
background: rgba(59, 130, 246, 0.15);
border-color: var(--accent-color);
}
.station-item img {
width: 50px;
height: 50px;
border-radius: 10px;
object-fit: cover;
margin-right: 1rem;
}
.station-details {
flex: 1;
}
.station-details h4 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 4px;
}
.station-details span {
font-size: 0.8rem;
color: var(--text-secondary);
background: rgba(255,255,255,0.05);
padding: 2px 6px;
border-radius: 4px;
}
.play-indicator {
opacity: 0;
color: var(--accent-color);
transition: var(--transition);
}
.station-item.active .play-indicator {
opacity: 1;
}
.fav-btn {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 8px;
transition: var(--transition);
}
.fav-btn:hover, .fav-btn.active {
color: #ec4899;
}
/* --- Footer --- */
footer {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
/* Status Toast */
.status-toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: var(--card-bg);
border: var(--glass-border);
padding: 12px 24px;
border-radius: 50px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 1000;
backdrop-filter: blur(10px);
}
.status-toast.show {
transform: translateX(-50%) translateY(0);
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text-secondary);
}
.status-toast.success .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
.status-toast.error .status-dot { background: var(--danger); box-shadow: 0 0 10px var(--danger); }
</style>
</head>
<body>
<header>
<div class="brand">
<i class="fa-solid fa-radio"></i>
<h1>DE Radio<span style="color:var(--accent-color)">.Stream</span></h1>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em; margin-left:5px;"></i>
</a>
</header>
<main>
<!-- Player Section -->
<section class="player-section">
<div class="visualizer-card">
<canvas id="audioVisualizer"></canvas>
<div class="now-playing-info">
<img id="currentLogo" src="https://picsum.photos/seed/radio/300/300" alt="Station Logo" class="station-logo">
<h2 id="currentName" class="station-name">Wähle einen Sender</h2>
<div class="station-meta">
<span id="currentGenre" class="genre">Pop</span>
<span class="live-badge" id="liveBadge" style="display:none;">LIVE</span>
</div>
</div>
<div class="controls">
<button class="btn-control" id="prevBtn" title="Vorheriger">
<i class="fa-solid fa-backward-step"></i>
</button>
<button class="btn-control btn-play" id="playBtn" title="Play/Pause">
<i class="fa-solid fa-play" id="playIcon"></i>
</button>
<button class="btn-control" id="nextBtn" title="Nächster">
<i class="fa-solid fa-forward-step"></i>
</button>
</div>
<div class="volume-container">
<i class="fa-solid fa-volume-low" style="color: var(--text-secondary)"></i>
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="0.8">
<i class="fa-solid fa-volume-high" style="color: var(--text-secondary)"></i>
</div>
</div>
</section>
<!-- Playlist Section -->
<section class="playlist-section">
<div class="playlist-header">
<div class="search-box">
<i class="fa-solid fa-magnifying-glass"></i>
<input type="text" id="searchInput" placeholder="Sender suchen...">
</div>
</div>
<ul class="station-list" id="stationList">
<!-- Stations werden hier per JS eingefügt -->
</ul>
</section>
</main>
<footer>
&copy; 2023 DE Radio Stream. Designed for modern browsers.
</footer>
<!-- Toast Notification -->
<div id="toast" class="status-toast">
<div class="status-dot"></div>
<span id="toastMessage">Nachricht</span>
</div>
<script>
// --- Radio Station Data ---
// Verwendet öffentliche Stream-URLs. Einige erfordern CORS-Header, die wir im Audio-Objekt setzen.
const stations = [
{
id: 1,
name: "Antenne Bayern",
genre: "Pop & Hits",
url: "https://mp3channels.webradio.antenne.de/antenne-bayern",
imgSeed: "antenne"
},
{
id: 2,
name: "1LIVE",
genre: "Pop & Dance",
url: "https://wdr-1live-live.icecastssl.wdr.de/wdr/1live/live/mp3/128/stream.mp3",
imgSeed: "1live"
},
{
id: 3,
name: "Bayern 3",
genre: "Pop & Rock",
url: "https://br-br3-live.cast.addradio.de/br/br3/live/mp3/128/stream.mp3",
imgSeed: "bayern3"
},
{
id: 4,
name: "SWR3",
genre: "Pop",
url: "https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3",
imgSeed: "swr3"
},
{
id: 5,
name: "Deutschlandfunk",
genre: "Nachrichten & Info",
url: "https://stmdsl.dlf.de/dlf/01/128/mp3/stream.mp3",
imgSeed: "dlf"
},
{
id: 6,
name: "Bremen Vier",
genre: "Pop & Alternative",
url: "https://rb-bremenvier-live.cast.addradio.de/rb/bremenvier/live/mp3/128/stream.mp3",
imgSeed: "bremen4"
},
{
id: 7,
name: "NDR 2",
genre: "Pop",
url: "https://ndr-ndr2-niedersachsen.cast.addradio.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3",
imgSeed: "ndr2"
},
{
id: 8,
name: "YOU FM",
genre: "Dance & Pop",
url: "https://hr-youfm-live.cast.addradio.de/hr/youfm/live/mp3/128/stream.mp3",
imgSeed: "youfm"
},
{
id: 9,
name: "Radio Paloma",
genre: "Schlager",
url: "https://stream.radio-paloma.de/radio-paloma/mp3-192",
imgSeed: "paloma"
},
{
id: 10,
name: "JAM FM",
genre: "Black & Urban",
url: "https://stream.jam.fm/jamfm-live/mp3-128",
imgSeed: "jamfm"
},
{
id: 11,
name: "Klassik Radio",
genre: "Klassik",
url: "https://stream.klassikradio.de/live/mp3-192",
imgSeed: "klassik"
},
{
id: 12,
name: "FFN",
genre: "Pop & Rock",
url: "https://ffn-live.cast.addradio.de/ffn/live/mp3/128/stream.mp3",
imgSeed: "ffn"
}
];
// --- State Management ---
let currentStationIndex = -1;
let isPlaying = false;
let audio = new Audio();
audio.crossOrigin = "anonymous"; // Wichtig für Visualizer
// Visualizer Context
let audioContext;
let analyser;
let dataArray;
let source;
let isAudioContextSetup = false;
// --- DOM Elements ---
const playBtn = document.getElementById('playBtn');
const playIcon = document.getElementById('playIcon');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const volumeSlider = document.getElementById('volumeSlider');
const stationListEl = document.getElementById('stationList');
const searchInput = document.getElementById('searchInput');
// Player UI Elements
const currentLogo = document.getElementById('currentLogo');
const currentName = document.getElementById('currentName');
const currentGenre = document.getElementById('currentGenre');
const liveBadge = document.getElementById('liveBadge');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
// Canvas
const canvas = document.getElementById('audioVisualizer');
const canvasCtx = canvas.getContext('2d');
// --- Initialization ---
function init() {
renderList(stations);
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Volume init
audio.volume = volumeSlider.value;
}
// --- Playlist Rendering ---
function renderList(data) {
stationListEl.innerHTML = '';
data.forEach((station, index) => {
// Finde den echten Index im ursprünglichen Array
const originalIndex = stations.findIndex(s => s.id === station.id);
const li = document.createElement('li');
li.className = `station-item ${originalIndex === currentStationIndex ? 'active' : ''}`;
li.onclick = () => loadStation(originalIndex);
li.innerHTML = `
<img src="https://picsum.photos/seed/${station.imgSeed}/100/100" alt="${station.name}">
<div class="station-details">
<h4>${station.name}</h4>
<span>${station.genre}</span>
</div>
<div class="play-indicator">
<i class="fa-solid fa-chart-simple"></i>
</div>
`;
stationListEl.appendChild(li);
});
}
// --- Audio Logic ---
function loadStation(index) {
// Wenn wir auf den aktiven Sender klicken, nur Play/Pause toggeln
if (index === currentStationIndex) {
togglePlay();
return;
}
currentStationIndex = index;
const station = stations[index];
// UI Update
currentName.innerText = station.name;
currentGenre.innerText = station.genre;
currentLogo.src = `https://picsum.photos/seed/${station.imgSeed}/300/300`;
liveBadge.style.display = 'none';
// Audio Source
audio.src = station.url;
audio.load();
playAudio();
renderList(stations); // Update active state in list
showToast(`Lade: ${station.name}`, 'neutral');
}
function togglePlay() {
if (currentStationIndex === -1) {
loadStation(0); // Starte ersten Sender wenn noch keiner gewählt
return;
}
if (isPlaying) {
pauseAudio();
} else {
playAudio();
}
}
function playAudio() {
// AudioContext muss nach User-Geste gestartet werden
setupAudioContext();
const playPromise = audio.play();
if (playPromise !== undefined) {
playPromise.then(_ => {
isPlaying = true;
updatePlayButton();
liveBadge.style.display = 'inline-block';
animateVisualizer();
showToast('Live Stream gestartet', 'success');
})
.catch(error => {
console.error("Play Error:", error);
showToast('Fehler beim Starten des Streams', 'error');
isPlaying = false;
updatePlayButton();
});
}
}
function pauseAudio() {
audio.pause();
isPlaying = false;
updatePlayButton();
liveBadge.style.display = 'none';
}
function updatePlayButton() {
if (isPlaying) {
playIcon.classList.remove('fa-play');
playIcon.classList.add('fa-pause');
currentLogo.classList.add('playing');
} else {
playIcon.classList.remove('fa-pause');
playIcon.classList.add('fa-play');
currentLogo.classList.remove('playing');
}
}
// --- Web Audio API Visualizer ---
function setupAudioContext() {
if (isAudioContextSetup) return;
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
source = audioContext.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
isAudioContextSetup = true;
} catch (e) {
console.warn("Audio Context setup failed (CORS or browser restriction)", e);
}
}
function resizeCanvas() {
canvas.width = canvas.parentElement.offsetWidth;
canvas.height = canvas.parentElement.offsetHeight;
}
function animateVisualizer() {
if (!isPlaying) {
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
return;
}
requestAnimationFrame(animateVisualizer);
if(analyser) {
analyser.getByteFrequencyData(dataArray);
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
const barWidth = (canvas.width / dataArray.length) * 2.5;
let barHeight;
let x = 0;
for (let i = 0; i < dataArray.length; i++) {
barHeight = dataArray[i] / 2;
// Gradient Color
const gradient = canvasCtx.createLinearGradient(0, canvas.height, 0, 0);
gradient.addColorStop(0, '#3b82f6');
gradient.addColorStop(1, '#ec4899');
canvasCtx.fillStyle = gradient;
// Draw rounded bars from bottom
canvasCtx.beginPath();
canvasCtx.roundRect(x, canvas.height - barHeight, barWidth, barHeight, 5);
canvasCtx.fill();
x += barWidth + 2;
}
}
}
// --- Event Listeners ---
playBtn.addEventListener('click', togglePlay);
prevBtn.addEventListener('click', () => {
let newIndex = currentStationIndex - 1;
if (newIndex < 0) newIndex = stations.length - 1;
loadStation(newIndex);
});
nextBtn.addEventListener('click', () => {
let newIndex = currentStationIndex + 1;
if (newIndex >= stations.length) newIndex = 0;
loadStation(newIndex);
});
volumeSlider.addEventListener('input', (e) => {
audio.volume = e.target.value;
});
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const filtered = stations.filter(s =>
s.name.toLowerCase().includes(query) ||
s.genre.toLowerCase().includes(query)
);
renderList(filtered);
});
// Audio Error Handling
audio.addEventListener('error', (e) => {
console.error("Stream Error", e);
isPlaying = false;
updatePlayButton();
showToast('Stream nicht verfügbar', 'error');
});
// --- Toast Helper ---
function showToast(msg, type = 'neutral') {
toastMessage.innerText = msg;
toast.className = 'status-toast show';
if(type === 'success') toast.classList.add('success');
if(type === 'error') toast.classList.add('error');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Start App
init();
</script>
</body>
</html>