直播源:https://raw.githubusercontent.com/hujingguang/ChinaIPTV/main/cnTV_AutoUpdate.m3u8
网盘:https://www.yeahhe.online/OneDriveShare/小虎会享
下载源码:https://www.yeahhe.online/d/Tencent%20Cloud/IPTV.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能IPTV播放器</title>
<link href="https://vjs.zencdn.net/7.20.3/video-js.min.css" rel="stylesheet">
<script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #2c3e50;
--secondary-color: #34495e;
--accent-color: #3498db;
--background-color: #ecf0f1;
--text-color: #2c3e50;
--hover-color: #e74c3c;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Noto Sans SC', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
text-align: center;
padding: 20px 0;
margin-bottom: 30px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
h1 {
font-size: 2.5em;
font-weight: 700;
letter-spacing: 2px;
}
.content {
display: flex;
gap: 30px;
background-color: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
#channel-list {
flex: 0 0 300px;
height: calc(100vh - 200px);
overflow-y: auto;
background-color: var(--secondary-color);
padding: 20px;
scrollbar-width: thin;
scrollbar-color: var(--accent-color) var(--secondary-color);
}
#channel-list::-webkit-scrollbar {
width: 8px;
}
#channel-list::-webkit-scrollbar-track {
background: var(--secondary-color);
}
#channel-list::-webkit-scrollbar-thumb {
background-color: var(--accent-color);
border-radius: 20px;
}
#channel-list button {
display: block;
width: 100%;
padding: 15px;
border: none;
background-color: transparent;
color: white;
text-align: left;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 8px;
margin-bottom: 10px;
}
#channel-list button:hover, #channel-list button.active {
background-color: var(--accent-color);
transform: translateX(5px);
}
#player-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
}
#video-wrapper {
width: 100%;
max-width: 1080px;
position: relative;
padding-top: 56.25%; /* 16:9 Aspect Ratio */
}
.video-js {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.channel-item {
display: flex;
align-items: center;
}
.channel-icon {
width: 40px;
height: 40px;
margin-right: 15px;
object-fit: contain;
border-radius: 50%;
background-color: white;
padding: 5px;
transition: transform 0.3s ease;
}
#channel-list button:hover .channel-icon {
transform: scale(1.1);
}
.channel-info {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.channel-name {
font-weight: bold;
margin-bottom: 5px;
font-size: 1em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-description {
font-size: 0.8em;
opacity: 0.8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 968px) {
.content {
flex-direction: column;
}
#channel-list, #player-container {
width: 100%;
}
#channel-list {
height: 300px;
flex: none;
}
}
</style>
</head>
<body>
<header>
<h1>智能IPTV播放器</h1>
</header>
<div class="container">
<div class="content">
<div id="channel-list"></div>
<div id="player-container">
<div id="video-wrapper">
<video-js id="my-video" class="video-js vjs-big-play-centered" controls preload="auto" data-setup='{"techOrder": ["html5"]}'>
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video-js>
</div>
</div>
</div>
</div>
<script>
const playlistUrl = 'https://raw.githubusercontent.com/hujingguang/ChinaIPTV/main/cnTV_AutoUpdate.m3u8';
const channelList = document.getElementById('channel-list');
const player = videojs('my-video', {
html5: {
hls: {
overrideNative: true
}
}
});
let currentChannelIndex = -1;
let channels = [];
function isValidIpv4Url(string) {
try {
const url = new URL(string);
return !string.includes('[') && !string.includes(']');
} catch (_) {
return false;
}
}
function parseM3ULine(line) {
if (line.startsWith('http://') || line.startsWith('https://')) {
return line.trim();
}
return null;
}
function extractTvgInfo(extinf) {
const logoMatch = extinf.match(/tvg-logo="([^"]+)"/);
const nameMatch = extinf.match(/tvg-name="([^"]+)"/);
return {
logo: logoMatch ? logoMatch[1] : null,
name: nameMatch ? nameMatch[1] : null
};
}
function playChannel(index) {
if (index >= 0 && index < channels.length) {
const channel = channels[index];
player.src({
type: 'application/x-mpegURL',
src: channel.url
});
player.play();
channelList.querySelectorAll('button').forEach(btn => btn.classList.remove('active'));
channelList.children[index].classList.add('active');
currentChannelIndex = index;
channelList.children[index].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
fetch(playlistUrl)
.then(response => response.text())
.then(data => {
const lines = data.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXTINF')) {
const tvgInfo = extractTvgInfo(lines[i]);
const title = lines[i].split(',')[1];
const url = parseM3ULine(lines[i+1]);
if (url && isValidIpv4Url(url)) {
channels.push({ title, url, tvgInfo });
const button = document.createElement('button');
const channelItem = document.createElement('div');
channelItem.className = 'channel-item';
if (tvgInfo.logo) {
const img = document.createElement('img');
img.src = tvgInfo.logo;
img.className = 'channel-icon';
img.onerror = function() { this.style.display = 'none'; };
channelItem.appendChild(img);
}
const infoDiv = document.createElement('div');
infoDiv.className = 'channel-info';
const nameSpan = document.createElement('span');
nameSpan.className = 'channel-name';
nameSpan.textContent = tvgInfo.name || title;
infoDiv.appendChild(nameSpan);
if (tvgInfo.name && tvgInfo.name !== title) {
const descSpan = document.createElement('span');
descSpan.className = 'channel-description';
descSpan.textContent = title;
infoDiv.appendChild(descSpan);
}
channelItem.appendChild(infoDiv);
button.appendChild(channelItem);
channelList.appendChild(button);
}
}
}
if (channels.length > 0) {
playChannel(0);
}
// 设置频道点击事件
channelList.querySelectorAll('button').forEach((button, index) => {
button.onclick = function() {
playChannel(index);
};
});
})
.catch(error => console.error('Error:', error));
</script>
</body>
</html>```