显示控件范围
This commit is contained in:
@@ -728,6 +728,11 @@ header p {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.square-btn {
|
||||||
|
letter-spacing: -0.1em;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
.control-btn:hover {
|
.control-btn:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
background: rgba(51, 65, 85, 0.95);
|
background: rgba(51, 65, 85, 0.95);
|
||||||
@@ -740,6 +745,37 @@ header p {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control-popover {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover-panel {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: calc(100% + 0.55rem);
|
||||||
|
transform: translateX(-50%) translateY(6px);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||||
|
padding: 0.55rem 0.6rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
|
background: rgba(15, 23, 42, 0.96);
|
||||||
|
box-shadow: 0 12px 24px rgba(2, 6, 23, 0.34);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
z-index: 20;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-popover:hover .popover-panel,
|
||||||
|
.control-popover:focus-within .popover-panel {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
.volume-control {
|
.volume-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -760,23 +796,11 @@ header p {
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.34rem 0.55rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(15, 23, 42, 0.7);
|
|
||||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
||||||
color: rgba(226, 232, 240, 0.92);
|
|
||||||
font-size: 0.76rem;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speed-control {
|
.speed-control {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 0.4rem;
|
align-items: flex-start;
|
||||||
|
gap: 0.45rem;
|
||||||
color: rgba(226, 232, 240, 0.88);
|
color: rgba(226, 232, 240, 0.88);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -803,11 +827,21 @@ header p {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seek-bar-progress {
|
.seek-bar-transcode {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: linear-gradient(90deg, #38bdf8, #2563eb);
|
background: linear-gradient(90deg, rgba(59, 130, 246, 0.45), rgba(37, 99, 235, 0.82));
|
||||||
|
}
|
||||||
|
|
||||||
|
.seek-bar-progress {
|
||||||
|
position: relative;
|
||||||
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, #f8fafc, #dbeafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
.seek-bar-handle {
|
.seek-bar-handle {
|
||||||
|
|||||||
@@ -132,35 +132,43 @@
|
|||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span id="playback-status-text">Paused</span>
|
<span id="playback-status-text">Paused</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="transcode-progress-chip" class="progress-chip">Transcode 0%</div>
|
<div class="control-popover volume-popover">
|
||||||
<div id="playback-progress-chip" class="progress-chip">Play 0%</div>
|
<button id="control-mute-toggle" class="control-btn icon-btn" type="button" aria-label="Mute">o</button>
|
||||||
<button id="control-mute-toggle" class="control-btn" type="button">Mute</button>
|
<div class="popover-panel">
|
||||||
<div class="volume-control">
|
<div class="volume-control">
|
||||||
<input id="volume-slider" class="volume-slider" type="range" min="0" max="1" step="0.05" value="1" />
|
<input id="volume-slider" class="volume-slider" type="range" min="0" max="1" step="0.05" value="1" />
|
||||||
<span id="volume-value" class="volume-value">100%</span>
|
<span id="volume-value" class="volume-value">100%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="custom-seek-container" class="control-seek hidden">
|
<div id="custom-seek-container" class="control-seek hidden">
|
||||||
<span id="seek-current-time" class="control-time">00:00:00</span>
|
<span id="seek-current-time" class="control-time">00:00:00</span>
|
||||||
<div id="seek-bar" class="seek-bar">
|
<div id="seek-bar" class="seek-bar">
|
||||||
|
<div id="seek-bar-transcode" class="seek-bar-transcode"></div>
|
||||||
<div id="seek-bar-progress" class="seek-bar-progress"></div>
|
<div id="seek-bar-progress" class="seek-bar-progress"></div>
|
||||||
<div id="seek-bar-handle" class="seek-bar-handle"></div>
|
<div id="seek-bar-handle" class="seek-bar-handle"></div>
|
||||||
</div>
|
</div>
|
||||||
<span id="seek-total-time" class="control-time">00:00:00</span>
|
<span id="seek-total-time" class="control-time">00:00:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-right">
|
<div class="controls-right">
|
||||||
<label class="speed-control" for="playback-speed">
|
<div class="control-popover speed-popover">
|
||||||
<span>Speed</span>
|
<button id="control-speed-toggle" class="control-btn" type="button" aria-label="Playback Speed">1x</button>
|
||||||
<select id="playback-speed" class="speed-select">
|
<div class="popover-panel">
|
||||||
<option value="0.5">0.5x</option>
|
<label class="speed-control" for="playback-speed">
|
||||||
<option value="0.75">0.75x</option>
|
<span>Speed</span>
|
||||||
<option value="1" selected>1x</option>
|
<select id="playback-speed" class="speed-select">
|
||||||
<option value="1.25">1.25x</option>
|
<option value="0.5">0.5x</option>
|
||||||
<option value="1.5">1.5x</option>
|
<option value="0.75">0.75x</option>
|
||||||
<option value="2">2x</option>
|
<option value="1" selected>1x</option>
|
||||||
</select>
|
<option value="1.25">1.25x</option>
|
||||||
</label>
|
<option value="1.5">1.5x</option>
|
||||||
<button id="control-fullscreen-toggle" class="control-btn" type="button">Fullscreen</button>
|
<option value="2">2x</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="control-fullscreen-toggle" class="control-btn icon-btn square-btn" type="button" aria-label="Fullscreen">[]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="now-playing" class="now-playing hidden">
|
<div id="now-playing" class="now-playing hidden">
|
||||||
|
|||||||
@@ -26,13 +26,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const controlPlayToggle = document.getElementById('control-play-toggle');
|
const controlPlayToggle = document.getElementById('control-play-toggle');
|
||||||
const controlMuteToggle = document.getElementById('control-mute-toggle');
|
const controlMuteToggle = document.getElementById('control-mute-toggle');
|
||||||
const controlFullscreenToggle = document.getElementById('control-fullscreen-toggle');
|
const controlFullscreenToggle = document.getElementById('control-fullscreen-toggle');
|
||||||
|
const controlSpeedToggle = document.getElementById('control-speed-toggle');
|
||||||
const volumeSlider = document.getElementById('volume-slider');
|
const volumeSlider = document.getElementById('volume-slider');
|
||||||
const volumeValue = document.getElementById('volume-value');
|
const volumeValue = document.getElementById('volume-value');
|
||||||
const playbackStatus = document.getElementById('playback-status');
|
const playbackStatus = document.getElementById('playback-status');
|
||||||
const playbackStatusText = document.getElementById('playback-status-text');
|
const playbackStatusText = document.getElementById('playback-status-text');
|
||||||
const playbackSpeed = document.getElementById('playback-speed');
|
const playbackSpeed = document.getElementById('playback-speed');
|
||||||
const transcodeProgressChip = document.getElementById('transcode-progress-chip');
|
|
||||||
const playbackProgressChip = document.getElementById('playback-progress-chip');
|
|
||||||
|
|
||||||
// Download phase elements
|
// Download phase elements
|
||||||
const downloadPhase = document.getElementById('download-phase');
|
const downloadPhase = document.getElementById('download-phase');
|
||||||
@@ -53,6 +52,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Custom seek bar elements
|
// Custom seek bar elements
|
||||||
const customSeekContainer = document.getElementById('custom-seek-container');
|
const customSeekContainer = document.getElementById('custom-seek-container');
|
||||||
const seekBar = document.getElementById('seek-bar');
|
const seekBar = document.getElementById('seek-bar');
|
||||||
|
const seekBarTranscode = document.getElementById('seek-bar-transcode');
|
||||||
const seekBarProgress = document.getElementById('seek-bar-progress');
|
const seekBarProgress = document.getElementById('seek-bar-progress');
|
||||||
const seekBarHandle = document.getElementById('seek-bar-handle');
|
const seekBarHandle = document.getElementById('seek-bar-handle');
|
||||||
const seekCurrentTime = document.getElementById('seek-current-time');
|
const seekCurrentTime = document.getElementById('seek-current-time');
|
||||||
@@ -159,27 +159,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
volumeValue.textContent = `${Math.round(effectiveVolume * 100)}%`;
|
volumeValue.textContent = `${Math.round(effectiveVolume * 100)}%`;
|
||||||
}
|
}
|
||||||
if (controlMuteToggle) {
|
if (controlMuteToggle) {
|
||||||
controlMuteToggle.textContent = effectiveVolume === 0 ? 'Unmute' : 'Mute';
|
controlMuteToggle.textContent = effectiveVolume === 0 ? 'x' : 'o';
|
||||||
|
controlMuteToggle.setAttribute('aria-label', effectiveVolume === 0 ? 'Unmute' : 'Mute');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFullscreenControls = () => {
|
const updateFullscreenControls = () => {
|
||||||
if (controlFullscreenToggle) {
|
if (controlFullscreenToggle) {
|
||||||
controlFullscreenToggle.textContent = document.fullscreenElement ? 'Exit Fullscreen' : 'Fullscreen';
|
controlFullscreenToggle.textContent = '[]';
|
||||||
|
controlFullscreenToggle.setAttribute('aria-label', document.fullscreenElement ? 'Exit Fullscreen' : 'Fullscreen');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePlaybackProgressChip = (absoluteTime = 0) => {
|
const updateSpeedControls = () => {
|
||||||
if (!playbackProgressChip) return;
|
if (controlSpeedToggle && playbackSpeed) {
|
||||||
const safeDuration = videoDuration > 0 ? videoDuration : 0;
|
controlSpeedToggle.textContent = playbackSpeed.value === '1' ? '1x' : playbackSpeed.value;
|
||||||
const percent = safeDuration > 0 ? Math.min(Math.max(Math.round((absoluteTime / safeDuration) * 100), 0), 100) : 0;
|
}
|
||||||
playbackProgressChip.textContent = `Play ${percent}%`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTranscodeProgressChip = (percent = 0, label = 'Transcode') => {
|
const updateTranscodeProgressBar = (percent = 0) => {
|
||||||
if (!transcodeProgressChip) return;
|
if (!seekBarTranscode) return;
|
||||||
const safePercent = Math.min(Math.max(Math.round(percent || 0), 0), 100);
|
const safePercent = Math.min(Math.max(Math.round(percent || 0), 0), 100);
|
||||||
transcodeProgressChip.textContent = `${label} ${safePercent}%`;
|
seekBarTranscode.style.width = `${safePercent}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showCustomControls = () => {
|
const showCustomControls = () => {
|
||||||
@@ -254,7 +255,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (seekTotalTime) seekTotalTime.textContent = formatTime(videoDuration);
|
if (seekTotalTime) seekTotalTime.textContent = formatTime(videoDuration);
|
||||||
showSeekBar();
|
showSeekBar();
|
||||||
updateSeekBarPosition(seekOffset + (videoPlayer.currentTime || 0));
|
updateSeekBarPosition(seekOffset + (videoPlayer.currentTime || 0));
|
||||||
updatePlaybackProgressChip(seekOffset + (videoPlayer.currentTime || 0));
|
|
||||||
}
|
}
|
||||||
if (message.type === 'progress') {
|
if (message.type === 'progress') {
|
||||||
handleProgress(message.progress);
|
handleProgress(message.progress);
|
||||||
@@ -275,7 +275,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (status === 'downloading') {
|
if (status === 'downloading') {
|
||||||
showDownloadPhase();
|
showDownloadPhase();
|
||||||
const percent = Math.min(Math.max(Math.round(progress.percent || 0), 0), 100);
|
const percent = Math.min(Math.max(Math.round(progress.percent || 0), 0), 100);
|
||||||
updateTranscodeProgressChip(percent, 'Download');
|
updateTranscodeProgressBar(percent);
|
||||||
const downloaded = formatBytes(progress.downloadedBytes || 0);
|
const downloaded = formatBytes(progress.downloadedBytes || 0);
|
||||||
const total = formatBytes(progress.totalBytes || 0);
|
const total = formatBytes(progress.totalBytes || 0);
|
||||||
downloadSizeText.textContent = `${downloaded} / ${total}`;
|
downloadSizeText.textContent = `${downloaded} / ${total}`;
|
||||||
@@ -283,7 +283,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadProgressFill.style.width = `${percent}%`;
|
downloadProgressFill.style.width = `${percent}%`;
|
||||||
} else if (status === 'downloaded') {
|
} else if (status === 'downloaded') {
|
||||||
showDownloadPhase();
|
showDownloadPhase();
|
||||||
updateTranscodeProgressChip(100, 'Download');
|
updateTranscodeProgressBar(100);
|
||||||
const downloaded = formatBytes(progress.downloadedBytes || progress.totalBytes || 0);
|
const downloaded = formatBytes(progress.downloadedBytes || progress.totalBytes || 0);
|
||||||
const total = formatBytes(progress.totalBytes || 0);
|
const total = formatBytes(progress.totalBytes || 0);
|
||||||
downloadSizeText.textContent = `${downloaded} / ${total} — 下载完成`;
|
downloadSizeText.textContent = `${downloaded} / ${total} — 下载完成`;
|
||||||
@@ -295,7 +295,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
} else if (status === 'transcoding') {
|
} else if (status === 'transcoding') {
|
||||||
showTranscodePhase();
|
showTranscodePhase();
|
||||||
const percent = Math.min(Math.max(Math.round(progress.percent || 0), 0), 100);
|
const percent = Math.min(Math.max(Math.round(progress.percent || 0), 0), 100);
|
||||||
updateTranscodeProgressChip(percent, 'Transcode');
|
updateTranscodeProgressBar(percent);
|
||||||
transcodeProgressText.textContent = `${percent}%`;
|
transcodeProgressText.textContent = `${percent}%`;
|
||||||
transcodeProgressFill.style.width = `${percent}%`;
|
transcodeProgressFill.style.width = `${percent}%`;
|
||||||
transcodeDetailText.textContent = progress.details || 'FFmpeg 转码中...';
|
transcodeDetailText.textContent = progress.details || 'FFmpeg 转码中...';
|
||||||
@@ -308,16 +308,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
} else if (status === 'finished') {
|
} else if (status === 'finished') {
|
||||||
showTranscodePhase();
|
showTranscodePhase();
|
||||||
updateTranscodeProgressChip(100, 'Transcode');
|
updateTranscodeProgressBar(100);
|
||||||
transcodeProgressText.textContent = '100%';
|
transcodeProgressText.textContent = '100%';
|
||||||
transcodeProgressFill.style.width = '100%';
|
transcodeProgressFill.style.width = '100%';
|
||||||
transcodeDetailText.textContent = '转码完成';
|
transcodeDetailText.textContent = '转码完成';
|
||||||
} else if (status === 'failed') {
|
} else if (status === 'failed') {
|
||||||
updateTranscodeProgressChip(progress.percent || 0, 'Failed');
|
updateTranscodeProgressBar(progress.percent || 0);
|
||||||
transcodeDetailText.textContent = `失败: ${progress.details || '未知错误'}`;
|
transcodeDetailText.textContent = `失败: ${progress.details || '未知错误'}`;
|
||||||
transcodeProgressFill.style.background = 'linear-gradient(90deg, #dc2626, #b91c1c)';
|
transcodeProgressFill.style.background = 'linear-gradient(90deg, #dc2626, #b91c1c)';
|
||||||
} else if (status === 'cancelled') {
|
} else if (status === 'cancelled') {
|
||||||
updateTranscodeProgressChip(0, 'Stopped');
|
updateTranscodeProgressBar(0);
|
||||||
transcodeDetailText.textContent = '已取消';
|
transcodeDetailText.textContent = '已取消';
|
||||||
transcodeProgressFill.style.width = '0%';
|
transcodeProgressFill.style.width = '0%';
|
||||||
}
|
}
|
||||||
@@ -350,8 +350,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (statFps) statFps.textContent = '';
|
if (statFps) statFps.textContent = '';
|
||||||
if (statBitrate) statBitrate.textContent = '';
|
if (statBitrate) statBitrate.textContent = '';
|
||||||
if (statTime) statTime.textContent = '';
|
if (statTime) statTime.textContent = '';
|
||||||
updateTranscodeProgressChip(0, 'Transcode');
|
updateTranscodeProgressBar(0);
|
||||||
updatePlaybackProgressChip(0);
|
|
||||||
|
|
||||||
if (stopTranscodeBtn) {
|
if (stopTranscodeBtn) {
|
||||||
stopTranscodeBtn.classList.add('hidden');
|
stopTranscodeBtn.classList.add('hidden');
|
||||||
@@ -377,7 +376,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
seekBarProgress.style.width = `${ratio * 100}%`;
|
seekBarProgress.style.width = `${ratio * 100}%`;
|
||||||
seekBarHandle.style.left = `${ratio * 100}%`;
|
seekBarHandle.style.left = `${ratio * 100}%`;
|
||||||
seekCurrentTime.textContent = formatTime(absoluteTime);
|
seekCurrentTime.textContent = formatTime(absoluteTime);
|
||||||
updatePlaybackProgressChip(absoluteTime);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track playback position in the custom seek bar
|
// Track playback position in the custom seek bar
|
||||||
@@ -1048,6 +1046,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
playbackSpeed.addEventListener('change', (event) => {
|
playbackSpeed.addEventListener('change', (event) => {
|
||||||
const nextRate = Math.max(0.25, Math.min(4, parseFloat(event.target.value) || 1));
|
const nextRate = Math.max(0.25, Math.min(4, parseFloat(event.target.value) || 1));
|
||||||
videoPlayer.playbackRate = nextRate;
|
videoPlayer.playbackRate = nextRate;
|
||||||
|
updateSpeedControls();
|
||||||
revealPlaybackChrome();
|
revealPlaybackChrome();
|
||||||
schedulePlaybackChromeHide();
|
schedulePlaybackChromeHide();
|
||||||
});
|
});
|
||||||
@@ -1103,8 +1102,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
updatePlayControls();
|
updatePlayControls();
|
||||||
updateVolumeControls();
|
updateVolumeControls();
|
||||||
updateFullscreenControls();
|
updateFullscreenControls();
|
||||||
updateTranscodeProgressChip(0, 'Transcode');
|
updateSpeedControls();
|
||||||
updatePlaybackProgressChip(0);
|
updateTranscodeProgressBar(0);
|
||||||
|
|
||||||
// Bind events
|
// Bind events
|
||||||
refreshBtn.addEventListener('click', () => fetchVideos(selectedBucket));
|
refreshBtn.addEventListener('click', () => fetchVideos(selectedBucket));
|
||||||
|
|||||||
Reference in New Issue
Block a user