// ==UserScript== // @name HeiMuer TV Video Player // @namespace http://tampermonkey.net/ // @version 0.3 // @description Add video player for HeiMuer TV // @author You // @match https://heimuer.tv/index.php/vod/detail/id/* // @grant GM_log // @require https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.10/hls.min.js // ==/UserScript== (function () { 'use strict'; const processedUrls = new Set(); let currentHls = null; function createVideoPlayer() { // Create main panel const panelDiv = document.createElement('div'); panelDiv.className = 'stui-pannel clearfix'; // Create header const headDiv = document.createElement('div'); headDiv.className = 'stui-pannel__head clearfix'; // Create title const title = document.createElement('h3'); title.className = 'title'; title.textContent = '在线播放'; headDiv.appendChild(title); // Create episode list container const episodeListDiv = document.createElement('div'); episodeListDiv.className = 'episode-list'; episodeListDiv.style.cssText = 'margin: 10px 0; display: flex; flex-wrap: wrap; gap: 5px;'; headDiv.appendChild(episodeListDiv); // Create video wrapper const wrapper = document.createElement('div'); wrapper.className = 'video-wrapper'; wrapper.style.cssText = 'width: 100%; position: relative; aspect-ratio: 16/9;'; // Create video container const container = document.createElement('div'); container.className = 'custom-video-player'; container.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'; // Create video element const video = document.createElement('video'); video.controls = true; video.style.cssText = 'width: 100%; height: 100%; object-fit: contain;'; // Assemble DOM container.appendChild(video); wrapper.appendChild(container); panelDiv.appendChild(headDiv); panelDiv.appendChild(wrapper); // Add loading indicator const loadingDiv = document.createElement('div'); loadingDiv.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.7); color: white; padding: 10px 20px; border-radius: 4px; display: none; `; loadingDiv.textContent = '加载中...'; container.appendChild(loadingDiv); return { container: panelDiv, episodeList: episodeListDiv, video: video, playUrl: function (url) { loadingDiv.style.display = 'block'; if (currentHls) { currentHls.destroy(); } const tryPlay = () => { video.play().catch(error => { console.log("Retrying autoplay..."); setTimeout(tryPlay, 1000); }); }; if (Hls.isSupported()) { currentHls = new Hls(); currentHls.loadSource(url); currentHls.attachMedia(video); currentHls.on(Hls.Events.MANIFEST_LOADED, () => { console.log("Manifest loaded"); }); currentHls.on(Hls.Events.MANIFEST_PARSED, () => { console.log("Manifest parsed"); loadingDiv.style.display = 'none'; tryPlay(); }); currentHls.on(Hls.Events.ERROR, (event, data) => { console.log("HLS error:", data); loadingDiv.style.display = 'none'; }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = url; video.addEventListener('loadedmetadata', () => { loadingDiv.style.display = 'none'; tryPlay(); }); } } }; } function init() { const descDiv = document.getElementById('desc'); if (!descDiv) return; // Parse all episodes const episodes = []; document.querySelectorAll('.hidden-xs').forEach(el => { const text = el.textContent || ''; if (text.includes('m3u8.heimuertv.com')) { const [number, fullUrl] = text.split('$'); const url = fullUrl.match(/https:\/\/m3u8\.heimuertv\.com\/play\/[\w\.]+\.m3u8/)?.[0]; if (url) { console.log('Episode number:', number); // Debug log episodes.push({ number, url }); } } }); if (episodes.length > 0) { // Sort episodes episodes.sort((a, b) => Number(a.number) - Number(b.number)); // Create player const player = createVideoPlayer(); descDiv.parentNode.insertBefore(player.container, descDiv); // Create episode buttons episodes.forEach((episode, idx) => { const btn = document.createElement('button'); const episodeNum = (idx + 1).toString().padStart(2, '0'); // Convert index to episode number btn.textContent = `第${episodeNum}集`; btn.style.cssText = ` padding: 5px 15px; border: 1px solid ${idx === 0 ? '#007bff' : '#ddd'}; background: ${idx === 0 ? '#007bff' : '#fff'}; color: ${idx === 0 ? '#fff' : '#333'}; border-radius: 3px; cursor: pointer; `; btn.onclick = () => { // Update button styles player.episodeList.querySelectorAll('button').forEach(b => { b.style.background = '#fff'; b.style.color = '#333'; b.style.borderColor = '#ddd'; }); btn.style.background = '#007bff'; btn.style.color = '#fff'; btn.style.borderColor = '#007bff'; // Play episode player.playUrl(episode.url); }; player.episodeList.appendChild(btn); }); // Play first episode // Trigger initial play with slight delay setTimeout(() => { player.playUrl(episodes[0].url); }, 500); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();