|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function () { |
|
|
'use strict'; |
|
|
|
|
|
const processedUrls = new Set(); |
|
|
let currentHls = null; |
|
|
|
|
|
function createVideoPlayer() { |
|
|
|
|
|
const panelDiv = document.createElement('div'); |
|
|
panelDiv.className = 'stui-pannel clearfix'; |
|
|
|
|
|
|
|
|
const headDiv = document.createElement('div'); |
|
|
headDiv.className = 'stui-pannel__head clearfix'; |
|
|
|
|
|
|
|
|
const title = document.createElement('h3'); |
|
|
title.className = 'title'; |
|
|
title.textContent = '在线播放'; |
|
|
headDiv.appendChild(title); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
const wrapper = document.createElement('div'); |
|
|
wrapper.className = 'video-wrapper'; |
|
|
wrapper.style.cssText = 'width: 100%; position: relative; aspect-ratio: 16/9;'; |
|
|
|
|
|
|
|
|
const container = document.createElement('div'); |
|
|
container.className = 'custom-video-player'; |
|
|
container.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'; |
|
|
|
|
|
|
|
|
const video = document.createElement('video'); |
|
|
video.controls = true; |
|
|
video.style.cssText = 'width: 100%; height: 100%; object-fit: contain;'; |
|
|
|
|
|
|
|
|
container.appendChild(video); |
|
|
wrapper.appendChild(container); |
|
|
panelDiv.appendChild(headDiv); |
|
|
panelDiv.appendChild(wrapper); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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); |
|
|
episodes.push({ number, url }); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
if (episodes.length > 0) { |
|
|
|
|
|
episodes.sort((a, b) => Number(a.number) - Number(b.number)); |
|
|
|
|
|
|
|
|
const player = createVideoPlayer(); |
|
|
descDiv.parentNode.insertBefore(player.container, descDiv); |
|
|
|
|
|
|
|
|
episodes.forEach((episode, idx) => { |
|
|
const btn = document.createElement('button'); |
|
|
const episodeNum = (idx + 1).toString().padStart(2, '0'); |
|
|
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 = () => { |
|
|
|
|
|
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'; |
|
|
|
|
|
|
|
|
player.playUrl(episode.url); |
|
|
}; |
|
|
player.episodeList.appendChild(btn); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
player.playUrl(episodes[0].url); |
|
|
}, 500); |
|
|
} |
|
|
} |
|
|
|
|
|
if (document.readyState === 'loading') { |
|
|
document.addEventListener('DOMContentLoaded', init); |
|
|
} else { |
|
|
init(); |
|
|
} |
|
|
})(); |