diff --git a/web/static/videoapp/app.js b/web/static/videoapp/app.js index d2b9f14..5cb4855 100644 --- a/web/static/videoapp/app.js +++ b/web/static/videoapp/app.js @@ -193,6 +193,7 @@ async function loadVideoDetail(viewkey) { document.getElementById('playerStatus').classList.add('hidden'); document.getElementById('playerBox').classList.remove('hidden'); document.getElementById('videoPlayer').classList.add('hidden'); + document.getElementById('playerError').classList.add('hidden'); document.getElementById('videoSource').src = ''; extractedVideoURL = null; @@ -211,11 +212,7 @@ async function loadVideoDetail(viewkey) { if (d.video_url) { extractedVideoURL = d.video_url; - - // 显示播放器 - showVideoPlayer(d.video_url, d.title); - - // 显示源地址和操作按钮 + startVideoPlayer(d.video_url); document.getElementById('playerStatus').className = 'player-status success'; document.getElementById('playerStatus').textContent = '✅ 视频源提取成功'; document.getElementById('playerStatus').classList.remove('hidden'); @@ -230,6 +227,8 @@ async function loadVideoDetail(viewkey) { } catch(e) { document.getElementById('videoTitle').textContent = '提取失败'; document.getElementById('playerLabel').textContent = '提取失败'; + document.getElementById('playerBox').classList.remove('hidden'); + document.getElementById('videoPlayer').classList.add('hidden'); document.getElementById('playerStatus').className = 'player-status error'; document.getElementById('playerStatus').textContent = '❌ ' + e.message; document.getElementById('playerStatus').classList.remove('hidden'); @@ -245,58 +244,59 @@ async function loadVideoDetail(viewkey) { } catch(e) {} } -function showVideoPlayer(src, title) { +function startVideoPlayer(src) { const playerBox = document.getElementById('playerBox'); - const videoPlayer = document.getElementById('videoPlayer'); - const videoSource = document.getElementById('videoSource'); + const video = document.getElementById('videoPlayer'); + const source = document.getElementById('videoSource'); + const errorOverlay = document.getElementById('playerError'); - // 设置视频源 - videoSource.src = src; - videoPlayer.load(); - - // 切换显示 - playerBox.classList.add('hidden'); - videoPlayer.classList.remove('hidden'); - - // 监听播放事件 - videoPlayer.onloadedmetadata = function() { + // 先注册事件,再加载 + video.onloadedmetadata = function() { document.getElementById('playerStatus').className = 'player-status success'; - document.getElementById('playerStatus').textContent = '✅ 视频就绪 — ' + formatDuration(videoPlayer.duration); + document.getElementById('playerStatus').textContent = '✅ 视频就绪 — ' + formatDuration(video.duration); document.getElementById('playerStatus').classList.remove('hidden'); }; - videoPlayer.onerror = function() { - const err = videoPlayer.error; - let msg = '播放失败'; - if (err) { - switch(err.code) { - case 1: msg = '视频加载被中止'; break; - case 2: msg = '网络错误,无法加载视频'; break; - case 3: msg = '视频解码失败(格式不支持)'; break; - case 4: msg = '视频不可用(CDN 防盗链或链接过期)'; break; - } - } - document.getElementById('playerStatus').className = 'player-status error'; - document.getElementById('playerStatus').textContent = '⚠️ ' + msg + ' — 可尝试复制地址用下载工具打开'; - document.getElementById('playerStatus').classList.remove('hidden'); - - // 错误时显示回退占位 - playerBox.classList.remove('hidden'); - videoPlayer.classList.add('hidden'); - document.getElementById('playerLabel').textContent = '⚠️ 无法在线播放'; - }; - - videoPlayer.onplaying = function() { + video.onplaying = function() { document.getElementById('playerStatus').className = 'player-status success'; document.getElementById('playerStatus').textContent = '▶ 正在播放'; document.getElementById('playerStatus').classList.remove('hidden'); }; + + video.onerror = function() { + const err = video.error; + var msg = '无法在线播放'; + var hint = '请尝试复制地址用下载工具打开'; + if (err) { + switch(err.code) { + case 1: msg = '视频加载被中止'; break; + case 2: msg = '网络错误,无法加载'; break; + case 3: msg = '视频解码失败(格式不支持)'; break; + case 4: msg = 'CDN 防盗链或链接已过期'; hint = '可尝试「新窗口打开」或复制地址下载'; break; + } + } + document.getElementById('playerErrorMsg').textContent = msg; + document.getElementById('playerErrorHint').textContent = hint; + // 显示覆盖层,不隐藏播放器 + errorOverlay.classList.remove('hidden'); + document.getElementById('playerStatus').className = 'player-status error'; + document.getElementById('playerStatus').textContent = '⚠️ ' + msg; + document.getElementById('playerStatus').classList.remove('hidden'); + }; + + // 切换显示:隐藏占位,显示播放器 + source.src = src; + video.load(); + playerBox.classList.add('hidden'); + video.classList.remove('hidden'); + errorOverlay.classList.add('hidden'); } async function extractCurrent() { if (!currentViewkey) return; document.getElementById('playerBox').classList.remove('hidden'); document.getElementById('videoPlayer').classList.add('hidden'); + document.getElementById('playerError').classList.add('hidden'); document.getElementById('videoSource').src = ''; document.getElementById('playerLabel').textContent = '正在提取视频源...'; document.getElementById('playerStatus').classList.remove('hidden'); diff --git a/web/static/videoapp/index.html b/web/static/videoapp/index.html index 1965723..d46cae3 100644 --- a/web/static/videoapp/index.html +++ b/web/static/videoapp/index.html @@ -90,8 +90,12 @@ +
diff --git a/web/static/videoapp/style.css b/web/static/videoapp/style.css index 75be1d2..dd1584d 100644 --- a/web/static/videoapp/style.css +++ b/web/static/videoapp/style.css @@ -131,6 +131,18 @@ body { } #videoPlayer.hidden, .player-placeholder.hidden { display:none !important; } +.player-error-overlay { + position:absolute; top:0; left:0; right:0; bottom:0; z-index:10; + display:flex; flex-direction:column; align-items:center; justify-content:center; + background:rgba(0,0,0,0.85); border-radius:var(--radius); + color:var(--text); text-align:center; padding:20px; +} +.player-error-overlay.hidden { display:none !important; } +.player-error-overlay .error-icon { font-size:3rem; margin-bottom:12px; } +.player-error-overlay p { margin:4px 0; font-size:0.95rem; } +.player-error-overlay #playerErrorHint { color:var(--text2); font-size:0.8rem; margin-top:8px; } +.player-error-overlay .btn { margin-top:12px; } + .player-status { padding:12px 16px; background:var(--card); border-radius:8px; margin-top:8px; font-size:0.85rem; } .player-status.success { border-left:3px solid var(--success); color:var(--success); } .player-status.error { border-left:3px solid var(--accent); color:var(--accent); }