⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.124
Server IP:
50.28.103.30
Server:
Linux host.jcukjv-lwsites.com 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64
Server Software:
nginx/1.28.0
PHP Version:
8.3.12
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
www
/
wwwroot
/
ef.electronharmony.com
/
1
/
T12
/
View File Name :
index.php
<?php // index.php — PHP 版:扫描 Video/ 生成播放清单 + 输出完整前端样式 // 用法: // 1) 把本文件放到站点根目录(与 Video/ 同级) // 2) 访问 http://localhost/index.php ;若需 JSON 清单:?manifest=1 mb_internal_encoding('UTF-8'); $root = __DIR__; $videoDir = $root . DIRECTORY_SEPARATOR . 'Video'; $allowExt = ['mp4','webm','m4v']; function walkVideos($baseDir, $allowExt) { $out = []; if (!is_dir($baseDir)) return $out; $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS)); foreach ($it as $f) { /** @var SplFileInfo $f */ if ($f->isDir()) continue; $ext = strtolower($f->getExtension()); if (!in_array($ext, $allowExt, true)) continue; $abs = $f->getPathname(); $rel = str_replace($baseDir . DIRECTORY_SEPARATOR, '', $abs); $rel = str_replace(DIRECTORY_SEPARATOR, '/', $rel); $basename = pathinfo($rel, PATHINFO_BASENAME); $title = preg_replace('/\.[^.]+$/', '', $basename); // URL 需逐段编码,避免中文/空格问题 $url = 'Video/' . implode('/', array_map('rawurlencode', explode('/', $rel))); $out[] = [ 'title' => $title, 'artist' => '', 'video' => $url, 'tracks' => [ ['name' => '视频原声', 'src' => ''] ], // 空 src 表示直接用 <video> 自带音频 'top' => $title, 'bottom' => '' ]; } return $out; } $playlist = walkVideos($videoDir, $allowExt); if (empty($playlist)) { $playlist = [[ 'title' => '示例视频', 'artist' => '', 'video' => 'https://filesamples.com/samples/video/mp4/sample_640x360.mp4', 'tracks' => [ ['name' => '视频原声', 'src' => ''] ], 'top' => '示例视频', 'bottom' => '' ]]; } // 仅输出 JSON 清单(供其它前端使用) if (isset($_GET['manifest'])) { header('Content-Type: application/json; charset=utf-8'); echo json_encode($playlist, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); exit; } ?> <!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>KTV(PHP 扫描 Video/)— 带音轨选择</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" /> <style> body{background:#000;color:#fff} .screen{position:relative;max-width:1200px;margin:40px auto;border-radius:16px;overflow:hidden;box-shadow:0 0 30px rgba(0,0,0,.6);aspect-ratio:16/9;} .screen.expanded{position:fixed;inset:0;margin:0;max-width:none;border-radius:0;z-index:9999} video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:brightness(.75);z-index:0} .lyric{position:absolute;bottom:max(4vh, env(safe-area-inset-bottom) + 16px);left:50%;transform:translateX(-50%);width:min(92%,1100px);text-align:center;font-weight:800;text-shadow:0 2px 16px rgba(0,0,0,.85);z-index:2} .lyric .top{color:#9fd4ff;font-size:clamp(18px,3.2vw,40px)} .lyric .bottom{color:#fff;font-size:clamp(20px,3.6vw,46px)} .control-bar{position:absolute;right:12px;bottom:12px;display:flex;gap:12px;align-items:center;padding:8px 12px;border-radius:999px;background:rgba(20,20,28,.35);backdrop-filter:blur(8px);z-index:5;border:1px solid rgba(255,255,255,.2)} .cbtn{display:grid;place-items:center;width:40px;height:40px;border-radius:10px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.08);color:#fff;cursor:pointer} .cbtn:hover{background:rgba(255,255,255,.16)} .track-menu{position:absolute;right:12px;bottom:70px;min-width:220px;background:rgba(12,12,16,.8);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.18);border-radius:14px;overflow:hidden;z-index:6;display:none} .track-menu.show{display:block} .track-menu .item{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer} .track-menu .item:hover{background:rgba(255,255,255,.08)} .track-menu .item.active{background:rgba(111,139,255,.25)} .track-menu .badge{margin-left:auto} .dbg{max-width:1200px;margin:10px auto 0;font:12px/1.4 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;color:#9ecbff;opacity:.9} </style> </head> <body> <div class="screen" id="screen"> <video id="bgVideo" autoplay muted loop playsinline></video> <audio id="audioMain" preload="auto" crossorigin="anonymous"></audio> <div class="lyric" id="lyricBox"> <span class="top">在夜色中等待</span> <span class="bottom">把思念唱成海</span> </div> <div class="control-bar"> <button id="btnPlay" class="cbtn" title="播放/暂停"><i class="bi bi-play-fill" id="iconPlay"></i></button> <button id="btnPrev" class="cbtn" title="上一首"><i class="bi bi-skip-backward-fill"></i></button> <button id="btnNext" class="cbtn" title="下一首"><i class="bi bi-skip-forward-fill"></i></button> <button id="btnTrack" class="cbtn" title="选择音轨"><i class="bi bi-music-note-beamed"></i></button> <button id="btnFS" class="cbtn" title="放大/还原"><i class="bi bi-arrows-fullscreen" id="iconFS"></i></button> </div> <div class="track-menu" id="trackMenu"></div> </div> <div class="dbg" id="dbg"></div> <!-- 以安全方式嵌入 JSON,避免在 <script> 中直接插入对象导致的解析冲突(Unexpected token '<') --> <script id="playlist-data" type="application/json"><?php echo json_encode($playlist, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); ?></script> <script> const screenEl = document.getElementById('screen'); const videoEl = document.getElementById('bgVideo'); const audioMain = document.getElementById('audioMain'); const iconPlay = document.getElementById('iconPlay'); const iconFS = document.getElementById('iconFS'); const topLine = document.querySelector('.top'); const bottomLine = document.querySelector('.bottom'); const dbg = document.getElementById('dbg'); // 从 <script type="application/json"> 安全读取播放清单 let PLAYLIST = []; try { const raw = document.getElementById('playlist-data').textContent; PLAYLIST = JSON.parse(raw); } catch(e){ console.error('PLAYLIST 解析失败:', e); PLAYLIST = []; } // 兜底 BEEP(保留但不强制使用) const BEEP = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACAA8AaW1wbwAAACQAAABAQEBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ"; let index = 0; let trackIndex = 0; let useVideoAudio = false; // 选中音轨为空时用 <video> 原声 function log(s){ dbg.innerText = s + "\n" + dbg.innerText; } function renderTrackMenu(tracks){ const menu = document.getElementById('trackMenu'); menu.innerHTML = (tracks||[]).map((t,i)=> `<div class="item ${i===trackIndex?'active':''}" data-i="${i}">`+ `<i class="bi ${i===0?'bi-mic':'bi-music-note'}"></i><span>${t.name||'音轨'+(i+1)}</span>`+ `${i===trackIndex?'<span class="badge bg-primary">当前</span>':''}`+ `</div>` ).join(''); } // WebAudio 回退 let audioCtx=null,osc=null,gainNode=null,beepMode=false; function startBeep(){ try{ if(!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)(); stopBeep(); osc = audioCtx.createOscillator(); gainNode = audioCtx.createGain(); osc.type='sine'; osc.frequency.value=440; gainNode.gain.value=0.08; osc.connect(gainNode).connect(audioCtx.destination); osc.start(); beepMode=true; }catch(e){ log('⚠️ WebAudio 失败:'+e.message); } } function stopBeep(){ try{ if(osc){osc.stop();osc.disconnect();} if(gainNode){gainNode.disconnect();} }catch(_){ } osc=null; gainNode=null; beepMode=false; } async function switchTrack(i){ const item = PLAYLIST[index] || {}; const tracks = item.tracks || []; if(!tracks[i]) return; const wasPlaying = !videoEl.paused; const t = videoEl.currentTime || 0; trackIndex = i; const src = (tracks[i].src||'').trim(); if(!src){ // 使用视频原声 useVideoAudio = true; stopBeep(); audioMain.pause(); audioMain.src = ''; videoEl.muted = false; try{ videoEl.currentTime = t; }catch(_){ } if(wasPlaying) try{ await videoEl.play(); }catch(_){ } }else{ // 使用独立音轨 useVideoAudio = false; videoEl.muted = true; stopBeep(); audioMain.src = src; try{ audioMain.currentTime = t; }catch(_){ } if(wasPlaying){ try { await audioMain.play(); } catch(err){ if(src.startsWith('data:audio/wav')) startBeep(); } } } renderTrackMenu(tracks); log(`🎚️ 切换音轨:${tracks[i].name || '(视频原声)'}`); } function loadTrack(i){ const item = PLAYLIST[i]; if(!item){ log('❗ 无可加载曲目'); return; } topLine.textContent = item.top || item.title || ''; bottomLine.textContent = item.bottom || ''; videoEl.src = item.video || ''; trackIndex = 0; const first = (item.tracks && item.tracks[0] && (item.tracks[0].src||'').trim()) || ''; if(!first){ // 默认使用视频自带音频 useVideoAudio = true; videoEl.muted = false; stopBeep(); audioMain.pause(); audioMain.src = ''; }else{ useVideoAudio = false; videoEl.muted = true; stopBeep(); audioMain.src = first; audioMain.currentTime = 0; } renderTrackMenu(item.tracks || []); log(`🎵 已载入:${item.title}`); } // 监听与控件 function mediaErrorText(t){ const e=t.error; if(!e) return '未知'; const m={1:'ABORTED',2:'NETWORK',3:'DECODE',4:'SRC_NOT_SUPPORTED'}; return (m[e.code]||e.code)+ (e.message? (': '+e.message):''); } videoEl.addEventListener('error', ()=> log('❌ 视频错误:'+mediaErrorText(videoEl)+' | '+videoEl.currentSrc)); audioMain.addEventListener('error', ()=>{ log('❌ 音频错误:'+mediaErrorText(audioMain)+' | '+(audioMain.currentSrc||audioMain.src)); if((audioMain.currentSrc||'').startsWith('data:audio/wav')) startBeep(); }); videoEl.addEventListener('loadedmetadata', ()=> log('✅ 视频就绪:'+Math.round(videoEl.duration||0)+'s')); document.getElementById('btnPlay').onclick = async ()=>{ if(useVideoAudio){ if(videoEl.paused){ try{ await videoEl.play(); iconPlay.className='bi bi-pause-fill'; } catch(_){ log('⚠️ 自动播放被阻止'); } }else{ videoEl.pause(); iconPlay.className='bi bi-play-fill'; } } else { if(videoEl.paused){ try{ await videoEl.play(); await audioMain.play(); iconPlay.className='bi bi-pause-fill'; } catch(_){ log('⚠️ 自动播放被阻止'); } }else{ videoEl.pause(); audioMain.pause(); iconPlay.className='bi bi-play-fill'; } } }; document.getElementById('btnPrev').onclick = ()=>{ index=(index-1+PLAYLIST.length)%PLAYLIST.length; loadTrack(index); }; document.getElementById('btnNext').onclick = ()=>{ index=(index+1)%PLAYLIST.length; loadTrack(index); }; const trackMenu = document.getElementById('trackMenu'); document.getElementById('btnTrack').onclick = (e)=>{ e.stopPropagation(); trackMenu.classList.toggle('show'); }; document.addEventListener('click', (e)=>{ if(!trackMenu.contains(e.target) && e.target.id!=='btnTrack') trackMenu.classList.remove('show'); }); trackMenu.addEventListener('click', (e)=>{ const item=e.target.closest('.item'); if(!item) return; switchTrack(+item.dataset.i); }); document.getElementById('btnFS').onclick = ()=>{ screenEl.classList.toggle('expanded'); iconFS.className = screenEl.classList.contains('expanded')? 'bi bi-fullscreen-exit':'bi bi-arrows-fullscreen'; }; document.addEventListener('keydown',(e)=>{ if(['INPUT','TEXTAREA'].includes(e.target.tagName)) return; if(e.code==='Space'){e.preventDefault();document.getElementById('btnPlay').click();} if(e.key==='j'||e.key==='J'){document.getElementById('btnPrev').click();} if(e.key==='k'||e.key==='K'){document.getElementById('btnNext').click();} if(e.key==='t'||e.key==='T'){document.getElementById('btnTrack').click();} }); // === 最小化测试用例(不要改动原有行为)=== function assert(cond, msg){ if(!cond){ log('❌ TEST: '+msg); } else { log('✅ TEST: '+msg); } } (function runTests(){ assert(Array.isArray(PLAYLIST), 'PLAYLIST 是数组'); assert(PLAYLIST.length >= 1, 'PLAYLIST 非空'); assert(typeof loadTrack === 'function' && typeof switchTrack === 'function', '存在 loadTrack 与 switchTrack'); try { renderTrackMenu([{name:'视频原声',src:''}]); assert(true,'渲染音轨菜单成功'); } catch(e){ assert(false,'渲染音轨菜单失败'); } // 新增:JSON 注入安全测试(避免 Unexpected token '<') try { JSON.parse(document.getElementById('playlist-data').textContent); assert(true,'播放清单 JSON 可解析'); } catch(e){ assert(false,'播放清单 JSON 解析失败'); } })(); // 启动 loadTrack(index); </script> </body> </html>