<!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.04</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.04</small></h1>
<audio id="audio-player" crossOrigin="anonymous"></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', () => {
// 全51局のリスト
const stations = [
{ 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: "JPopsuki Radio", url: "http://jpopsuki.fm:8000/stream", metaUrl: "", metaType: "ICY" },
{ 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: "Ghost Anime Radio", url: "http://animeradio.su:8000/;", metaUrl: "", metaType: "ICY" },
{ 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: "Hotmix Radio Japan", url: "http://hotmixradio-japan.ice.infomaniak.ch/hotmixradio-japan-128.mp3", metaUrl: "", metaType: "ICY" },
{ 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: "Ottava (オッターヴァ)", url: "http://rakuten.streamguys1.com/ottava1_b", metaUrl: "", metaType: "ICY" },
{ name: "Tokyo FM World", url: "http://tokyofmworld.leanstream.co/JOAUFM-MP3?args=tunein", metaUrl: "", metaType: "ICY" },
{ name: "Japan-a-Radio", url: "http://audio.misproductions.com/japan48k", metaUrl: "http://audio.misproductions.com/7.html", metaType: "7.html" },
{ name: "J-Club Powerplay HipHop", url: "http://agnes.torontocast.com:8051/;", metaUrl: "http://agnes.torontocast.com:8051/stats?json=1", metaType: "JSON" },
{ name: "WREP", url: "http://139.99.4.27/stream", metaUrl: "", metaType: "ICY" },
{ 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: "Retro PC GAME", url: "http://gyusyabu.ddo.jp:8000/", metaUrl: "http://gyusyabu.ddo.jp:8000/7.html", metaType: "7.html" },
{ name: "No Life Radio", url: "http://radio.nolife-radio.com:9000/stream", metaUrl: "", metaType: "ICY" },
{ name: "J1 Hits", url: "http://jenny.torontocast.com:20000/stream/J1HITS", metaUrl: "http://jenny.torontocast.com:20000/stats?sid=J1HITS&json=1", metaType: "JSON" },
{ name: "J1 Xtra", url: "http://jenny.torontocast.com:8058/xtra", metaUrl: "http://jenny.torontocast.com:8058/stats?json=1", metaType: "JSON" },
{ name: "Nonstop Casiopea", url: "http://hyades.shoutca.st:8551/", metaUrl: "http://hyades.shoutca.st:8551/stats?json=1", metaType: "JSON" },
{ name: "J-Idols Project Radio", url: "http://stream.radio.co/s953f51e42/listen", metaUrl: "", metaType: "ICY" },
{ name: "Anime Plus Radio", url: "http://192.240.102.133:11216/stream", metaUrl: "http://192.240.102.133:11216/stats?json=1", metaType: "JSON" },
{ name: "The Kyoto Connection", url: "https://radio.thekyotoconnection.com/stream.mp3", metaUrl: "", metaType: "ICY" },
{ name: "JPHiP Radio", url: "http://radio.jphip.com:8800/", metaUrl: "http://radio.jphip.com:8800/7.html", metaType: "7.html" },
{ name: "Isekai-Online", url: "https://www.isekai-online.com/radioking/icecast.php", metaUrl: "", metaType: "ICY" },
{ name: "Stereo Anime", url: "http://192.99.150.42:2345/stream", metaUrl: "http://192.99.150.42:2345/stats?json=1", metaType: "JSON" },
{ name: "Anison FM", url: "http://anison.fm:8000/radio", metaUrl: "", metaType: "ICY" },
{ name: "Extreme Anime Radio", url: "http://radio.keiichi.net:8000/ear.mp3", metaUrl: "", metaType: "ICY" },
{ name: "J-Pop Project Radio", url: "http://stream.radio.co/s953f51e42/listen", metaUrl: "", metaType: "ICY" },
{ name: "Radio Anime Nexus", url: "http://nexus.radio:8000/stream", metaUrl: "", metaType: "ICY" },
{ name: "Yggdrasil Radio", url: "http://radio.yggdrasil.me/radio/8000/radio.mp3", metaUrl: "", metaType: "ICY" },
{ name: "JMusicAnime Radio", url: "http://192.99.8.192:3560/stream", metaUrl: "http://192.99.8.192:3560/stats?json=1", metaType: "JSON" },
{ name: "Radio Animati", url: "http://stream.radioanimati.it:8000/radioanimati", metaUrl: "", metaType: "ICY" },
{ name: "Anime Web Radio", url: "http://animewebradio.it:8000/stream.mp3", metaUrl: "", metaType: "ICY" },
{ name: "Kibo.FM", url: "http://stream.kibo.fm/live", metaUrl: "", metaType: "ICY" },
{ name: "Radio-AniNeko", url: "http://radio-anineko.de:8000/live.mp3", metaUrl: "", metaType: "ICY" },
{ 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: "FM千里", url: "http://simul.freebit.net:8310/fmsenri", metaUrl: "", metaType: "ICY" },
{ name: "Radio MOMO", url: "http://musicbird.leanstream.co/JCB079-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB079-MP3", metaType: "JSON" },
{ name: "Radio Hayama", url: "http://199.195.194.140:8073/", metaUrl: "http://199.195.194.140:8073/7.html", metaType: "7.html" },
{ name: "FM-POCO", url: "http://musicbird.leanstream.co/JCB010-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB010-MP3", metaType: "JSON" },
{ name: "富士山GOGOエフエム", url: "http://musicbird.leanstream.co/JCB037-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB037-MP3", metaType: "JSON" },
{ name: "FM Apple Wave", url: "http://musicbird.leanstream.co/JCB004-MP3", metaUrl: "http://musicbird.leanstream.co/status-json.xsl?mount=/JCB004-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.sort((a, b) => a.name.localeCompare(b.name, 'ja'));
stations.forEach((station, index) => {
const option = document.createElement('option');
// インデックスではなく、一意のURLをvalueに設定して堅牢性を高める
option.value = station.url;
option.textContent = station.name;
stationSelect.appendChild(option);
});
// valueをリセットして、placeholderが選択されるようにする
stationSelect.value = "";
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', () => {
if (stationSelect.value === "") {
audioPlayer.src = '';
audioPlayer.pause();
stationNameDisplay.textContent = '放送局が選択されていません';
songTitleDisplay.textContent = '曲情報なし';
if (metadataInterval) clearInterval(metadataInterval);
return;
}
// URLを元に選択された局オブジェクトを検索
const selectedStation = stations.find(s => s.url === stationSelect.value);
if (!selectedStation) return;
stationNameDisplay.textContent = selectedStation.name;
songTitleDisplay.textContent = '...'; // 初期表示
audioPlayer.src = selectedStation.url;
audioPlayer.play().catch(e => console.error("再生エラー:", e));
// 既存のタイマーをクリア
if (metadataInterval) clearInterval(metadataInterval);
// メタデータ取得処理を開始
updateMetadata(selectedStation);
// 定期的に更新(対応している局のみ)
metadataInterval = setInterval(() => updateMetadata(selectedStation), 10000);
});
async function updateMetadata(station) {
// 対応していない形式の場合は、ここで処理を終了
if (station.metaType === 'ICY' || !station.metaUrl) {
songTitleDisplay.textContent = '曲情報 (非対応)';
return;
}
try {
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();
let title = '情報なし';
if (station.metaType === 'JSON') {
const metadata = JSON.parse(data.contents);
if (metadata.songtitle) {
title = metadata.songtitle; // SHOUTcast v2
} else if (metadata.icestats && metadata.icestats.source) {
const source = Array.isArray(metadata.icestats.source) ? metadata.icestats.source[0] : metadata.icestats.source;
if (source && source.title) {
title = source.title; // Icecast
}
}
} else if (station.metaType === '7.html') {
const htmlContent = data.contents;
const bodyContent = htmlContent.match(/<BODY>(.*)<\/BODY>/i);
if (bodyContent && bodyContent[1]) {
const parts = bodyContent[1].split(',');
if (parts.length > 6 && parts[6]) {
title = parts[6];
}
} else {
title = 'メタデータ形式エラー';
}
}
songTitleDisplay.textContent = title;
} catch (error) {
console.error('曲情報の取得に失敗:', error);
songTitleDisplay.textContent = '曲情報 (取得失敗)';
}
}
});
</script>
</body>
</html>
使用変数
args | |
audioPlayer | |
bodyContent | |
charset | |
class | |
content | |
crossOrigin | |
data | |
e | |
for | |
htmlContent | |
id | |
J1HITS&json | |
json | |
lang | |
metadata | |
metadataInterval | |
metaType | |
mount | |
name | |
option | |
parts | |
playPauseButton | |
proxyUrl | |
response | |
s | |
scale | |
selectedStation | |
sid | |
songTitleDisplay | |
source | |
src | |
stationNameDisplay | |
stations | |
stationSelect | |
textContent | |
title | |
updateMetadata -------( Function ) | |
url | |
value | |
width |