懒人必备:一键部署Cloudflare Worker 电视台直播播放器

作者:Q师傅 发布时间: 2026-02-25 阅读量:3 评论数:0
// 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 });
    },
};

评论