[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);