吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1052|回复: 7
收起左侧

[学习记录] 自定义视频播放器

  [复制链接]
AiGuoZhe66 发表于 2026-2-24 20:08
[JavaScript] 纯文本查看 复制代码
// 获取DOM元素
const videoPlayer = document.getElementById('videoPlayer');
const m3u8UrlInput = document.getElementById('m3u8Url');
const loadBtn = document.getElementById('loadBtn');
const playPauseBtn = document.getElementById('playPauseBtn');
const stopBtn = document.getElementById('stopBtn');
const volumeSlider = document.getElementById('volumeSlider');
const volumeValue = document.getElementById('volumeValue');
const statusDisplay = document.getElementById('status');
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const playlistContainer = document.getElementById('playlist');
const speedSelect = document.getElementById('speedSelect');
const qualitySelect = document.getElementById('qualitySelect');
const currentBitrateDisplay = document.getElementById('currentBitrate');
const bufferProgressDisplay = document.getElementById('bufferProgress');

// 初始化HLS实例
let hls = null;
let currentUrl = '';
let playlist = [];
let qualityLevels = [];

// 设置音量显示
function updateVolumeDisplay(value) {
    const volumePercent = Math.round(value * 100);
    volumeValue.textContent = `${volumePercent}%`;
}

// 更新缓冲信息
function updateBufferInfo() {
    if (!videoPlayer.buffered.length) {
        bufferProgressDisplay.textContent = '缓冲:0%';
        return;
    }
    
    const bufferedEnd = videoPlayer.buffered.end(videoPlayer.buffered.length - 1);
    const currentTime = videoPlayer.currentTime;
    const bufferLength = bufferedEnd - currentTime;
    const bufferPercent = Math.min(100, Math.round((bufferLength / 30) * 100)); // 基于30秒目标缓冲区
    
    bufferProgressDisplay.textContent = `缓冲:${Math.round(bufferLength)}秒 (${bufferPercent}%)`;
}

// 更新码率信息
function updateBitrateInfo(level) {
    if (level === -1) {
        currentBitrateDisplay.textContent = '码率:自动';
    } else if (qualityLevels[level]) {
        const bitrate = Math.round(qualityLevels[level].bitrate / 1000);
        currentBitrateDisplay.textContent = `码率:${bitrate} kbps`;
    }
}

// 更新码率选择列表
function updateQualitySelect() {
    if (!hls || !hls.levels) return;
    
    // 保存当前选择
    const currentValue = qualitySelect.value;
    
    // 清空列表
    qualitySelect.innerHTML = '<option value="auto" selected>自动码率</option>';
    
    // 添加可用码率
    qualityLevels = hls.levels;
    qualityLevels.forEach((level, index) => {
        const bitrate = Math.round(level.bitrate / 1000);
        const resolution = level.height ? `${level.width}x${level.height}` : '未知分辨率';
        const option = document.createElement('option');
        option.value = index;
        option.textContent = `${resolution} (${bitrate} kbps)`;
        qualitySelect.appendChild(option);
    });
    
    // 恢复当前选择
    qualitySelect.value = currentValue;
}

// 设置播放速度
function setPlaybackSpeed(speed) {
    videoPlayer.playbackRate = parseFloat(speed);
    updateStatus(`播放速度:${speed}x`);
}

// 设置码率
function setQuality(quality) {
    if (!hls) return;
    
    if (quality === 'auto') {
        hls.currentLevel = -1; // 自动码率
        updateBitrateInfo(-1);
        updateStatus('已切换到自动码率');
    } else {
        const levelIndex = parseInt(quality);
        if (levelIndex >= 0 && levelIndex < hls.levels.length) {
            hls.currentLevel = levelIndex;
            updateBitrateInfo(levelIndex);
            const bitrate = Math.round(hls.levels[levelIndex].bitrate / 1000);
            updateStatus(`已切换到 ${bitrate} kbps 码率`);
        }
    }
}

// 加载视频
function loadVideo(url) {
    // 停止当前播放
    if (hls) {
        hls.destroy();
        hls = null;
    }
    
    // 重置视频元素
    videoPlayer.pause();
    videoPlayer.src = '';
    videoPlayer.load();
    
    // 验证URL
    if (!url || !url.trim()) {
        updateStatus('错误:请输入有效的M3U8地址');
        return;
    }
    
    if (!url.startsWith('http') || !url.endsWith('.m3u8')) {
        updateStatus('错误:请输入有效的M3U8地址');
        return;
    }
    
    currentUrl = url;
    updateStatus(`正在加载视频:${url}`);
    
    // 更新活跃列表项
    updateActivePlaylistItem(url);
    
    // 检查浏览器是否支持HLS
    if (Hls.isSupported()) {
        try {
            // HLS配置优化,添加缓存和性能设置
            const hlsConfig = {
                maxBufferSize: 10 * 1024 * 1024, // 最大缓冲区大小(10MB)
                maxMaxBufferLength: 60, // 最大缓冲区时长(60秒)
                maxBufferLength: 30, // 目标缓冲区时长(30秒)
                startLevel: -1, // 自动选择合适的码率
                defaultAudioCodec: 'mp4a.40.2',
                enableWorker: true, // 启用Web Worker
                lowLatencyMode: false, // 关闭低延迟模式以提高稳定性
                backBufferLength: 90, // 后缓冲区长度(90秒)
                capLevelOnFPSDrop: true, // 在FPS下降时自动降低码率
                minAutoBitrate: 0, // 最小自动码率
                maxAutoBitrate: Infinity, // 最大自动码率
                testBandwidth: true, // 测试带宽
                bandwidthEstimateUpdateInterval: 5000, // 带宽估计更新间隔
                enableSoftwareAES: true, // 启用软件AES解密
                fpsDroppedMonitoringPeriod: 5000, // FPS下降监控周期
                fpsDroppedThreshold: 0.2, // FPS下降阈值
                maxFragLookUpTolerance: 0.25, // 最大片段查找容差
                abrEwmaFastLive: 3.0, // 快速带宽估计权重
                abrEwmaSlowLive: 9.0, // 慢速带宽估计权重
                abrEwmaFastVoD: 3.0, // VOD快速带宽估计权重
                abrEwmaSlowVoD: 9.0 // VOD慢速带宽估计权重
            };
            
            hls = new Hls(hlsConfig);
            hls.loadSource(url);
            hls.attachMedia(videoPlayer);
            
            hls.on(Hls.Events.MANIFEST_PARSED, () => {
                updateStatus('视频加载成功,开始播放');
                updateQualitySelect(); // 更新码率选择列表
                videoPlayer.play();
            });
            
            hls.on(Hls.Events.ERROR, (event, data) => {
                if (data.fatal) {
                    switch (data.type) {
                        case Hls.ErrorTypes.NETWORK_ERROR:
                            updateStatus('错误:网络错误,正在重试...');
                            hls.startLoad();
                            break;
                        case Hls.ErrorTypes.MEDIA_ERROR:
                            updateStatus('错误:媒体错误,无法播放');
                            videoPlayer.pause();
                            break;
                        default:
                            updateStatus('错误:播放失败');
                            hls.destroy();
                            hls = null;
                            break;
                    }
                }
            });
            
            // 添加码率切换事件监听
            hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
                updateBitrateInfo(data.level);
            });
            
            // 添加缓冲区更新事件监听
            hls.on(Hls.Events.BUFFER_LEVEL_STATE_CHANGED, () => {
                updateBufferInfo();
            });
        } catch (error) {
            updateStatus(`错误:加载失败 - ${error.message}`);
        }
    } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
        // Safari原生支持HLS
        try {
            videoPlayer.src = url;
            videoPlayer.addEventListener('loadedmetadata', () => {
                updateStatus('视频加载成功,开始播放');
            });
            videoPlayer.addEventListener('error', (e) => {
                updateStatus(`错误:播放失败 - ${e.message}`);
            });
            videoPlayer.play();
        } catch (error) {
            updateStatus(`错误:加载失败 - ${error.message}`);
        }
    } else {
        updateStatus('错误:您的浏览器不支持HLS视频播放');
    }
}

// 播放/暂停控制
function togglePlayPause() {
    if (videoPlayer.paused || videoPlayer.ended) {
        videoPlayer.play();
        playPauseBtn.textContent = '暂停';
    } else {
        videoPlayer.pause();
        playPauseBtn.textContent = '播放';
    }
}

// 停止播放
function stopVideo() {
    videoPlayer.pause();
    videoPlayer.currentTime = 0;
    playPauseBtn.textContent = '播放';
    updateStatus('已停止播放');
}

// 更新状态
function updateStatus(message) {
    statusDisplay.textContent = message;
    console.log(message);
}

// 读取文本文件
function readTextFile(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => resolve(e.target.result);
        reader.onerror = (e) => reject(e);
        reader.readAsText(file);
    });
}

// 解析M3U8链接列表
function parseM3U8Links(text) {
    const lines = text.split('\n');
    const links = [];
    
    lines.forEach(line => {
        const trimmedLine = line.trim();
        if (trimmedLine && trimmedLine.startsWith('http') && trimmedLine.endsWith('.m3u8')) {
            links.push(trimmedLine);
        }
    });
    
    return links;
}

// 生成视频列表
function generatePlaylist(links) {
    playlist = links;
    playlistContainer.innerHTML = '';
    
    if (links.length === 0) {
        playlistContainer.innerHTML = '<div style="padding: 1rem; color: #666; text-align: center;">无有效M3U8链接</div>';
        return;
    }
    
    links.forEach((link, index) => {
        const playlistItem = document.createElement('div');
        playlistItem.className = 'playlist-item';
        playlistItem.textContent = link;
        playlistItem.dataset.url = link;
        playlistItem.dataset.index = index + 1;
        
        // 添加点击事件
        playlistItem.addEventListener('click', () => {
            loadVideo(link);
        });
        
        playlistContainer.appendChild(playlistItem);
    });
    
    updateStatus(`成功加载${links.length}个视频链接`);
}

// 更新活跃列表项
function updateActivePlaylistItem(url) {
    const items = playlistContainer.querySelectorAll('.playlist-item');
    items.forEach(item => {
        if (item.dataset.url === url) {
            item.classList.add('active');
        } else {
            item.classList.remove('active');
        }
    });
}

// 事件监听器
loadBtn.addEventListener('click', () => {
    const url = m3u8UrlInput.value.trim();
    loadVideo(url);
});

// 按Enter键加载视频
m3u8UrlInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
        const url = m3u8UrlInput.value.trim();
        loadVideo(url);
    }
});

playPauseBtn.addEventListener('click', togglePlayPause);

stopBtn.addEventListener('click', stopVideo);

volumeSlider.addEventListener('input', (e) => {
    const volume = parseFloat(e.target.value);
    videoPlayer.volume = volume;
    updateVolumeDisplay(volume);
});

// 视频事件监听
videoPlayer.addEventListener('play', () => {
    playPauseBtn.textContent = '暂停';
});

videoPlayer.addEventListener('pause', () => {
    playPauseBtn.textContent = '播放';
});

videoPlayer.addEventListener('ended', () => {
    playPauseBtn.textContent = '播放';
    updateStatus('视频播放完毕');
});

videoPlayer.addEventListener('error', (e) => {
    updateStatus(`视频错误:${e.message}`);
});

// 缓冲事件监听
videoPlayer.addEventListener('progress', updateBufferInfo);
videoPlayer.addEventListener('timeupdate', updateBufferInfo);

// 文件上传事件
uploadBtn.addEventListener('click', (file) => {
    fileInput.click();
});

fileInput.addEventListener('change', async (e) => {
	console.log('---', e)
    const file = e.target.files[0];
	console.log('---', file)
    if (!file) return;
    
    if (file.type !== 'text/plain' && !file.name.endsWith('.txt')) {
        updateStatus('错误:请选择.txt文本文件');
        return;
    }
    
    updateStatus(`正在读取文件:${file.name}`);
    
    try {
		console.log('--file--', file)
        const text = await readTextFile(file);
        const links = parseM3U8Links(text);
        generatePlaylist(links);
    } catch (error) {
        updateStatus(`错误:读取文件失败 - ${error.message}`);
    }
});

// 播放速度选择事件
speedSelect.addEventListener('change', (e) => {
    setPlaybackSpeed(e.target.value);
});

// 画质选择事件
qualitySelect.addEventListener('change', (e) => {
    setQuality(e.target.value);
});

// 初始化
updateStatus('就绪 - 请输入M3U8视频地址或上传列表文件');
updateVolumeDisplay(videoPlayer.volume);
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>M3U8视频播放器</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
    <div class="player-container">
        <h1>M3U8视频播放器</h1>
        <div class="url-input-section">
            <input type="text" id="m3u8Url" placeholder="请输入M3U8视频地址">
            <button id="loadBtn">加载视频</button>
            <input type="file" id="fileInput" accept=".txt" style="display: none;">
            <button id="uploadBtn">上传M3U8列表文件</button>
        </div>
        <div class="video-container">
            <video id="videoPlayer" controls autoplay width="100%" height="auto"></video>
        </div>
        <div class="controls-section">
            <button id="playPauseBtn">暂停</button>
            <button id="stopBtn">停止</button>
            <select id="speedSelect">
                <option value="0.5">0.5x</option>
                <option value="0.75">0.75x</option>
                <option value="1" selected>1x</option>
                <option value="1.25">1.25x</option>
                <option value="1.5">1.5x</option>
                <option value="2">2x</option>
            </select>
            <select id="qualitySelect">
                <option value="auto" selected>自动码率</option>
            </select>
            <input type="range" id="volumeSlider" min="0" max="1" step="0.1" value="1">
            <span id="volumeValue">100%</span>
        </div>
        <div class="playlist-section">
            <h3>视频列表</h3>
            <div id="playlist" class="playlist-container"></div>
        </div>
        <div class="info-section">
            <p id="status">就绪</p>
            <div class="buffer-info">
                <span id="currentBitrate">码率:自动</span>
                <span id="bufferProgress">缓冲:0%</span>
            </div>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>


支持M3u8视频地址的播放,播放格式可拓展,功能支持拓展,倍速播放,缓存等功能

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
wanfon + 1 + 1 热心回复!
YYL7535 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

天天哈皮 发表于 2026-2-25 09:46
学习了,可以配下成品截图
congheerlai 发表于 2026-2-25 10:33
floven 发表于 2026-2-25 10:48
如果有运行时候的截图就完美了,反手给一个赞先~
知心 发表于 2026-2-25 10:53
作者没有分享css,只能看到这样
ScreenShot_2026-02-25_105203_762.png
devSteven 发表于 2026-2-25 11:09
好东西,网页版的HLS播放器实现。支持播放M3U8视频。
syao117 发表于 2026-2-25 11:25
可以一试,感谢分享
 楼主| AiGuoZhe66 发表于 2026-2-25 19:55
添加CSS样式代码
[JavaScript] 纯文本查看 复制代码
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    color: #333;
    line-height: 1.6;
}

.player-container {
    max-width: 900px;
    margin: 2rem auto;
    padding: 1.5rem;
    background: white;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    color: #2c3e50;
    margin-bottom: 1.5rem;
    font-size: 1.8rem;
}

.url-input-section {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

#m3u8Url {
    flex: 1;
    padding: 0.75rem;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 1rem;
    transition: border-color 0.3s;
}

#m3u8Url:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
}

button {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.3s;
    background-color: #3498db;
    color: white;
}

button:hover {
    background-color: #2980b9;
    transform: translateY(-1px);
}

button:active {
    transform: translateY(0);
}

#stopBtn {
    background-color: #e74c3c;
}

#stopBtn:hover {
    background-color: #c0392b;
}

.video-container {
    margin-bottom: 1.5rem;
    border-radius: 8px;
    overflow: hidden;
    background-color: #000;
}

video {
    display: block;
    width: 100%;
    height: auto;
}

.controls-section {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
}

#volumeSlider {
    flex: 0 1 150px;
    cursor: pointer;
}

#volumeValue {
    min-width: 50px;
    text-align: center;
    font-size: 0.9rem;
    color: #666;
}

.info-section {
    background-color: #f8f9fa;
    padding: 1rem;
    border-radius: 6px;
    border-left: 4px solid #3498db;
}

#status {
    margin: 0;
    font-size: 0.95rem;
    color: #555;
}

[url=home.php?mod=space&uid=945662]@media[/url] (max-width: 768px) {
    .player-container {
        margin: 1rem;
        padding: 1rem;
    }
    
    .url-input-section {
        flex-direction: column;
    }
    
    .controls-section {
        flex-direction: column;
        align-items: stretch;
    }
    
    #volumeSlider {
        flex: none;
        width: 100%;
    }
}

/* 文件上传按钮样式 */
#uploadBtn {
    background-color: #27ae60;
}

#uploadBtn:hover {
    background-color: #229954;
}

/* 视频列表样式 */
.playlist-section {
    margin-bottom: 1.5rem;
}

.playlist-section h3 {
    color: #2c3e50;
    margin-bottom: 1rem;
    font-size: 1.2rem;
    font-weight: 600;
}

.playlist-container {
    max-height: 250px;
    overflow-y: auto;
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    background-color: #f8f9fa;
}

.playlist-item {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid #e0e0e0;
    cursor: pointer;
    transition: all 0.3s;
    font-size: 0.95rem;
    word-break: break-all;
}

.playlist-item:last-child {
    border-bottom: none;
}

.playlist-item:hover {
    background-color: #e3f2fd;
    transform: translateX(4px);
}

.playlist-item.active {
    background-color: #2196f3;
    color: white;
    font-weight: 500;
}

.playlist-item.active:hover {
    background-color: #1976d2;
    transform: translateX(4px);
}

/* 滚动条样式 */
.playlist-container::-webkit-scrollbar {
    width: 8px;
}

.playlist-container::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
}

.playlist-container::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 4px;
}

.playlist-container::-webkit-scrollbar-thumb:hover {
    background: #555;
}

/* 播放速度和画质选择样式 */
#speedSelect, #qualitySelect {
    padding: 0.75rem 1rem;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 1rem;
    background-color: white;
    cursor: pointer;
    transition: all 0.3s;
    min-width: 100px;
}

#speedSelect:hover, #qualitySelect:hover {
    border-color: #3498db;
    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
}

#speedSelect:focus, #qualitySelect:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}

/* 缓冲信息样式 */
.buffer-info {
    display: flex;
    gap: 1.5rem;
    margin-top: 0.5rem;
    font-size: 0.9rem;
    color: #666;
    flex-wrap: wrap;
}

.buffer-info span {
    background-color: #e8f4f8;
    padding: 0.25rem 0.75rem;
    border-radius: 12px;
    border: 1px solid #d1ecf1;
}

@media (max-width: 768px) {
    .buffer-info {
        flex-direction: column;
        gap: 0.5rem;
    }
    
    #speedSelect, #qualitySelect {
        min-width: auto;
    }
}
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-17 17:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表