// ==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 = `
00:00 / 00:00
`; 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 }); })();