// Cloudflare Worker 直播播放器
// 部署:复制全部代码到 Worker 编辑器中保存即可
// 您提供的直播源列表 - 带中文频道名称
const CHANNELS = [
["CCTV1 综合", "https://zb.fxynet.cn/ysp.php?pd=cctv1"],
["CCTV2 财经", "https://zb.fxynet.cn/ysp.php?pd=cctv2"],
["CCTV3 综艺", "https://zb.fxynet.cn/ysp.php?pd=cctv3"],
["CCTV4 中文国际", "https://zb.fxynet.cn/ysp.php?pd=cctv4"],
["CCTV5 体育", "https://zb.fxynet.cn/ysp.php?pd=cctv5"],
["CCTV5+ 体育赛事", "https://zb.fxynet.cn/ysp.php?pd=cctv5p"],
["CCTV6 电影", "https://zb.fxynet.cn/ysp.php?pd=cctv6"],
["CCTV7 国防军事", "https://zb.fxynet.cn/ysp.php?pd=cctv7"],
["CCTV8 电视剧", "https://zb.fxynet.cn/ysp.php?pd=cctv8"],
["CCTV9 纪录", "https://zb.fxynet.cn/ysp.php?pd=cctv9"],
["CCTV10 科教", "https://zb.fxynet.cn/ysp.php?pd=cctv10"],
["CCTV11 戏曲", "https://zb.fxynet.cn/ysp.php?pd=cctv11"],
["CCTV12 社会与法", "https://zb.fxynet.cn/ysp.php?pd=cctv12"],
["CCTV13 新闻", "https://zb.fxynet.cn/ysp.php?pd=cctv13"],
["CCTV14 少儿", "https://zb.fxynet.cn/ysp.php?pd=cctv14"],
["CCTV15 音乐", "https://zb.fxynet.cn/ysp.php?pd=cctv15"],
["CCTV16 奥林匹克", "https://zb.fxynet.cn/ysp.php?pd=cctv16"],
["CCTV16 4K 奥林匹克", "https://zb.fxynet.cn/ysp.php?pd=cctv164k"],
["CCTV17 农业农村", "https://zb.fxynet.cn/ysp.php?pd=cctv17"],
["CCTV4K 超高清", "https://zb.fxynet.cn/ysp.php?pd=cctv4k"],
["CCTV8K 超高清", "https://zb.fxynet.cn/ysp.php?pd=cctv8k"],
["CGTN 新闻", "https://zb.fxynet.cn/ysp.php?pd=cgtn"],
["CGTN 法语", "https://zb.fxynet.cn/ysp.php?pd=cgtnfayu"],
["CGTN 俄语", "https://zb.fxynet.cn/ysp.php?pd=cgtneyu"],
["CGTN 阿拉伯语", "https://zb.fxynet.cn/ysp.php?pd=cgtnalaboyu"],
["CGTN 西班牙语", "https://zb.fxynet.cn/ysp.php?pd=cgtnxibanyayu"],
["CGTN 外语纪录", "https://zb.fxynet.cn/ysp.php?pd=cgtnwaiyujilu"],
["CETV1 中国教育", "https://zb.fxynet.cn/ysp.php?pd=cetv1"],
["国学频道", "https://zb.fxynet.cn/ysp.php?pd=guoxuepindao"],
["北京卫视", "https://zb.fxynet.cn/ysp.php?pd=beijingweishi"],
["东方卫视", "https://zb.fxynet.cn/ysp.php?pd=dongfangweishi"],
["江苏卫视", "https://zb.fxynet.cn/ysp.php?pd=jiangsuweishi"],
["浙江卫视", "https://zb.fxynet.cn/ysp.php?pd=zhejiangweishi"],
["湖南卫视", "https://zb.fxynet.cn/ysp.php?pd=hunanweishi"],
["湖北卫视", "https://zb.fxynet.cn/ysp.php?pd=hubeiweishi"],
["广东卫视", "https://zb.fxynet.cn/ysp.php?pd=guangdongweishi"],
["广西卫视", "https://zb.fxynet.cn/ysp.php?pd=guangxiweishi"],
["黑龙江卫视", "https://zb.fxynet.cn/ysp.php?pd=heilongjiangweishi"],
["海南卫视", "https://zb.fxynet.cn/ysp.php?pd=hainanweishi"],
["重庆卫视", "https://zb.fxynet.cn/ysp.php?pd=chongqingweishi"],
["深圳卫视", "https://zb.fxynet.cn/ysp.php?pd=shenzhenweishi"],
["四川卫视", "https://zb.fxynet.cn/ysp.php?pd=sichuanweishi"],
["河南卫视", "https://zb.fxynet.cn/ysp.php?pd=henanweishi"],
["东南卫视", "https://zb.fxynet.cn/ysp.php?pd=dongnanweishi"],
["贵州卫视", "https://zb.fxynet.cn/ysp.php?pd=guizhouweishi"],
["江西卫视", "https://zb.fxynet.cn/ysp.php?pd=jiangxiweishi"],
["辽宁卫视", "https://zb.fxynet.cn/ysp.php?pd=liaoningweishi"],
["安徽卫视", "https://zb.fxynet.cn/ysp.php?pd=anhuiweishi"],
["河北卫视", "https://zb.fxynet.cn/ysp.php?pd=hebeiweishi"],
["山东卫视", "https://zb.fxynet.cn/ysp.php?pd=shandongweishi"],
["天津卫视", "https://zb.fxynet.cn/ysp.php?pd=tianjinweishi"],
["陕西卫视", "https://zb.fxynet.cn/ysp.php?pd=shaanxiweishi"],
["内蒙古卫视", "https://zb.fxynet.cn/ysp.php?pd=neimengguweishi"],
["甘肃卫视", "https://zb.fxynet.cn/ysp.php?pd=gansuweishi"],
["宁夏卫视", "https://zb.fxynet.cn/ysp.php?pd=ningxiaweishi"],
["山西卫视", "https://zb.fxynet.cn/ysp.php?pd=shanxiweishi"],
["云南卫视", "https://zb.fxynet.cn/ysp.php?pd=yunnanweishi"],
["吉林卫视", "https://zb.fxynet.cn/ysp.php?pd=jilinweishi"],
["青海卫视", "https://zb.fxynet.cn/ysp.php?pd=qinghaiweishi"],
["西藏卫视", "https://zb.fxynet.cn/ysp.php?pd=xizangweishi"],
["新疆卫视", "https://zb.fxynet.cn/ysp.php?pd=xinjiangweishi"],
["兵团卫视", "https://zb.fxynet.cn/ysp.php?pd=bingtuanweishi"]
];
// 找到浙江卫视的索引(默认选中)
const DEFAULT_CHANNEL_INDEX = CHANNELS.findIndex(ch => ch[0].includes("浙江卫视"));
// HTML 页面模板 - 大窗口版本,无提示文字
const HTML_TEMPLATE = `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电视直播</title>
<style>
* {
box-sizing: border-box;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
margin: 0;
padding: 0;
}
body {
background: #0b1120;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
.player-container {
max-width: 1400px;
width: 100%;
background: #0f172a;
border-radius: 2rem;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.8), 0 0 0 1px rgba(56, 189, 248, 0.2);
overflow: hidden;
backdrop-filter: blur(4px);
border: 1px solid #1e293b;
}
.video-wrapper {
background: #030712;
position: relative;
aspect-ratio: 16 / 9;
}
#live-video {
width: 100%;
height: 100%;
display: block;
background: #000;
outline: none;
}
.video-control-bar {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1.5rem;
background: #0f172a;
flex-wrap: wrap;
}
.channel-info {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
background: #1e293b;
padding: 0.4rem 1rem;
border-radius: 40px;
color: #b9d3f0;
font-size: 0.95rem;
border: 1px solid #334155;
}
.channel-info i {
color: #38bdf8;
}
.control-btn {
background: #1e293b;
border: none;
color: #e2e8f0;
width: 40px;
height: 40px;
border-radius: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.2s;
font-size: 1.3rem;
border: 1px solid #334155;
}
.control-btn:hover {
background: #2d3b52;
color: #90d2ff;
border-color: #38bdf8;
}
.control-btn.primary {
background: #2563eb;
border-color: #3b82f6;
color: white;
}
.control-btn.primary:hover {
background: #3b82f6;
}
.channel-selector {
flex: 2;
min-width: 280px;
}
.channel-selector select {
width: 100%;
padding: 0.65rem 1rem;
background: #1e293b;
border: 1px solid #334155;
border-radius: 40px;
color: #f1f5f9;
font-size: 0.95rem;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
background-size: 16px;
}
.channel-selector select option {
background: #0f172a;
color: #e2e8f0;
padding: 4px;
}
.status-badge {
color: #94a3b8;
font-size: 0.8rem;
background: #1e293b;
padding: 4px 12px;
border-radius: 24px;
display: inline-block;
border: 1px solid #334155;
margin-left: auto;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js"></script>
</head>
<body>
<div class="player-container" id="app">
<div class="video-wrapper">
<video id="live-video" controls autoplay playsinline></video>
</div>
<div class="video-control-bar">
<div class="channel-info">
<i>📺</i> <span id="current-channel-name">浙江卫视</span>
</div>
<button class="control-btn" id="reload-btn" title="重新加载">↻</button>
<button class="control-btn primary" id="fullscreen-btn" title="全屏">⛶</button>
<div class="channel-selector">
<select id="channel-select-dropdown"></select>
</div>
<span class="status-badge" id="channel-count"></span>
</div>
</div>
<script>
(function() {
const CHANNELS = ${JSON.stringify(CHANNELS)};
const DEFAULT_INDEX = ${DEFAULT_CHANNEL_INDEX};
const video = document.getElementById('live-video');
const currentNameSpan = document.getElementById('current-channel-name');
const dropdown = document.getElementById('channel-select-dropdown');
const reloadBtn = document.getElementById('reload-btn');
const fullscreenBtn = document.getElementById('fullscreen-btn');
const channelCountSpan = document.getElementById('channel-count');
let currentIndex = DEFAULT_INDEX;
let hlsInstance = null;
channelCountSpan.innerText = CHANNELS.length + '个频道';
function destroyHls() {
if (hlsInstance) {
try { hlsInstance.destroy(); } catch (e) {}
hlsInstance = null;
}
}
function playChannelByIndex(index) {
if (index < 0 || index >= CHANNELS.length) return;
const [name, url] = CHANNELS[index];
currentNameSpan.innerText = name;
if (dropdown) dropdown.value = index;
destroyHls();
video.pause();
video.removeAttribute('src');
video.load();
// 智能播放
if (url.includes('.m3u8') || url.includes('ysp.php')) {
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
} else if (Hls.isSupported()) {
const hls = new Hls({ enableWorker: true, lowLatencyMode: true });
hlsInstance = hls;
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
destroyHls();
video.src = url;
}
});
} else {
video.src = url;
}
} else {
video.src = url;
}
video.play().catch(e => console.log('自动播放被阻止', e));
}
// 渲染下拉框
let dropdownHtml = '';
CHANNELS.forEach((ch, idx) => {
const [name] = ch;
dropdownHtml += <option value="\${idx}" \${idx === DEFAULT_INDEX ? 'selected' : ''}>\${name}</option>\;
});
dropdown.innerHTML = dropdownHtml;
dropdown.addEventListener('change', (e) => {
const idx = parseInt(e.target.value, 10);
if (!isNaN(idx)) {
currentIndex = idx;
playChannelByIndex(currentIndex);
}
});
reloadBtn.addEventListener('click', () => playChannelByIndex(currentIndex));
fullscreenBtn.addEventListener('click', () => {
if (video.requestFullscreen) video.requestFullscreen();
else if (video.webkitRequestFullscreen) video.webkitRequestFullscreen();
else if (video.msRequestFullscreen) video.msRequestFullscreen();
});
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && document.activeElement !== dropdown) {
e.preventDefault();
if (video.paused) video.play(); else video.pause();
}
});
// 默认播放浙江卫视
playChannelByIndex(DEFAULT_INDEX);
})();
</script>
</body>
</html>`;
// Worker 主入口
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// 如果是请求根路径,返回HTML页面
if (url.pathname === "/" || url.pathname === "") {
return new Response(HTML_TEMPLATE, {
headers: {
"Content-Type": "text/html;charset=UTF-8",
"Cache-Control": "public, max-age=3600",
},
});
}
// 其他路径返回404
return new Response("Not Found", { status: 404 });
},
};