|
|
<!DOCTYPE html> |
|
|
<html lang="zh-CN"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
|
|
<title>Girls</title> |
|
|
<style> |
|
|
html, body { height: 100%; margin: 0; padding: 0; background: #111; color: #fff; } |
|
|
body { |
|
|
min-height: 100vh; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif; |
|
|
background: linear-gradient(135deg, #181c24 0%, #232526 60%, #23243a 100%); |
|
|
} |
|
|
#player-box { |
|
|
background: none; |
|
|
padding: 0; |
|
|
border-radius: 0; |
|
|
box-shadow: none; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
width: 100vw; |
|
|
max-width: 100vw; |
|
|
} |
|
|
.video-wrap { |
|
|
position: relative; |
|
|
width: 100vw; |
|
|
max-width: 420px; |
|
|
aspect-ratio: 9/16; |
|
|
background: |
|
|
radial-gradient(ellipse at 60% 20%, rgba(60,70,110,0.18) 0%, rgba(30,30,40,0.12) 60%, rgba(20,20,20,0.92) 100%), |
|
|
linear-gradient(135deg, rgba(40,44,60,0.85) 0%, rgba(30,30,40,0.92) 100%); |
|
|
border-radius: 28px; |
|
|
box-shadow: |
|
|
0 8px 32px 0 rgba(31, 38, 135, 0.18), |
|
|
0 2px 8px 0 rgba(0,0,0,0.18), |
|
|
0 0 0 4px rgba(80,120,255,0.08) inset, |
|
|
0 1.5px 8px 0 rgba(0,0,0,0.10); |
|
|
overflow: hidden; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
backdrop-filter: blur(3.5px) saturate(1.15); |
|
|
} |
|
|
.video-wrap::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
inset: 0; |
|
|
border-radius: 28px; |
|
|
pointer-events: none; |
|
|
box-shadow: |
|
|
0 0 32px 8px rgba(255,255,255,0.08) inset, |
|
|
0 0 0 2px rgba(255,255,255,0.10) inset; |
|
|
} |
|
|
.video-fade { |
|
|
opacity: 0; |
|
|
transition: opacity 0.4s; |
|
|
} |
|
|
.video-fade-in { |
|
|
opacity: 1 !important; |
|
|
transition: opacity 0.4s; |
|
|
} |
|
|
.loading-spinner { |
|
|
position: absolute; |
|
|
left: 50%; |
|
|
top: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
width: 48px; |
|
|
height: 48px; |
|
|
border: 4px solid rgba(255,255,255,0.18); |
|
|
border-top: 4px solid #fff; |
|
|
border-radius: 50%; |
|
|
animation: spin 1s linear infinite; |
|
|
z-index: 20; |
|
|
background: none; |
|
|
pointer-events: none; |
|
|
} |
|
|
@keyframes spin { |
|
|
0% { transform: translate(-50%, -50%) rotate(0deg); } |
|
|
100% { transform: translate(-50%, -50%) rotate(360deg); } |
|
|
} |
|
|
.load-fail-tip { |
|
|
position: absolute; |
|
|
left: 50%; |
|
|
top: 60%; |
|
|
transform: translate(-50%, -50%); |
|
|
background: rgba(30,30,40,0.92); |
|
|
color: #fff; |
|
|
padding: 12px 24px; |
|
|
border-radius: 14px; |
|
|
font-size: 15px; |
|
|
z-index: 21; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.12); |
|
|
display: none; |
|
|
} |
|
|
video { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
border-radius: 20px; |
|
|
background: #111; |
|
|
display: block; |
|
|
object-fit: cover; |
|
|
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.10); |
|
|
transition: box-shadow 0.2s; |
|
|
} |
|
|
.controls { |
|
|
position: absolute; |
|
|
bottom: 60px; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
display: flex; |
|
|
flex-direction: row; |
|
|
justify-content: center; |
|
|
gap: 8px; |
|
|
padding: 0; |
|
|
background: none; |
|
|
box-sizing: border-box; |
|
|
opacity: 0; |
|
|
pointer-events: none; |
|
|
transition: opacity 0.3s, transform 0.3s; |
|
|
z-index: 10; |
|
|
transform: translateY(20px); |
|
|
} |
|
|
.info { |
|
|
position: absolute; |
|
|
top: 10px; |
|
|
left: 14px; |
|
|
color: #fff; |
|
|
font-size: 13px; |
|
|
background: rgba(0,0,0,0.05); |
|
|
padding: 2px 10px; |
|
|
border-radius: 12px; |
|
|
z-index: 2; |
|
|
pointer-events: none; |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s, transform 0.3s; |
|
|
transform: translateY(-10px); |
|
|
} |
|
|
.video-wrap:hover .controls { |
|
|
opacity: 1; |
|
|
pointer-events: auto; |
|
|
transform: translateY(0); |
|
|
} |
|
|
.video-wrap:hover .info { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
@media (max-width: 600px) { |
|
|
html, body { |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
min-height: 100vh; |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
overflow: hidden; |
|
|
border-radius: 0 !important; |
|
|
} |
|
|
.video-wrap { |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
max-width: 100vw; |
|
|
max-height: 100vh; |
|
|
border-radius: 0 !important; |
|
|
box-shadow: none; |
|
|
aspect-ratio: unset; |
|
|
} |
|
|
.video-wrap::before { |
|
|
box-shadow: none !important; |
|
|
} |
|
|
video { |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
border-radius: 0 !important; |
|
|
object-fit: cover; |
|
|
} |
|
|
#player-box { |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
max-width: 100vw; |
|
|
max-height: 100vh; |
|
|
border-radius: 0 !important; |
|
|
} |
|
|
.controls { |
|
|
bottom: 15%; |
|
|
left: 0; |
|
|
width: 100vw; |
|
|
justify-content: center; |
|
|
padding-bottom: env(safe-area-inset-bottom, 0); |
|
|
} |
|
|
button { |
|
|
font-size: 15px; |
|
|
padding: 10px 0; |
|
|
width: 36vw; |
|
|
max-width: 120px; |
|
|
} |
|
|
.info { |
|
|
font-size: 12px; |
|
|
left: 8px; |
|
|
top: 8px; |
|
|
} |
|
|
} |
|
|
@media (hover: none) and (pointer: coarse) { |
|
|
.controls { |
|
|
opacity: 1 !important; |
|
|
pointer-events: auto !important; |
|
|
} |
|
|
.info { |
|
|
opacity: 1 !important; |
|
|
} |
|
|
} |
|
|
button { |
|
|
padding: 6px 0; |
|
|
width: 28vw; |
|
|
max-width: 80px; |
|
|
font-size: 13px; |
|
|
border: none; |
|
|
border-radius: 14px; |
|
|
background: rgba(40,40,40,0.05); |
|
|
color: #fff; |
|
|
cursor: pointer; |
|
|
transition: background 0.2s; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
button:active { |
|
|
background: rgba(64,158,255,0.05); |
|
|
} |
|
|
.guide-tip { |
|
|
position: fixed; |
|
|
top: 18%; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
background: rgba(30,30,40,0.92); |
|
|
color: #fff; |
|
|
padding: 18px 28px; |
|
|
border-radius: 18px; |
|
|
font-size: 17px; |
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.18); |
|
|
z-index: 9999; |
|
|
opacity: 0; |
|
|
pointer-events: none; |
|
|
transition: opacity 0.5s; |
|
|
} |
|
|
.guide-tip.show { |
|
|
opacity: 1; |
|
|
pointer-events: auto; |
|
|
} |
|
|
#blur-bg-canvas { |
|
|
position: fixed; |
|
|
left: 0; top: 0; width: 100vw; height: 100vh; |
|
|
z-index: 0; |
|
|
pointer-events: none; |
|
|
filter: blur(36px) brightness(0.82) saturate(1.18) hue-rotate(-6deg); |
|
|
opacity: 0.82; |
|
|
transition: opacity 0.5s; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div id="player-box"> |
|
|
<div class="video-wrap"> |
|
|
<video id="videoPlayer" controls autoplay playsinline webkit-playsinline> |
|
|
<source id="videoSource" src="" type="video/mp4"> |
|
|
您的浏览器不支持 video 标签。 |
|
|
</video> |
|
|
<div class="loading-spinner" id="loadingSpinner" style="display:none;"></div> |
|
|
<div class="load-fail-tip" id="loadFailTip">视频加载失败,已自动切换下一个</div> |
|
|
<div class="controls"> |
|
|
<button id="loopBtn" aria-label="切换循环模式" tabindex="0"> |
|
|
<span id="loopText">loop:关</span> |
|
|
</button> |
|
|
<button id="nextBtn" aria-label="下一个视频" tabindex="0"> |
|
|
<span style="vertical-align:middle;display:inline-block;width:18px;height:18px;line-height:0;"> |
|
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> |
|
|
</span> |
|
|
<span style="margin-left:4px;">Next</span> |
|
|
</button> |
|
|
</div> |
|
|
<div class="info" id="videoInfo"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="guide-tip" id="guideTip">手机点击屏幕可切换视频,长按可切换循环模式</div> |
|
|
<script> |
|
|
|
|
|
let videoList = []; |
|
|
let currentIndex = 0; |
|
|
const videoPlayer = document.getElementById('videoPlayer'); |
|
|
const videoSource = document.getElementById('videoSource'); |
|
|
const loopBtn = document.getElementById('loopBtn'); |
|
|
const nextBtn = document.getElementById('nextBtn'); |
|
|
const videoInfo = document.getElementById('videoInfo'); |
|
|
const loadingSpinner = document.getElementById('loadingSpinner'); |
|
|
const loadFailTip = document.getElementById('loadFailTip'); |
|
|
const loopText = document.getElementById('loopText'); |
|
|
|
|
|
function showLoading() { |
|
|
loadingSpinner.style.display = 'block'; |
|
|
} |
|
|
function hideLoading() { |
|
|
loadingSpinner.style.display = 'none'; |
|
|
} |
|
|
function showFailTip() { |
|
|
loadFailTip.style.display = 'block'; |
|
|
setTimeout(() => { loadFailTip.style.display = 'none'; }, 2000); |
|
|
} |
|
|
|
|
|
|
|
|
fetch('urls.json').then(r => r.json()).then(list => { |
|
|
videoList = list; |
|
|
if (videoList.length === 0) { |
|
|
videoInfo.textContent = '未找到可播放的视频。'; |
|
|
videoPlayer.style.display = 'none'; |
|
|
nextBtn.disabled = true; |
|
|
loopBtn.disabled = true; |
|
|
return; |
|
|
} |
|
|
|
|
|
let initialIdx = 0; |
|
|
if (videoList.length > 1) { |
|
|
initialIdx = Math.floor(Math.random() * videoList.length); |
|
|
} |
|
|
updateVideo(initialIdx); |
|
|
}); |
|
|
|
|
|
function updateVideo(idx) { |
|
|
if (videoList.length === 0) return; |
|
|
currentIndex = idx; |
|
|
videoPlayer.classList.remove('video-fade-in'); |
|
|
videoPlayer.classList.add('video-fade'); |
|
|
setTimeout(() => { |
|
|
videoSource.src = videoList[currentIndex]; |
|
|
videoPlayer.load(); |
|
|
videoInfo.textContent = `当前视频:${currentIndex + 1} / ${videoList.length}`; |
|
|
}, 200); |
|
|
} |
|
|
videoPlayer.addEventListener('canplay', function() { |
|
|
videoPlayer.classList.remove('video-fade'); |
|
|
videoPlayer.classList.add('video-fade-in'); |
|
|
}); |
|
|
videoSource.addEventListener('error', function() { |
|
|
setTimeout(() => { |
|
|
let nextIdx = getRandomNextIndex(); |
|
|
updateVideo(nextIdx); |
|
|
}, 200); |
|
|
}); |
|
|
loopBtn.onclick = function() { |
|
|
videoPlayer.loop = !videoPlayer.loop; |
|
|
loopText.textContent = 'loop:' + (videoPlayer.loop ? '开' : '关'); |
|
|
}; |
|
|
function getRandomNextIndex() { |
|
|
if (videoList.length <= 1) return 0; |
|
|
let nextIdx; |
|
|
do { |
|
|
nextIdx = Math.floor(Math.random() * videoList.length); |
|
|
} while (nextIdx === currentIndex); |
|
|
return nextIdx; |
|
|
} |
|
|
nextBtn.onclick = function() { |
|
|
if (videoList.length === 0) return; |
|
|
let nextIdx = getRandomNextIndex(); |
|
|
updateVideo(nextIdx); |
|
|
}; |
|
|
videoPlayer.addEventListener('ended', function() { |
|
|
if (!videoPlayer.loop) { |
|
|
let nextIdx = getRandomNextIndex(); |
|
|
updateVideo(nextIdx); |
|
|
} |
|
|
}); |
|
|
|
|
|
videoPlayer.loop = false; |
|
|
loopText.textContent = 'loop:关'; |
|
|
|
|
|
let initialIdx = 0; |
|
|
if (videoList.length > 1) { |
|
|
initialIdx = Math.floor(Math.random() * videoList.length); |
|
|
} |
|
|
updateVideo(initialIdx); |
|
|
|
|
|
|
|
|
function isMobile() { |
|
|
return /Mobi|Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent); |
|
|
} |
|
|
if (isMobile()) { |
|
|
const controls = document.querySelector('.controls'); |
|
|
const info = document.querySelector('.info'); |
|
|
let hideTimer = null; |
|
|
function showControls() { |
|
|
controls.style.opacity = '1'; |
|
|
controls.style.pointerEvents = 'auto'; |
|
|
info.style.opacity = '1'; |
|
|
if (hideTimer) clearTimeout(hideTimer); |
|
|
hideTimer = setTimeout(() => { |
|
|
controls.style.opacity = '0'; |
|
|
controls.style.pointerEvents = 'none'; |
|
|
info.style.opacity = '0'; |
|
|
}, 3000); |
|
|
} |
|
|
document.querySelector('.video-wrap').addEventListener('touchstart', showControls); |
|
|
document.querySelector('.video-wrap').addEventListener('click', showControls); |
|
|
|
|
|
setTimeout(() => { |
|
|
controls.style.opacity = '0'; |
|
|
controls.style.pointerEvents = 'none'; |
|
|
info.style.opacity = '0'; |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!isMobile()) { |
|
|
window.addEventListener('keydown', function(e) { |
|
|
if (e.target.tagName.toLowerCase() === 'input' || e.target.tagName.toLowerCase() === 'textarea') return; |
|
|
if (e.code === 'ArrowRight' || e.key === 'ArrowRight') { |
|
|
let nextIdx = getRandomNextIndex(); |
|
|
updateVideo(nextIdx); |
|
|
} else if (e.code === 'ArrowLeft' || e.key === 'ArrowLeft') { |
|
|
let nextIdx = getRandomNextIndex(); |
|
|
updateVideo(nextIdx); |
|
|
} else if (e.code === 'Space' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
if (videoPlayer.paused) videoPlayer.play(); |
|
|
else videoPlayer.pause(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
loopBtn.addEventListener('keydown', function(e) { |
|
|
if (e.key === 'Enter' || e.keyCode === 13) { |
|
|
loopBtn.click(); |
|
|
} |
|
|
}); |
|
|
nextBtn.addEventListener('keydown', function(e) { |
|
|
if (e.key === 'Enter' || e.keyCode === 13) { |
|
|
nextBtn.click(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
if (!isMobile()) { |
|
|
|
|
|
let blurBgCanvas = document.getElementById('blur-bg-canvas'); |
|
|
if (!blurBgCanvas) { |
|
|
blurBgCanvas = document.createElement('canvas'); |
|
|
blurBgCanvas.id = 'blur-bg-canvas'; |
|
|
document.body.insertBefore(blurBgCanvas, document.body.firstChild); |
|
|
} |
|
|
function updateBlurBg() { |
|
|
try { |
|
|
|
|
|
const w = window.innerWidth, h = window.innerHeight; |
|
|
const scale = window.devicePixelRatio > 1.5 ? 0.25 : 0.33; |
|
|
blurBgCanvas.width = Math.round(w * scale); |
|
|
blurBgCanvas.height = Math.round(h * scale); |
|
|
const ctx = blurBgCanvas.getContext('2d'); |
|
|
ctx.drawImage(videoPlayer, 0, 0, blurBgCanvas.width, blurBgCanvas.height); |
|
|
} catch(e) {} |
|
|
} |
|
|
let blurInterval = 50; |
|
|
let blurTimer = null; |
|
|
videoPlayer.addEventListener('play', function() { |
|
|
if(blurTimer) clearInterval(blurTimer); |
|
|
blurTimer = setInterval(updateBlurBg, blurInterval); |
|
|
}); |
|
|
videoPlayer.addEventListener('pause', function(){ |
|
|
if(blurTimer) clearInterval(blurTimer); |
|
|
}); |
|
|
videoPlayer.addEventListener('ended', function(){ |
|
|
if(blurTimer) clearInterval(blurTimer); |
|
|
}); |
|
|
window.addEventListener('resize', updateBlurBg); |
|
|
|
|
|
setTimeout(updateBlurBg, blurInterval); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |