feat(player): add volume slider and wheel control for video preview
This commit is contained in:
2 files changed
+97
-4
No files matched your search
+55
-4
@@ -18,6 +18,7 @@ interface State {
|
||||
loopTrim: boolean;
|
||||
previewMixAudio: boolean;
|
||||
mutedPreview: boolean;
|
||||
previewVolume: number;
|
||||
usingProxy: boolean;
|
||||
encoder: string;
|
||||
hwDecode: string;
|
||||
@@ -53,6 +54,7 @@ const state: State = {
|
||||
loopTrim: true,
|
||||
previewMixAudio: false,
|
||||
mutedPreview: false,
|
||||
previewVolume: Number(localStorage.getItem('convert_preview_volume') ?? 1),
|
||||
usingProxy: false,
|
||||
encoder: 'libx264',
|
||||
hwDecode: 'none',
|
||||
@@ -188,7 +190,11 @@ function renderShell() {
|
||||
<button id="setEndBtn" disabled>${tr.setEnd} ⏭</button>
|
||||
<button id="loopBtn" class="pill-active">🔁 ${tr.loopTrim}</button>
|
||||
<button id="previewMixBtn" class="hidden">🎧 ${tr.previewMix}</button>
|
||||
<button id="mutePreviewBtn">🔊</button>
|
||||
<div class="volume-box">
|
||||
<button id="mutePreviewBtn" title="Звук">🔊</button>
|
||||
<input type="range" id="volumeRange" min="0" max="1" step="0.05" value="1" title="Громкость" />
|
||||
<span id="volumeVal" class="mono volume-val">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -567,6 +573,37 @@ function updatePlayhead(currentTime: number) {
|
||||
el<'span'>('timeReadout').textContent = `${formatTimecode(currentTime, true)} / ${formatTimecode(dur, true)}`;
|
||||
}
|
||||
|
||||
function updateVolumeUI() {
|
||||
const video = el<'video'>('videoEl');
|
||||
const btn = el<'button'>('mutePreviewBtn');
|
||||
const rng = el<'input'>('volumeRange');
|
||||
const val = el<'span'>('volumeVal');
|
||||
if (!btn || !rng || !val) return;
|
||||
|
||||
const vol = Math.max(0, Math.min(1, state.previewVolume));
|
||||
if (video) {
|
||||
video.volume = vol;
|
||||
video.muted = state.mutedPreview;
|
||||
}
|
||||
|
||||
rng.value = String(vol);
|
||||
val.textContent = state.mutedPreview ? 'Mute' : `${Math.round(vol * 100)}%`;
|
||||
|
||||
if (state.mutedPreview || vol === 0) {
|
||||
btn.textContent = '🔇';
|
||||
btn.title = 'Включить звук';
|
||||
} else if (vol < 0.4) {
|
||||
btn.textContent = '🔈';
|
||||
btn.title = 'Выключить звук';
|
||||
} else if (vol < 0.8) {
|
||||
btn.textContent = '🔉';
|
||||
btn.title = 'Выключить звук';
|
||||
} else {
|
||||
btn.textContent = '🔊';
|
||||
btn.title = 'Выключить звук';
|
||||
}
|
||||
}
|
||||
|
||||
function buildRequest(): ConvertRequest {
|
||||
return {
|
||||
inputPath: state.loaded?.info.path ?? '',
|
||||
@@ -679,7 +716,7 @@ function applyLoadedUI(
|
||||
const mixBtn = el<'button'>('previewMixBtn');
|
||||
mixBtn.classList.toggle('hidden', res.info.audioStreams.length < 2);
|
||||
mixBtn.classList.toggle('pill-active', state.previewMixAudio);
|
||||
el<'button'>('mutePreviewBtn').textContent = state.mutedPreview ? '🔇' : '🔊';
|
||||
updateVolumeUI();
|
||||
|
||||
el<'pre'>('cliBox').classList.toggle('hidden', !state.cliExpanded);
|
||||
el<'button'>('copyCliBtn').classList.toggle('hidden', !state.cliExpanded);
|
||||
@@ -727,6 +764,7 @@ function setPlayerSource(
|
||||
|
||||
const rate = Math.max(0.25, Math.min(8, state.speed));
|
||||
video.muted = state.mutedPreview;
|
||||
video.volume = Math.max(0, Math.min(1, state.previewVolume));
|
||||
video.defaultPlaybackRate = rate;
|
||||
video.playbackRate = rate;
|
||||
|
||||
@@ -740,6 +778,7 @@ function setPlayerSource(
|
||||
video.defaultPlaybackRate = r;
|
||||
video.playbackRate = r;
|
||||
video.muted = state.mutedPreview;
|
||||
video.volume = Math.max(0, Math.min(1, state.previewVolume));
|
||||
fitVideoStage();
|
||||
if (seekSec > 0 && Number.isFinite(seekSec)) {
|
||||
video.currentTime = Math.min(seekSec, res.info.duration || seekSec);
|
||||
@@ -1058,8 +1097,20 @@ function bindEvents() {
|
||||
});
|
||||
el<'button'>('mutePreviewBtn').addEventListener('click', () => {
|
||||
state.mutedPreview = !state.mutedPreview;
|
||||
video.muted = state.mutedPreview;
|
||||
el<'button'>('mutePreviewBtn').textContent = state.mutedPreview ? '🔇' : '🔊';
|
||||
if (!state.mutedPreview && state.previewVolume === 0) {
|
||||
state.previewVolume = 0.5;
|
||||
localStorage.setItem('convert_preview_volume', '0.5');
|
||||
}
|
||||
updateVolumeUI();
|
||||
});
|
||||
el<'input'>('volumeRange').addEventListener('input', (e) => {
|
||||
const v = Number((e.target as HTMLInputElement).value);
|
||||
state.previewVolume = v;
|
||||
if (v > 0) {
|
||||
state.mutedPreview = false;
|
||||
}
|
||||
localStorage.setItem('convert_preview_volume', String(v));
|
||||
updateVolumeUI();
|
||||
});
|
||||
|
||||
// Timeline drag (handles + seek)
|
||||
|
||||
Reference in new issue
Block a user