个人FM电台源代码
复制源码到文档保存为html格式文档 双击打开就可以了
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>辣条FM电台</title>
<link rel="stylesheet" href=".https://cdn.bootcdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", "Microsoft Yahei", sans-serif;
}
body {
background-color: #f9fafb;
padding: 20px;
color: #334155;
line-height: 1.6;
}
.container {
max-width: 900px;
margin: 0 auto;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 30px;
overflow: hidden;
}
h1 {
text-align: center;
color: #1e293b;
margin-bottom: 24px;
font-size: 24px;
font-weight: 600;
}
/* 标签与搜索区域 */
.tab-search-wrap {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
position: relative;
}
.list-tabs {
display: flex;
gap: 4px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 8px;
flex: 1;
}
.tab-btn {
padding: 6px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.tab-btn::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background-color: #0ea5e9;
transition: width 0.2s ease;
}
.tab-btn.active {
color: #0ea5e9;
background-color: #e0f2fe;
}
.tab-btn.active::after {
width: 100%;
}
.search-trigger {
width: 36px;
height: 36px;
border: none;
border-radius: 6px;
background-color: #f8fafc;
color: #64748b;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.search-trigger:hover {
background-color: #e2e8f0;
color: #0ea5e9;
transform: scale(1.05);
}
.search-trigger:active {
transform: scale(0.95);
}
.search-box {
position: absolute;
top: 42px;
left: 0;
right: 0;
background-color: #fff;
padding: 8px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10;
display: none;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.search-box.show {
display: block;
}
.search-input {
width: 100%;
padding: 12px 16px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: all 0.2s ease;
background-color: #f8fafc;
}
.search-input::placeholder {
color: #94a3b8;
}
/* 播放器区域 - 固定定位容器 */
.player-fixed-wrap {
position: sticky;
top: 20px;
z-index: 5;
margin-bottom: 20px;
background-color: #fff;
padding: 10px;
border-radius: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: all 0.3s ease;
}
.player-fixed-wrap:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.player {
padding: 20px;
background-color: #f8fafc;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 16px;
transition: all 0.3s ease;
}
.status {
color: #64748b;
font-size: 14px;
text-align: center;
line-height: 1.5;
transition: all 0.3s ease;
min-height: 21px;
}
.status.loading {
color: #0ea5e9;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.control-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.playback-controls {
display: flex;
align-items: center;
gap: 8px;
}
.control-btn {
width: 44px;
height: 44px;
border: none;
border-radius: 8px;
background-color: #38bdf8;
color: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transform: scale(1);
}
.control-btn:hover {
background-color: #0ea5e9;
transform: translateY(-1px) scale(1.05);
box-shadow: 0 4px 12px rgba(56, 189, 248, 0.3);
}
.control-btn:active {
transform: translateY(0) scale(0.95);
}
.control-btn.active-mode {
background-color: #22c55e;
}
.control-btn.collect-btn.active {
background-color: #ef4444;
animation: heartBeat 0.6s ease;
}
@keyframes heartBeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.1); }
28% { transform: scale(1); }
42% { transform: scale(1.1); }
70% { transform: scale(1); }
}
.volume-control {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
max-width: 220px;
}
.volume-icon {
font-size: 18px;
color: #64748b;
width: 20px;
text-align: center;
transition: color 0.2s ease;
cursor: pointer;
}
.volume-icon:hover {
color: #0ea5e9;
}
.volume-slider {
width: 100%;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: #e2e8f0;
border-radius: 3px;
outline: none;
transition: all 0.2s ease;
}
.volume-slider:hover {
background: #cbd5e1;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #38bdf8;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.volume-slider::-webkit-slider-thumb:hover {
background: #0ea5e9;
transform: scale(1.2);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
}
.volume-slider::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #38bdf8;
cursor: pointer;
border: none;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.volume-slider::-moz-range-thumb:hover {
background: #0ea5e9;
transform: scale(1.2);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
}
audio {
width: 100%;
height: 40px;
border: none;
border-radius: 8px;
background-color: #fff;
padding: 0 8px;
transition: all 0.3s ease;
}
audio:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 列表区域 */
.radio-lists {
border-radius: 8px;
overflow: hidden;
border: 1px solid #e2e8f0;
max-height: calc(100vh - 320px);
overflow-y: auto;
transition: all 0.3s ease;
}
.radio-lists:hover {
border-color: #cbd5e1;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.radio-lists::-webkit-scrollbar {
width: 6px;
}
.radio-lists::-webkit-scrollbar-track {
background: #f1f5f9;
}
.radio-lists::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.radio-lists::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
.radio-list {
list-style: none;
display: none;
}
.radio-list.active {
display: block;
}
.radio-item {
padding: 14px 16px;
border-bottom: 1px solid #f1f5f9;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
overflow: hidden;
}
.radio-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background-color: transparent;
transition: background-color 0.2s ease;
}
.radio-item:last-child {
border-bottom: none;
}
.radio-item:hover {
background-color: #f8fafc;
transform: translateX(4px);
}
.radio-item:hover::before {
background-color: #0ea5e9;
}
.radio-item.active {
background-color: #e0f2fe;
color: #0ea5e9;
font-weight: 500;
}
.radio-item.active::before {
background-color: #0ea5e9;
}
.radio-item .collect-icon {
font-size: 16px;
color: #cbd5e1;
cursor: pointer;
transition: all 0.2s ease;
width: 20px;
text-align: center;
transform: scale(1);
}
.radio-item .collect-icon:hover {
color: #ef4444;
transform: scale(1.2);
}
.radio-item .collect-icon.active {
color: #ef4444;
animation: heartBeat 0.6s ease;
}
.empty-tip {
padding: 40px 20px;
text-align: center;
color: #94a3b8;
font-size: 14px;
line-height: 1.6;
transition: all 0.3s ease;
}
.empty-tip i {
font-size: 32px;
margin-bottom: 12px;
display: block;
color: #cbd5e1;
transition: all 0.3s ease;
}
.empty-tip:hover i {
transform: scale(1.1);
color: #94a3b8;
}
/* 快捷键提示 */
.shortcut-tip {
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(30, 41, 59, 0.9);
color: white;
padding: 12px 16px;
border-radius: 8px;
font-size: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
opacity: 0.7;
transition: all 0.3s ease;
z-index: 100;
}
.shortcut-tip:hover {
opacity: 1;
transform: translateY(-4px);
}
.shortcut-tip h4 {
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
}
.shortcut-tip ul {
list-style: none;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 12px;
}
.shortcut-tip li {
display: flex;
align-items: center;
gap: 6px;
}
.shortcut-key {
background-color: rgba(255, 255, 255, 0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 11px;
}
/* 加载动画 */
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(56, 189, 248, 0.3);
border-radius: 50%;
border-top-color: #38bdf8;
animation: spin 0.8s ease-in-out infinite;
margin-right: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 管理界面样式 */
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e2e8f0;
}
.admin-header h2 {
font-size: 20px;
color: #1e293b;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
background-color: #38bdf8;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
gap: 6px;
text-decoration: none;
}
.btn:hover {
background-color: #0ea5e9;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(56, 189, 248, 0.3);
}
.btn:active {
transform: translateY(0);
}
.btn-secondary {
background-color: #94a3b8;
}
.btn-secondary:hover {
background-color: #64748b;
}
.form-group {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #334155;
font-size: 14px;
}
.form-control {
width: 100%;
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s ease;
background-color: #f8fafc;
}
.form-control:focus {
outline: none;
border-color: #38bdf8;
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr 120px;
gap: 16px;
align-items: end;
}
.add-radio-form {
margin-bottom: 32px;
padding: 20px;
background-color: #f8fafc;
border-radius: 8px;
}
.add-radio-form h3 {
margin-bottom: 16px;
color: #1e293b;
font-size: 16px;
}
.radio-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.radio-table th,
.radio-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.radio-table th {
background-color: #f1f5f9;
font-weight: 600;
color: #334155;
font-size: 14px;
}
.radio-table tr:hover {
background-color: #f8fafc;
}
.action-buttons {
display: flex;
gap: 8px;
}
.delete-btn {
background-color: #fecaca;
color: #dc2626;
}
.delete-btn:hover {
background-color: #fef2f2;
color: #b91c1c;
box-shadow: none;
}
.message {
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 16px;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.message.success {
background-color: #dcfce7;
color: #166534;
border: 1px solid #bbf7d0;
}
.message.error {
background-color: #fee2e2;
color: #b91c1c;
border: 1px solid #fecaca;
}
#messageContainer {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
max-width: 300px;
}
.login-form {
max-width: 400px;
margin: 0 auto;
padding: 24px;
background-color: #f8fafc;
border-radius: 8px;
}
.login-form h2 {
text-align: center;
margin-bottom: 24px;
color: #1e293b;
}
/* 模式切换按钮 */
.mode-switch {
position: fixed;
top: 20px;
right: 20px;
z-index: 100;
}
/* 隐藏区域 */
.hidden {
display: none !important;
}
/* 响应式设计 */
@media (max-width: 768px) {
body {
padding: 10px;
}
.container {
padding: 20px;
}
.control-bar {
flex-direction: column;
align-items: stretch;
}
.playback-controls {
justify-content: center;
}
.volume-control {
max-width: 100%;
}
.radio-lists {
max-height: calc(100vh - 380px);
}
.shortcut-tip {
display: none;
}
.form-row {
grid-template-columns: 1fr;
}
.radio-table {
font-size: 12px;
}
.radio-table th,
.radio-table td {
padding: 8px 12px;
}
.action-buttons {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="mode-switch">
<button class="btn btn-secondary" id="switchModeBtn">
<i class="fas fa-cog"></i> 切换到管理模式
</button>
</div>
<div class="container">
<h1>辣条FM电台</h1>
<!-- 前台播放区域 -->
<div id="playerArea">
<!-- 固定播放区域 -->
<div class="player-fixed-wrap">
<div class="player">
<div class="status" id="status">请选择下方电台开始播放</div>
<div class="control-bar">
<div class="playback-controls">
<button class="control-btn" id="playPauseBtn" title="播放/暂停 (空格键)">
<i class="fas fa-play"></i>
</button>
<button class="control-btn" id="nextBtn" title="下一曲 (→)">
<i class="fas fa-step-forward"></i>
</button>
<button class="control-btn" id="sequenceBtn" title="顺序播放">
<i class="fas fa-list-ol"></i>
</button>
<button class="control-btn" id="randomBtn" title="随机播放">
<i class="fas fa-shuffle"></i>
</button>
<button class="control-btn collect-btn" id="collectBtn" title="收藏当前电台 (F)">
<i class="fas fa-heart"></i>
</button>
</div>
<div class="volume-control">
<i class="fas fa-volume-up volume-icon" id="volumeIcon" title="静音/取消静音 (M)"></i>
<input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.05" value="1">
</div>
</div>
<audio id="audioPlayer" controls></audio>
</div>
</div>
<!-- 标签与搜索区域 -->
<div class="tab-search-wrap">
<div class="list-tabs">
<button class="tab-btn active" data-tab="all">全部电台</button>
<button class="tab-btn" data-tab="collect">我的收藏</button>
</div>
<button class="search-trigger" id="searchTrigger" title="搜索电台 (S)">
<i class="fas fa-search"></i>
</button>
<div class="search-box" id="searchBox">
<input type="text" class="search-input" id="searchInput" placeholder="输入电台名称搜索...">
</div>
</div>
<!-- 可滚动列表区域 -->
<div class="radio-lists">
<ul class="radio-list active" id="allRadioList"></ul>
<ul class="radio-list" id="collectRadioList"></ul>
</div>
</div>
<!-- 管理区域 -->
<div id="adminArea" class="hidden">
<div class="admin-header">
<h2>电台管理</h2>
<button class="btn btn-secondary" id="backToPlayer">
<i class="fas fa-home"></i>
返回前台
</button>
</div>
<!-- 消息提示 -->
<div id="messageContainer"></div>
<!-- 添加电台表单 -->
<div class="add-radio-form">
<h3>添加新电台</h3>
<form id="addRadioForm" class="form-row">
<div class="form-group">
<label for="radioTitle">电台名称</label>
<input type="text" class="form-control" id="radioTitle" placeholder="请输入电台名称" required>
</div>
<div class="form-group">
<label for="radioUrl">电台URL</label>
<input type="url" class="form-control" id="radioUrl" placeholder="请输入电台流地址" required>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i>
添加
</button>
</form>
</div>
<!-- 电台列表 -->
<table class="radio-table">
<thead>
<tr>
<th>ID</th>
<th>电台名称</th>
<th>URL地址</th>
<th>操作</th>
</tr>
</thead>
<tbody id="radioTableBody"></tbody>
</table>
</div>
<!-- 管理员登录 -->
<div id="loginForm" class="login-form hidden">
<h2>管理员登录</h2>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" placeholder="请输入管理员密码">
</div>
<button class="btn btn-primary" style="width: 100%;" id="loginBtn">登录</button>
<div class="message error" id="loginError" style="display: none; margin-top: 16px;">
<i class="fas fa-exclamation-circle"></i>
<span>密码错误,请重试</span>
</div>
</div>
</div>
<!-- 快捷键提示 -->
<div class="shortcut-tip">
<h4>快捷键</h4>
<ul>
<li><span class="shortcut-key">空格</span> 播放/暂停</li>
<li><span class="shortcut-key">→</span> 下一曲</li>
<li><span class="shortcut-key">F</span> 收藏/取消</li>
<li><span class="shortcut-key">M</span> 静音/取消</li>
<li><span class="shortcut-key">S</span> 搜索</li>
<li><span class="shortcut-key">ESC</span> 关闭搜索</li>
</ul>
</div>
<script>
// 采用模块化设计,封装所有功能
(function() {
// 管理员密码(实际应用中应使用更安全的方式存储和验证)
const ADMIN_PASSWORD = 'admin123'; // 可以修改为你需要的密码
let isAdminLoggedIn = false;
// 电台数据 - 优先从localStorage读取,没有则使用默认数据
let radioData = JSON.parse(localStorage.getItem('radioData')) || [
{"title": "怀集音乐之声", "url": "http://lhttp.qingting.fm/live/4804/64k.mp3", "isCollected": false},
{"title": "两广之声音乐台", "url": "http://lhttp.qingting.fm/live/20500149/64k.mp3", "isCollected": false},
{"title": "清晨音乐台", "url": "http://lhttp.qingting.fm/live/4915/64k.mp3", "isCollected": false},
{"title": "河北音乐广播", "url": "http://lhttp.qingting.fm/live/1649/64k.mp3", "isCollected": false},
{"title": "上海流行音乐LoveRadio", "url": "http://lhttp.qingting.fm/live/273/64k.mp3", "isCollected": false},
{"title": "江苏经典流行音乐", "url": "http://lhttp.qingting.fm/live/4938/64k.mp3", "isCollected": false},
{"title": "广东音乐之声", "url": "http://lhttp.qingting.fm/live/1260/64k.mp3", "isCollected": false},
{"title": "上海动感101", "url": "http://lhttp.qingting.fm/live/274/64k.mp3", "isCollected": false},
{"title": "怀旧好声音", "url": "http://lhttp.qingting.fm/live/1223/64k.mp3", "isCollected": false},
{"title": "哈尔滨音乐广播", "url": "http://lhttp.qingting.fm/live/839/64k.mp3", "isCollected": false},
{"title": "苏州音乐广播", "url": "http://lhttp.qingting.fm/live/2803/64k.mp3", "isCollected": false},
{"title": "959年代音乐怀旧好声音", "url": "http://lhttp.qingting.fm/live/5021381/64k.mp3", "isCollected": false},
{"title": "动听音乐台", "url": "http://lhttp.qingting.fm/live/5022107/64k.mp3", "isCollected": false},
{"title": "500首华语经典", "url": "http://lhttp.qingting.fm/live/5022308/64k.mp3", "isCollected": false},
{"title": "北京音乐广播", "url": "http://lhttp.qingting.fm/live/332/64k.mp3", "isCollected": false},
{"title": "954汽车音乐广播", "url": "http://lhttp.qingting.fm/live/1936/64k.mp3", "isCollected": false},
{"title": "深圳飞扬971", "url": "http://lhttp.qingting.fm/live/1271/64k.mp3", "isCollected": false},
{"title": "950广西音乐台", "url": "http://lhttp.qingting.fm/live/4875/64k.mp3", "isCollected": false},
{"title": "厦门音乐广播", "url": "http://lhttp.qingting.fm/live/1739/64k.mp3", "isCollected": false},
{"title": "欧美音乐88.7", "url": "http://lhttp.qingting.fm/live/15318703/64k.mp3", "isCollected": false},
{"title": "无锡音乐广播", "url": "http://lhttp.qingting.fm/live/2779/64k.mp3", "isCollected": false},
{"title": "四川城市之音", "url": "http://lhttp.qingting.fm/live/1111/64k.mp3", "isCollected": false},
{"title": "陕西音乐广播", "url": "http://lhttp.qingting.fm/live/4873/64k.mp3", "isCollected": false},
{"title": "崂山921", "url": "http://lhttp.qingting.fm/live/20212426/64k.mp3", "isCollected": false},
{"title": "长沙101.7城市之声", "url": "http://lhttp.qingting.fm/live/4237/64k.mp3", "isCollected": false},
{"title": "上海经典947", "url": "http://lhttp.qingting.fm/live/267/64k.mp3", "isCollected": false},
{"title": "湖北经典音乐广播", "url": "http://lhttp.qingting.fm/live/1296/64k.mp3", "isCollected": false},
{"title": "成都年代音乐怀旧好声音", "url": "http://lhttp.qingting.fm/live/20211686/64k.mp3", "isCollected": false},
{"title": "山东经典音乐广播", "url": "http://lhttp.qingting.fm/live/20240/64k.mp3", "isCollected": false},
{"title": "江苏音乐广播Play897", "url": "http://lhttp.qingting.fm/live/4936/64k.mp3", "isCollected": false},
{"title": "天津TIKI 100.5", "url": "http://lhttp.qingting.fm/live/20003/64k.mp3", "isCollected": false},
{"title": "杭州90.7", "url": "http://lhttp.qingting.fm/live/15318146/64k.mp3", "isCollected": false},
{"title": "长春广播电视台 88.0", "url": "http://lhttp.qingting.fm/live/4850/64k.mp3", "isCollected": false},
{"title": "星河音乐", "url": "http://lhttp.qingting.fm/live/20210755/64k.mp3", "isCollected": false},
{"title": "江西音乐广播", "url": "http://lhttp.qingting.fm/live/1802/64k.mp3", "isCollected": false},
{"title": "广东广播电视台珠江之声", "url": "http://lhttp.qingting.fm/live/470/64k.mp3", "isCollected": false},
{"title": "内蒙古音乐之声", "url": "http://lhttp.qingting.fm/live/1886/64k.mp3", "isCollected": false},
{"title": "88.6长沙音乐广播", "url": "http://lhttp.qingting.fm/live/20847/64k.mp3", "isCollected": false},
{"title": "楚天音乐广播", "url": "http://lhttp.qingting.fm/live/1289/64k.mp3", "isCollected": false},
{"title": "重庆音乐广播", "url": "http://lhttp.qingting.fm/live/647/64k.mp3", "isCollected": false},
{"title": "年代音乐1022", "url": "http://lhttp.qingting.fm/live/20500066/64k.mp3", "isCollected": false},
{"title": "武安融媒综合广播", "url": "http://lhttp.qingting.fm/live/5022474/64k.mp3", "isCollected": false},
{"title": "南宁经典1049", "url": "http://lhttp.qingting.fm/live/20769/64k.mp3", "isCollected": false},
{"title": "惠州音乐广播", "url": "http://lhttp.qingting.fm/live/5021523/64k.mp3", "isCollected": false},
{"title": "黑龙江音乐广播", "url": "http://lhttp.qingting.fm/live/4969/64k.mp3", "isCollected": false},
{"title": "嘉兴音乐广播", "url": "http://lhttp.qingting.fm/live/1136/64k.mp3", "isCollected": false},
{"title": "80后音悦台", "url": "http://lhttp.qingting.fm/live/20207761/64k.mp3", "isCollected": false},
{"title": "经典983电台", "url": "http://lhttp.qingting.fm/live/20211575/64k.mp3", "isCollected": false},
{"title": "西安音乐广播", "url": "http://lhttp.qingting.fm/live/1612/64k.mp3", "isCollected": false},
{"title": "武汉经典音乐广播", "url": "http://lhttp.qingting.fm/live/1297/64k.mp3", "isCollected": false},
{"title": "怀旧音乐广播895", "url": "http://lhttp.qingting.fm/live/20211619/64k.mp3", "isCollected": false},
{"title": "定州交通音乐广播", "url": "http://lhttp.qingting.fm/live/20211638/64k.mp3", "isCollected": false},
{"title": "好听 1055", "url": "http://lhttp.qingting.fm/live/4885/64k.mp3", "isCollected": false},
{"title": "南通音乐广播", "url": "http://lhttp.qingting.fm/live/21275/64k.mp3", "isCollected": false},
{"title": "1003温州音乐之声", "url": "http://lhttp.qingting.fm/live/1149/64k.mp3", "isCollected": false},
{"title": "济南音乐广播88.7", "url": "http://lhttp.qingting.fm/live/1671/64k.mp3", "isCollected": false},
{"title": "就爱927 Love Radio", "url": "http://lhttp.qingting.fm/live/1831/64k.mp3", "isCollected": false},
{"title": "AsiaFM 亚洲音乐台", "url": "http://lhttp.qingting.fm/live/5022405/64k.mp3", "isCollected": false},
{"title": "89.3芒果音乐台", "url": "http://lhttp.qingting.fm/live/4979/64k.mp3", "isCollected": false},
{"title": "亚洲音乐成都FM96.5", "url": "http://lhttp.qingting.fm/live/4581/64k.mp3", "isCollected": false},
{"title": "1047 Nice FM", "url": "http://lhttp.qingting.fm/live/20033/64k.mp3", "isCollected": false},
{"title": "常州音乐广播", "url": "http://lhttp.qingting.fm/live/2799/64k.mp3", "isCollected": false},
{"title": "AsiaFM HD音乐台", "url": "http://lhttp.qingting.fm/live/15318341/64k.mp3", "isCollected": false},
{"title": "FM102.2亲子智慧电台", "url": "http://lhttp.qingting.fm/live/4930/64k.mp3", "isCollected": false},
{"title": "察布查尔FM99.5", "url": "http://lhttp.qingting.fm/live/5022610/64k.mp3", "isCollected": false},
{"title": "包头汽车音乐广播", "url": "http://lhttp.qingting.fm/live/1892/64k.mp3", "isCollected": false},
{"title": "吴江交通音乐广播", "url": "http://lhttp.qingting.fm/live/5022050/64k.mp3", "isCollected": false},
{"title": "大连easy radio106.7", "url": "http://lhttp.qingting.fm/live/1084/64k.mp3", "isCollected": false},
{"title": "青岛音乐体育广播", "url": "http://lhttp.qingting.fm/live/1677/64k.mp3", "isCollected": false},
{"title": "唐山音乐广播", "url": "http://lhttp.qingting.fm/live/4871/64k.mp3", "isCollected": false},
{"title": "贵州FM91.6音乐广播", "url": "http://lhttp.qingting.fm/live/20067/64k.mp3", "isCollected": false},
{"title": "廊坊飞扬105", "url": "http://lhttp.qingting.fm/live/20211678/64k.mp3", "isCollected": false},
{"title": "徐州音乐广播FM91.9", "url": "http://lhttp.qingting.fm/live/4923/64k.mp3", "isCollected": false},
{"title": "江西潮台969", "url": "http://lhttp.qingting.fm/live/20500092/64k.mp3", "isCollected": false},
{"title": "吉林市音乐广播", "url": "http://lhttp.qingting.fm/live/20211679/64k.mp3", "isCollected": false},
{"title": "冰城1026哈尔滨古典音乐广播", "url": "http://lhttp.qingting.fm/live/5022338/64k.mp3", "isCollected": false},
{"title": "台州音乐广播", "url": "http://lhttp.qingting.fm/live/1144/64k.mp3", "isCollected": false},
{"title": "流行音乐广播999正青春", "url": "http://lhttp.qingting.fm/live/20211620/64k.mp3", "isCollected": false},
{"title": "云南音乐广播", "url": "http://lhttp.qingting.fm/live/1929/64k.mp3", "isCollected": false},
{"title": "宁波音乐广播私家车986", "url": "http://lhttp.qingting.fm/live/1142/64k.mp3", "isCollected": false},
{"title": "宁夏音乐广播", "url": "http://lhttp.qingting.fm/live/15318294/64k.mp3", "isCollected": false},
{"title": "安阳1008音乐广播", "url": "http://lhttp.qingting.fm/live/2123/64k.mp3", "isCollected": false},
{"title": "海口WHIZRADIO音乐电台", "url": "http://lhttp.qingting.fm/live/20010/64k.mp3", "isCollected": false},
{"title": "邯郸音乐广播", "url": "http://lhttp.qingting.fm/live/4601/64k.mp3", "isCollected": false},
{"title": "沧州音乐广播FM103.6", "url": "http://lhttp.qingting.fm/live/5021902/64k.mp3", "isCollected": false},
{"title": "FM93.0霸州汽车音乐广播", "url": "http://lhttp.qingting.fm/live/20211658/64k.mp3", "isCollected": false},
{"title": "保定城市服务广播乐动1016", "url": "http://lhttp.qingting.fm/live/20406/64k.mp3", "isCollected": false},
{"title": "潍坊982广播电台", "url": "http://lhttp.qingting.fm/live/4865/64k.mp3", "isCollected": false},
{"title": "90后潮流音悦台", "url": "http://lhttp.qingting.fm/live/20207760/64k.mp3", "isCollected": false},
{"title": "盛京FM105.6", "url": "http://lhttp.qingting.fm/live/5022520/64k.mp3", "isCollected": false},
{"title": "100.7重庆永川之声", "url": "http://lhttp.qingting.fm/live/20210236/64k.mp3", "isCollected": false},
{"title": "乌鲁木齐旅游音乐", "url": "http://lhttp.qingting.fm/live/1920/64k.mp3", "isCollected": false},
{"title": "襄阳文化教育广播", "url": "http://lhttp.qingting.fm/live/5057/64k.mp3", "isCollected": false},
{"title": "蚌埠广播电视台经典104.2", "url": "http://lhttp.qingting.fm/live/20152/64k.mp3", "isCollected": false},
{"title": "新闻听天下", "url": "http://lhttp.qingting.fm/live/20500169/64k.mp3", "isCollected": false}
];
// 保存电台数据到localStorage
function saveRadioData(data) {
localStorage.setItem('radioData', JSON.stringify(data));
radioData = data;
}
// 获取电台数据
function getRadioData() {
return [...radioData];
}
// DOM元素
const elements = {
// 公共元素
switchModeBtn: document.getElementById('switchModeBtn'),
playerArea: document.getElementById('playerArea'),
adminArea: document.getElementById('adminArea'),
loginForm: document.getElementById('loginForm'),
backToPlayer: document.getElementById('backToPlayer'),
// 前台播放元素
searchTrigger: document.getElementById('searchTrigger'),
searchBox: document.getElementById('searchBox'),
searchInput: document.getElementById('searchInput'),
allRadioList: document.getElementById('allRadioList'),
collectRadioList: document.getElementById('collectRadioList'),
audioPlayer: document.getElementById('audioPlayer'),
status: document.getElementById('status'),
playPauseBtn: document.getElementById('playPauseBtn'),
nextBtn: document.getElementById('nextBtn'),
sequenceBtn: document.getElementById('sequenceBtn'),
randomBtn: document.getElementById('randomBtn'),
collectBtn: document.getElementById('collectBtn'),
volumeSlider: document.getElementById('volumeSlider'),
volumeIcon: document.getElementById('volumeIcon'),
tabBtns: document.querySelectorAll('.tab-btn'),
// 管理后台元素
addRadioForm: document.getElementById('addRadioForm'),
radioTitle: document.getElementById('radioTitle'),
radioUrl: document.getElementById('radioUrl'),
radioTableBody: document.getElementById('radioTableBody'),
messageContainer: document.getElementById('messageContainer'),
// 登录元素
password: document.getElementById('password'),
loginBtn: document.getElementById('loginBtn'),
loginError: document.getElementById('loginError')
};
// 播放状态
const state = {
currentRadioIndex: -1,
isPlaying: false,
isMuted: false,
playMode: 'sequence', // sequence 顺序播放, random 随机播放
searchTerm: ''
};
// 前台播放功能
// 渲染电台列表
function renderRadioList(listElement, radios) {
listElement.innerHTML = '';
if (radios.length === 0) {
listElement.innerHTML = `
<li class="empty-tip">
<i class="fas fa-headphones"></i>
<p>没有找到电台</p>
</li>
`;
return;
}
radios.forEach((radio, index) => {
const li = document.createElement('li');
li.className = `radio-item ${state.currentRadioIndex === getRadioData().indexOf(radio) ? 'active' : ''}`;
li.innerHTML = `
<span>${radio.title}</span>
<i class="fas fa-heart collect-icon ${radio.isCollected ? 'active' : ''}" data-index="${getRadioData().indexOf(radio)}"></i>
`;
li.dataset.index = getRadioData().indexOf(radio);
listElement.appendChild(li);
});
// 添加收藏按钮事件
document.querySelectorAll('.collect-icon').forEach(icon => {
icon.addEventListener('click', function(e) {
e.stopPropagation();
const index = parseInt(this.dataset.index);
toggleCollect(index);
});
});
}
// 获取收藏的电台
function getCollectData() {
return getRadioData().filter(radio => radio.isCollected);
}
// 获取搜索过滤后的全部电台
function getFilteredAllData() {
if (!state.searchTerm) return getRadioData();
return getRadioData().filter(radio =>
radio.title.toLowerCase().includes(state.searchTerm.toLowerCase())
);
}
// 播放电台
function playRadio(index) {
const radio = getRadioData()[index];
if (!radio) return;
state.currentRadioIndex = index;
elements.audioPlayer.src = radio.url;
elements.audioPlayer.load();
elements.audioPlayer.play();
state.isPlaying = true;
elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
elements.status.innerHTML = `正在播放: ${radio.title}`;
// 更新收藏按钮状态
updateCollectBtnState();
// 更新列表激活状态
renderRadioList(elements.allRadioList, getFilteredAllData());
renderRadioList(elements.collectRadioList, getCollectData());
}
// 切换播放/暂停
function togglePlayPause() {
if (state.currentRadioIndex === -1) {
// 如果没有选中电台,默认播放第一个
if (getRadioData().length > 0) {
playRadio(0);
}
return;
}
if (state.isPlaying) {
elements.audioPlayer.pause();
} else {
elements.audioPlayer.play();
}
}
// 播放下一个电台
function playNextRadio() {
if (getRadioData().length === 0) return;
let nextIndex;
if (state.playMode === 'random') {
// 随机播放
nextIndex = Math.floor(Math.random() * getRadioData().length);
} else {
// 顺序播放
nextIndex = (state.currentRadioIndex + 1) % getRadioData().length;
}
playRadio(nextIndex);
}
// 切换收藏状态
function toggleCollect(index) {
const radioData = getRadioData();
radioData[index].isCollected = !radioData[index].isCollected;
saveRadioData(radioData);
// 更新列表
renderRadioList(elements.allRadioList, getFilteredAllData());
renderRadioList(elements.collectRadioList, getCollectData());
// 如果当前播放的是这个电台,更新收藏按钮
if (index === state.currentRadioIndex) {
updateCollectBtnState();
}
showMessage(radioData[index].isCollected ? 'success' : 'success',
`${radioData[index].isCollected ? '收藏' : '取消收藏'}成功: ${radioData[index].title}`);
}
// 更新收藏按钮状态
function updateCollectBtnState() {
if (state.currentRadioIndex === -1) {
elements.collectBtn.classList.remove('active');
return;
}
const isCollected = getRadioData()[state.currentRadioIndex].isCollected;
if (isCollected) {
elements.collectBtn.classList.add('active');
} else {
elements.collectBtn.classList.remove('active');
}
}
// 切换当前播放电台的收藏状态
function toggleCurrentCollect() {
if (state.currentRadioIndex === -1) return;
toggleCollect(state.currentRadioIndex);
}
// 切换静音
function toggleMute() {
state.isMuted = !state.isMuted;
elements.audioPlayer.muted = state.isMuted;
updateVolumeIcon();
}
// 更新音量图标
function updateVolumeIcon() {
if (state.isMuted) {
elements.volumeIcon.className = 'fas fa-volume-mute volume-icon';
return;
}
const volume = elements.audioPlayer.volume;
if (volume === 0) {
elements.volumeIcon.className = 'fas fa-volume-off volume-icon';
} else if (volume < 0.5) {
elements.volumeIcon.className = 'fas fa-volume-down volume-icon';
} else {
elements.volumeIcon.className = 'fas fa-volume-up volume-icon';
}
}
// 切换播放模式
function setPlayMode(mode) {
state.playMode = mode;
// 更新按钮状态
elements.sequenceBtn.classList.remove('active-mode');
elements.randomBtn.classList.remove('active-mode');
if (mode === 'sequence') {
elements.sequenceBtn.classList.add('active-mode');
} else if (mode === 'random') {
elements.randomBtn.classList.add('active-mode');
}
}
// 切换搜索框显示/隐藏
function toggleSearchBox() {
elements.searchBox.classList.toggle('show');
if (elements.searchBox.classList.contains('show')) {
elements.searchInput.focus();
} else {
elements.searchInput.value = '';
state.searchTerm = '';
renderRadioList(elements.allRadioList, getFilteredAllData());
}
}
// 关闭搜索框
function closeSearchBox() {
elements.searchBox.classList.remove('show');
elements.searchInput.value = '';
state.searchTerm = '';
renderRadioList(elements.allRadioList, getFilteredAllData());
}
// 处理电台列表点击
function handleRadioListClick(e) {
const radioItem = e.target.closest('.radio-item');
if (radioItem) {
const index = parseInt(radioItem.dataset.index);
playRadio(index);
}
}
// 管理后台功能
// 渲染管理后台的电台列表
function renderAdminRadioList() {
const radioData = getRadioData();
elements.radioTableBody.innerHTML = '';
radioData.forEach((radio, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${index + 1}</td>
<td>${radio.title}</td>
<td>${radio.url}</td>
<td>
<div class="action-buttons">
<button class="btn delete-btn" data-index="${index}">
<i class="fas fa-trash"></i>
删除
</button>
</div>
</td>
`;
elements.radioTableBody.appendChild(row);
});
// 添加删除按钮事件监听
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
deleteRadio(index);
});
});
}
// 删除电台
function deleteRadio(index) {
if (confirm('确定要删除这个电台吗?')) {
const radioData = getRadioData();
const deletedRadio = radioData.splice(index, 1)[0];
saveRadioData(radioData);
renderAdminRadioList();
// 如果删除的是当前播放的电台
if (index === state.currentRadioIndex) {
state.currentRadioIndex = -1;
elements.audioPlayer.src = '';
elements.status.innerHTML = '请选择下方电台开始播放';
elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
state.isPlaying = false;
} else if (index < state.currentRadioIndex) {
// 如果删除的是当前播放电台之前的电台,调整索引
state.currentRadioIndex--;
}
// 更新前台列表
renderRadioList(elements.allRadioList, getFilteredAllData());
renderRadioList(elements.collectRadioList, getCollectData());
showMessage('success', `成功删除电台:${deletedRadio.title}`);
}
}
// 添加电台
function addRadio(e) {
e.preventDefault();
const title = elements.radioTitle.value.trim();
const url = elements.radioUrl.value.trim();
if (!title || !url) {
showMessage('error', '请填写完整的电台信息');
return;
}
// 验证URL格式
try {
new URL(url);
} catch (error) {
showMessage('error', '请输入有效的URL地址');
return;
}
const radioData = getRadioData();
// 检查是否已存在相同URL的电台
if (radioData.some(radio => radio.url === url)) {
showMessage('error', '该电台URL已存在');
return;
}
// 添加新电台
radioData.push({
title: title,
url: url,
isCollected: false
});
saveRadioData(radioData);
renderAdminRadioList();
renderRadioList(elements.allRadioList, getFilteredAllData());
// 清空表单
elements.radioTitle.value = '';
elements.radioUrl.value = '';
showMessage('success', `成功添加电台:${title}`);
}
// 登录验证
function login() {
const password = elements.password.value.trim();
if (password === ADMIN_PASSWORD) {
// 登录成功
isAdminLoggedIn = true;
elements.loginForm.classList.add('hidden');
elements.adminArea.classList.remove('hidden');
renderAdminRadioList();
} else {
// 登录失败
elements.loginError.style.display = 'block';
setTimeout(() => {
elements.loginError.style.display = 'none';
}, 3000);
}
}
// 显示消息提示
function showMessage(type, text) {
const message = document.createElement('div');
message.className = `message ${type}`;
message.innerHTML = `
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i>
<span>${text}</span>
`;
elements.messageContainer.appendChild(message);
// 3秒后自动移除消息
setTimeout(() => {
message.style.opacity = '0';
message.style.transform = 'translateY(-10px)';
message.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
setTimeout(() => {
message.remove();
}, 300);
}, 3000);
}
// 切换模式(前台/管理)
function switchMode() {
if (elements.playerArea.classList.contains('hidden')) {
// 切换到前台播放模式
elements.playerArea.classList.remove('hidden');
elements.adminArea.classList.add('hidden');
elements.loginForm.classList.add('hidden');
elements.switchModeBtn.innerHTML = '<i class="fas fa-cog"></i> 切换到管理模式';
} else {
// 切换到管理模式
elements.playerArea.classList.add('hidden');
if (isAdminLoggedIn) {
elements.adminArea.classList.remove('hidden');
elements.loginForm.classList.add('hidden');
} else {
elements.adminArea.classList.add('hidden');
elements.loginForm.classList.remove('hidden');
elements.password.value = '';
elements.loginError.style.display = 'none';
}
elements.switchModeBtn.innerHTML = '<i class="fas fa-music"></i> 切换到播放模式';
}
}
// 初始化事件监听
function initEventListeners() {
// 前台播放事件
elements.playPauseBtn.addEventListener('click', togglePlayPause);
elements.nextBtn.addEventListener('click', playNextRadio);
elements.sequenceBtn.addEventListener('click', () => setPlayMode('sequence'));
elements.randomBtn.addEventListener('click', () => setPlayMode('random'));
elements.collectBtn.addEventListener('click', toggleCurrentCollect);
elements.volumeSlider.addEventListener('input', function() {
elements.audioPlayer.volume = this.value;
state.isMuted = false;
updateVolumeIcon();
});
elements.volumeIcon.addEventListener('click', toggleMute);
elements.audioPlayer.addEventListener('play', () => {
state.isPlaying = true;
elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
});
elements.audioPlayer.addEventListener('pause', () => {
state.isPlaying = false;
elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});
elements.audioPlayer.addEventListener('volumechange', updateVolumeIcon);
// 列表点击事件委托
elements.allRadioList.addEventListener('click', handleRadioListClick);
elements.collectRadioList.addEventListener('click', handleRadioListClick);
// 搜索相关事件
elements.searchTrigger.addEventListener('click', toggleSearchBox);
elements.searchInput.addEventListener('input', function() {
state.searchTerm = this.value;
renderRadioList(elements.allRadioList, getFilteredAllData());
});
// 点击页面其他区域关闭搜索框
document.addEventListener('click', function(e) {
if (!elements.searchBox.contains(e.target) && !elements.searchTrigger.contains(e.target)) {
closeSearchBox();
}
});
// 标签切换事件
elements.tabBtns.forEach(btn => {
btn.addEventListener('click', function() {
elements.tabBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
const tab = this.dataset.tab;
document.querySelectorAll('.radio-list').forEach(list => {
list.classList.remove('active');
});
document.getElementById(`${tab}RadioList`).classList.add('active');
});
});
// 键盘快捷键
document.addEventListener('keydown', function(e) {
// 避免在输入框中触发快捷键
if (e.target.tagName === 'INPUT') return;
switch(e.code) {
case 'Space':
e.preventDefault();
togglePlayPause();
break;
case 'ArrowRight':
e.preventDefault();
playNextRadio();
break;
case 'KeyF':
e.preventDefault();
toggleCurrentCollect();
break;
case 'KeyM':
e.preventDefault();
toggleMute();
break;
case 'KeyS':
e.preventDefault();
toggleSearchBox();
break;
case 'Escape':
e.preventDefault();
closeSearchBox();
break;
}
});
// 管理后台事件
elements.addRadioForm.addEventListener('submit', addRadio);
elements.loginBtn.addEventListener('click', login);
elements.password.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
login();
}
});
elements.backToPlayer.addEventListener('click', switchMode);
elements.switchModeBtn.addEventListener('click', switchMode);
}
// 页面初始化
function init() {
renderRadioList(elements.allRadioList, getFilteredAllData());
renderRadioList(elements.collectRadioList, getCollectData());
elements.audioPlayer.volume = elements.volumeSlider.value;
elements.sequenceBtn.classList.add('active-mode');
updateVolumeIcon();
initEventListeners();
}
// 页面加载完成后初始化
window.addEventListener('load', init);
})();
</script>
</body>
</html>