// ==UserScript==
// @name M3U8 在线播放器
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 自动检测并播放页面中的 m3u8 视频流链接,支持 Hls.js
// @author WorkBuddy
// @match *://*/*
// @grant none
// @run-at document-end
// @require https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.min.js
// @icon https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.js.png
// ==/UserScript==
(function() {
'use strict';
// 配置
const CONFIG = {
autoPlay: true, // 自动播放
defaultVolume: 0.8, // 默认音量
enableHotkeys: true, // 启用快捷键
showOverlay: true, // 显示覆盖层按钮
detectAllLinks: true, // 检测所有链接
maxRetries: 3 // 最大重试次数
};
// 快捷键映射
const HOTKEYS = {
'Space': 'togglePlay',
'ArrowUp': 'volumeUp',
'ArrowDown': 'volumeDown',
'ArrowLeft': 'seekBackward',
'ArrowRight': 'seekForward',
'f': 'toggleFullscreen',
'm': 'toggleMute'
};
let hls = null;
let currentPlayer = null;
let playerContainer = null;
/**
* 初始化播放器
*/
function init() {
console.log('[M3U8 Player] 插件已加载');
// 等待 Hls.js 加载
if (typeof Hls === 'undefined') {
console.error('[M3U8 Player] Hls.js 未加载');
return;
}
// 添加样式
addStyles();
// 添加控制按钮
if (CONFIG.showOverlay) {
addControlButton();
}
// 自动检测页面中的 m3u8 链接
if (CONFIG.detectAllLinks) {
detectM3U8Links();
}
// 添加全局快捷键监听
if (CONFIG.enableHotkeys) {
addHotkeyListeners();
}
}
/**
* 添加自定义样式
*/
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.m3u8-player-control-btn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 99999;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 50px;
padding: 12px 24px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.m3u8-player-control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.m3u8-player-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999999;
background: #000;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
max-width: 90vw;
max-height: 90vh;
}
.m3u8-player-container video {
max-width: 100%;
max-height: 80vh;
display: block;
}
.m3u8-player-controls {
background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%);
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.m3u8-progress-bar {
width: 100%;
height: 6px;
background: rgba(255,255,255,0.2);
border-radius: 3px;
cursor: pointer;
position: relative;
}
.m3u8-progress-bar:hover {
height: 8px;
}
.m3u8-progress-bar .progress {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 3px;
width: 0%;
transition: width 0.1s linear;
}
.m3u8-control-buttons {
display: flex;
align-items: center;
justify-content: space-between;
}
.m3u8-btn {
background: transparent;
border: none;
color: white;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: all 0.2s ease;
}
.m3u8-btn:hover {
background: rgba(255,255,255,0.1);
}
.m3u8-btn svg {
width: 24px;
height: 24px;
fill: currentColor;
}
.m3u8-url-input {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.m3u8-url-input input {
flex: 1;
padding: 10px 15px;
border: 2px solid #667eea;
border-radius: 8px;
background: rgba(255,255,255,0.1);
color: white;
font-size: 14px;
}
.m3u8-url-input input:focus {
outline: none;
border-color: #764ba2;
}
.m3u8-url-input button {
padding: 10px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
.m3u8-close-btn {
position: absolute;
top: 15px;
right: 15px;
background: rgba(0,0,0,0.5);
border: none;
color: white;
}
.m3u8-time-display {
color: white;
font-size: 14px;
font-weight: 500;
}
.m3u8-volume-control {
display: flex;
align-items: center;
gap: 8px;
color: white;
}
.m3u8-volume-slider {
width: 80px;
height: 4px;
-webkit-appearance: none;
background: rgba(255,255,255,0.2);
border-radius: 2px;
outline: none;
}
.m3u8-volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
cursor: pointer;
}
.m3u8-link-highlight {
outline: 3px solid #667eea !important;
outline-offset: 2px;
cursor: pointer !important;
}
@keyframes m3u8Pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.m3u8-playing .m3u8-player-control-btn {
animation: m3u8Pulse 2s infinite;
}
`;
document.head.appendChild(style);
}
/**
* 添加控制按钮
*/
function addControlButton() {
const btn = document.createElement('button');
btn.className = 'm3u8-player-control-btn';
btn.innerHTML = `
播放 M3U8
`;
btn.onclick = () => showPlayerDialog();
document.body.appendChild(btn);
}
/**
* 显示播放器对话框
*/
function showPlayerDialog(url = '') {
if (playerContainer) {
playerContainer.remove();
}
playerContainer = document.createElement('div');
playerContainer.className = 'm3u8-player-container';
playerContainer.innerHTML = `
`;
document.body.appendChild(playerContainer);
// 绑定事件
bindPlayerEvents();
// 如果有 URL,自动播放
if (url) {
loadM3U8(url);
}
// 绑定全局函数
window.playM3U8FromInput = () => {
const urlInput = document.getElementById('m3u8-url');
if (urlInput && urlInput.value) {
loadM3U8(urlInput.value);
}
};
}
/**
* 加载并播放 M3U8
*/
function loadM3U8(url, retryCount = 0) {
const video = document.getElementById('m3u8-video');
if (!video) return;
// 清理之前的实例
if (hls) {
hls.destroy();
hls = null;
}
// 检测浏览器原生支持
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
if (CONFIG.autoPlay) {
video.play().catch(e => console.log('自动播放被阻止,用户需要手动播放'));
}
return;
}
// 使用 Hls.js
if (Hls.isSupported()) {
hls = new Hls({
debug: false,
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90
});
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
console.log('[M3U8 Player] 清单解析成功,找到 ' + data.levels.length + ' 个画质等级');
if (CONFIG.autoPlay) {
video.play().catch(e => console.log('自动播放被阻止,用户需要手动播放'));
}
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error('[M3U8 Player] 错误:', data);
if (data.fatal) {
if (retryCount < CONFIG.maxRetries) {
console.log(`[M3U8 Player] 重试加载 (${retryCount + 1}/${CONFIG.maxRetries})`);
setTimeout(() => loadM3U8(url, retryCount + 1), 2000);
} else {
alert('播放失败: ' + (data.details || '未知错误'));
}
}
});
currentPlayer = video;
} else {
alert('您的浏览器不支持 HLS 播放');
}
}
/**
* 绑定播放器事件
*/
function bindPlayerEvents() {
const video = document.getElementById('m3u8-video');
const progressBar = document.getElementById('m3u8-progress');
const progressFill = document.getElementById('m3u8-progress-fill');
const playBtn = document.getElementById('m3u8-play-btn');
const muteBtn = document.getElementById('m3u8-mute-btn');
const volumeSlider = document.getElementById('m3u8-volume');
const fullscreenBtn = document.getElementById('m3u8-fullscreen-btn');
const backwardBtn = document.getElementById('m3u8-backward-btn');
const forwardBtn = document.getElementById('m3u8-forward-btn');
const currentTimeEl = document.getElementById('m3u8-current-time');
const durationEl = document.getElementById('m3u8-duration');
if (!video) return;
// 设置默认音量
video.volume = CONFIG.defaultVolume;
// 播放/暂停按钮
playBtn.onclick = () => togglePlay();
// 进度条点击
progressBar.onclick = (e) => {
const rect = progressBar.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width;
video.currentTime = percent * video.duration;
};
// 更新进度条
video.ontimeupdate = () => {
if (video.duration) {
const percent = (video.currentTime / video.duration) * 100;
progressFill.style.width = percent + '%';
currentTimeEl.textContent = formatTime(video.currentTime);
durationEl.textContent = formatTime(video.duration);
}
};
// 静音按钮
muteBtn.onclick = () => {
video.muted = !video.muted;
muteBtn.innerHTML = video.muted ?
'' :
'';
};
// 音量滑块
volumeSlider.oninput = () => {
video.volume = volumeSlider.value;
video.muted = false;
};
// 全屏按钮
fullscreenBtn.onclick = () => {
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
}
};
// 后退10秒
backwardBtn.onclick = () => {
video.currentTime = Math.max(0, video.currentTime - 10);
};
// 前进10秒
forwardBtn.onclick = () => {
video.currentTime = Math.min(video.duration, video.currentTime + 10);
};
}
/**
* 播放/暂停切换
*/
function togglePlay() {
const video = document.getElementById('m3u8-video');
if (!video) return;
if (video.paused) {
video.play();
document.getElementById('m3u8-play-btn').innerHTML =
'';
} else {
video.pause();
document.getElementById('m3u8-play-btn').innerHTML =
'';
}
}
/**
* 添加快捷键监听
*/
function addHotkeyListeners() {
document.addEventListener('keydown', (e) => {
if (!playerContainer || !playerContainer.isConnected) return;
const video = document.getElementById('m3u8-video');
if (!video) return;
const action = HOTKEYS[e.code];
if (!action) return;
e.preventDefault();
const volumeSlider = document.getElementById('m3u8-volume');
switch (action) {
case 'togglePlay':
togglePlay();
break;
case 'volumeUp':
video.volume = Math.min(1, video.volume + 0.1);
if (volumeSlider) volumeSlider.value = video.volume;
break;
case 'volumeDown':
video.volume = Math.max(0, video.volume - 0.1);
if (volumeSlider) volumeSlider.value = video.volume;
break;
case 'seekBackward':
video.currentTime = Math.max(0, video.currentTime - 5);
break;
case 'seekForward':
video.currentTime = Math.min(video.duration, video.currentTime + 5);
break;
case 'toggleFullscreen':
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
}
break;
case 'toggleMute':
video.muted = !video.muted;
break;
}
});
}
/**
* 检测页面中的 m3u8 链接
*/
function detectM3U8Links() {
// 检测所有链接
const links = document.querySelectorAll('a[href]');
links.forEach(link => {
const href = link.getAttribute('href');
if (href && href.includes('.m3u8')) {
link.classList.add('m3u8-link-highlight');
link.title = '点击使用 M3U8 播放器播放';
link.onclick = (e) => {
e.preventDefault();
showPlayerDialog(href);
};
}
});
// 检测页面文本中的 m3u8 链接
const bodyText = document.body.innerText;
const m3u8Regex = /(https?:\/\/[^\s]+\.m3u8(?:\?[^\s]*)?)/gi;
const matches = bodyText.match(m3u8Regex);
if (matches) {
console.log(`[M3U8 Player] 检测到 ${matches.length} 个 M3U8 链接`);
// 可以添加更多处理逻辑,比如在页面中添加播放按钮
}
}
/**
* 格式化时间
*/
function formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '00:00';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// 监听 DOM 变化,动态检测新添加的 m3u8 链接
const observer = new MutationObserver(() => {
if (CONFIG.detectAllLinks) {
detectM3U8Links();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();