<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SHOUTcast Player Ver0.01</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f0f2f5;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.player-container {
background-color: #fff;
padding: 2em;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 500px;
}
h1 {
text-align: center;
color: #1a73e8;
margin-top: 0;
}
.controls {
display: flex;
flex-direction: column;
gap: 1em;
margin-bottom: 1.5em;
}
label {
font-weight: bold;
}
select, button {
padding: 0.8em;
border-radius: 6px;
border: 1px solid #ccc;
font-size: 1em;
width: 100%;
}
button {
background-color: #1a73e8;
color: white;
font-weight: bold;
cursor: pointer;
border: none;
transition: background-color 0.3s;
}
button:hover {
background-color: #155ab6;
}
button.playing {
background-color: #dc3545;
}
button.playing:hover {
background-color: #b02a37;
}
.info {
background-color: #f9f9f9;
padding: 1em;
border-radius: 6px;
border: 1px solid #eee;
min-height: 4em;
}
.info h3 {
margin-top: 0;
font-size: 0.9em;
color: #666;
}
#station-name {
font-weight: bold;
}
#song-title {
font-size: 1.1em;
color: #000;
}
audio {
display: none; /* 標準のプレーヤーは非表示 */
}
</style>
</head>
<body>
<div class="player-container">
<h1>SHOUTcast Player <small>Ver0.01</small></h1>
<audio id="audio-player"></audio>
<div class="controls">
<div>
<label for="station-select">ラジオ局を選択:</label>
<select id="station-select">
<option value="">-- ここから選択 --</option>
</select>
</div>
<button id="play-pause-button">再生</button>
</div>
<div class="info">
<h3>Now Playing:</h3>
<p id="station-name">放送局が選択されていません</p>
<p id="song-title">曲情報なし</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// CSVから取得したラジオ局データ
const stations = [
// データの整形と、再生に問題がありそうなURLの修正を行っています
{ name: "R/a/dio", url: "http://relay0.r-a-d.io/main.mp3", metaUrl: "http://relay1.r-a-d.io/status-json.xsl", metaType: "JSON" },
{ name: "Vocaloid Radio", url: "https://vocaloid.radioca.st/stream", metaUrl: "https://vocaloid.radioca.st/status-json.xsl", metaType: "JSON" },
{ name: "J-Pop Powerplay Kawaii", url: "http://149.56.23.7:20002/stream", metaUrl: "http://149.56.23.7:20002/stats?json=1", metaType: "JSON" },
{ name: "J-Pop Powerplay", url: "http://cabhs30.sonixcast.com:9628/;", metaUrl: "http://cabhs30.sonixcast.com:9628/stats?json=1", metaType: "JSON" },
{ name: "J-Rock Powerplay", url: "http://cabhs30.sonixcast.com:9508/;", metaUrl: "http://cabhs30.sonixcast.com:9508/stats?json=1", metaType: "JSON" },
{ name: "AnimeNfo Radio", url: "http://momori.animenfo.com:8000/", metaUrl: "http://momori.animenfo.com:8000/status-json.xsl", metaType: "JSON" },
{ name: "Japanimradio - Officiel", url: "http://listen.radioking.com/japanimradio-fm3", metaUrl: "http://listen.radioking.com/japanimradio-fm3/stats?json=1", metaType: "JSON" },
{ name: "Asia DREAM Radio - Japan Hits", url: "http://cabhs30.sonixcast.com:9764/", metaUrl: "http://cabhs30.sonixcast.com:9764/stats?json=1", metaType: "JSON" },
{ name: "J-Pop Sakura", url: "http://cabhs31.sonixcast.com:20278/", metaUrl: "http://cabhs31.sonixcast.com:20278/stats?json=1", metaType: "JSON" },
{ name: "Shonan Beach FM", url: "http://47.89.252.38:8000/by_the_sea", metaUrl: "http://47.89.252.38:8000/7.html", metaType: "7.html" },
{ name: "Be Happy!789", url: "http://musicbird.leanstream.co/JCB068-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB068-MP3", metaType: "JSON" },
{ name: "Kittikun Minimal Techno", url: "http://shoutcast.kittikun.jp:11168/", metaUrl: "http://shoutcast.kittikun.jp:11168/stats?json=1", metaType: "JSON" },
{ name: "Nightwave Plaza", url: "http://plaza.one/ogg", metaUrl: "http://plaza.one/status-json.xsl", metaType: "JSON" },
{ name: "Nonstop Casiopea", url: "http://hyades.shoutca.st:8551/", metaUrl: "http://hyades.shoutca.st:8551/stats?json=1", metaType: "JSON" },
{ name: "Stereo Anime", url: "http://192.99.150.42:2345/stream", metaUrl: "http://192.99.150.42:2345/stats?json=1", metaType: "JSON" },
{ name: "JMusicAnime Radio", url: "http://192.99.8.192:3560/stream", metaUrl: "http://192.99.8.192:3560/stats?json=1", metaType: "JSON" },
{ name: "鎌倉エフエム", url: "http://musicbird.leanstream.co/JCB016-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB016-MP3", metaType: "JSON" },
{ name: "FMちゅーピー", url: "http://musicbird.leanstream.co/JCB082-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB082-MP3", metaType: "JSON" },
{ name: "FM Edogawa", url: "http://musicbird.leanstream.co/JCB033-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB033-MP3", metaType: "JSON" },
{ name: "Radio MOMO", url: "http://musicbird.leanstream.co/JCB079-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB079-MP3", metaType: "JSON" }
];
const audioPlayer = document.getElementById('audio-player');
const stationSelect = document.getElementById('station-select');
const playPauseButton = document.getElementById('play-pause-button');
const stationNameDisplay = document.getElementById('station-name');
const songTitleDisplay = document.getElementById('song-title');
let metadataInterval;
// ドロップダウンリストを生成
stations.forEach((station, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = station.name;
stationSelect.appendChild(option);
});
// 再生・停止ボタンの制御
playPauseButton.addEventListener('click', () => {
if (audioPlayer.paused) {
if (audioPlayer.src) {
audioPlayer.play().catch(e => {
console.error("再生エラー:", e);
alert("この放送局の再生に失敗しました。URLが正しくないか、サーバーがダウンしている可能性があります。");
});
} else {
alert("先にラジオ局を選択してください。");
}
} else {
audioPlayer.pause();
}
});
// オーディオの状態に応じてボタンの表示を更新
audioPlayer.addEventListener('play', () => {
playPauseButton.textContent = '停止';
playPauseButton.classList.add('playing');
});
audioPlayer.addEventListener('pause', () => {
playPauseButton.textContent = '再生';
playPauseButton.classList.remove('playing');
});
// ラジオ局が選択されたときの処理
stationSelect.addEventListener('change', () => {
const selectedIndex = stationSelect.value;
if (!selectedIndex) {
audioPlayer.src = '';
stationNameDisplay.textContent = '放送局が選択されていません';
songTitleDisplay.textContent = '曲情報なし';
if (metadataInterval) clearInterval(metadataInterval);
return;
}
const selectedStation = stations[selectedIndex];
stationNameDisplay.textContent = selectedStation.name;
songTitleDisplay.textContent = '曲情報を取得中...';
audioPlayer.src = selectedStation.url;
// 再生開始と曲情報取得
audioPlayer.play().catch(e => console.error("再生エラー:", e));
updateMetadata(selectedStation);
if (metadataInterval) clearInterval(metadataInterval);
metadataInterval = setInterval(() => updateMetadata(selectedStation), 5000);
});
// 曲情報の取得と表示
async function updateMetadata(station) {
// JSON形式のメタデータのみ対応 (Ver0.01)
if (station.metaType !== 'JSON' || !station.metaUrl) {
songTitleDisplay.textContent = '曲情報 (非対応)';
return;
}
try {
// CORSプロキシを経由させることで、多くのCORSエラーを回避できます
// ここでは allorigins.win という公開プロキシを利用しています
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(station.metaUrl)}`;
const response = await fetch(proxyUrl);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
const metadata = JSON.parse(data.contents);
let title = '情報なし';
if (metadata.songtitle) {
title = metadata.songtitle; // SHOUTcast v2
} else if (metadata.icestats && metadata.icestats.source && metadata.icestats.source.title) {
title = metadata.icestats.source.title; // Icecast
}
songTitleDisplay.textContent = title;
} catch (error) {
console.error('曲情報の取得に失敗:', error);
songTitleDisplay.textContent = '曲情報 (取得失敗/CORSエラー)';
}
}
});
</script>
</body>
</html>
使用変数
audioPlayer | |
charset | |
class | |
content | |
data | |
e | |
for | |
id | |
json | |
lang | |
metadata | |
metadataInterval | |
mount | |
name | |
option | |
playPauseButton | |
proxyUrl | |
response | |
scale | |
selectedIndex | |
selectedStation | |
songTitleDisplay | |
src | |
stationNameDisplay | |
stations | |
stationSelect | |
textContent | |
title | |
updateMetadata -------( Function ) | |
url | |
value | |
width |