直播:音量与全屏叠在画面底部,全屏内可调

Made-with: Cursor
This commit is contained in:
whm
2026-03-26 15:17:30 +08:00
parent d441fe33fd
commit 8d800eee62

View File

@@ -26,6 +26,7 @@
class="live-room-video live-room-video--watch live-room-video--contain"
playsinline
autoplay
@volumechange="syncVolumeUIFromVideo"
></video>
<div class="live-dm-layer" aria-hidden="true">
<div
@@ -37,14 +38,32 @@
<span class="live-dm-from">{{ d.from }}</span>{{ d.text }}
</div>
</div>
<div class="live-video-toolbar" role="toolbar" aria-label="播放控制">
<button type="button" class="live-video-toolbtn" @click="unmuteAndPlay">开声音</button>
<button type="button" class="live-video-toolbtn" @click="toggleStageFullscreen">
<div class="live-video-overlay" role="toolbar" aria-label="播放与音量">
<div class="live-video-overlay-inner">
<button type="button" class="live-overlay-btn" @click="toggleMute">
{{ displayMuted ? '开声音' : '静音' }}
</button>
<label class="live-vol-wrap">
<span class="live-vol-label">音量</span>
<input
class="live-vol-range"
type="range"
min="0"
max="100"
step="1"
:value="volumePercent"
aria-label="音量"
@input="onVolumeRangeInput"
/>
<span class="live-vol-value" aria-hidden="true">{{ volumePercent }}%</span>
</label>
<button type="button" class="live-overlay-btn" @click="toggleStageFullscreen">
{{ stageFullscreen ? '退出全屏' : '全屏' }}
</button>
</div>
</div>
</div>
</div>
<div class="live-stage-footer">
<div class="live-dm-auth-row">
<template v-if="dmLoggedIn">
@@ -102,6 +121,9 @@ import { getSiteDmToken, getSiteDmUsername, clearSiteDmSession } from '../utils/
const watchVideoRef = ref(null)
const liveStageRef = ref(null)
const stageFullscreen = ref(false)
/** 与 video.volume / muted 同步(浏览器自动播放可能先静音) */
const volumePercent = ref(100)
const displayMuted = ref(false)
const rawLiveUrl = ref('')
const pageTitle = ref('视频直播')
const watchStatus = ref('正在检测本站直播…')
@@ -180,11 +202,37 @@ function goLiveRoom() {
window.location.href = target
}
function unmuteAndPlay() {
function syncVolumeUIFromVideo() {
const v = watchVideoRef.value
if (!v) return
v.muted = false
displayMuted.value = v.muted
volumePercent.value = Math.round(Math.min(1, Math.max(0, v.volume)) * 100)
}
function onVolumeRangeInput(e) {
volumePercent.value = Number(e.target.value)
const v = watchVideoRef.value
if (!v) return
v.volume = volumePercent.value / 100
v.muted = volumePercent.value === 0
v.play().catch(() => {})
displayMuted.value = v.muted
}
function toggleMute() {
const v = watchVideoRef.value
if (!v) return
if (v.muted) {
v.muted = false
if (v.volume === 0) {
v.volume = 1
volumePercent.value = 100
}
} else {
v.muted = true
}
v.play().catch(() => {})
syncVolumeUIFromVideo()
}
function syncStageFullscreenFlag() {
@@ -384,6 +432,8 @@ onMounted(async () => {
watchStatus.value = s
}
})
await nextTick()
syncVolumeUIFromVideo()
})
onUnmounted(() => {
@@ -529,11 +579,6 @@ onUnmounted(() => {
aspect-ratio: unset;
object-fit: contain;
}
.live-stage:fullscreen .live-video-toolbar,
.live-stage:-webkit-full-screen .live-video-toolbar {
flex-shrink: 0;
margin-top: 8px;
}
.live-stage:fullscreen .live-stage-footer,
.live-stage:-webkit-full-screen .live-stage-footer {
flex-shrink: 0;
@@ -547,6 +592,86 @@ onUnmounted(() => {
.live-stage:-webkit-full-screen .live-dm-hint {
max-width: none;
}
.live-video-overlay {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 6;
padding: 20px 10px 10px;
border-radius: 0 0 14px 14px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.45) 55%, transparent 100%);
pointer-events: auto;
}
.live-video-overlay-inner {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px 12px;
}
.live-overlay-btn {
padding: 6px 14px;
border-radius: 8px;
border: 1px solid rgba(0, 212, 255, 0.5);
background: rgba(0, 0, 0, 0.45);
color: #00d4ff;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
.live-overlay-btn:hover {
background: rgba(0, 212, 255, 0.18);
border-color: rgba(0, 212, 255, 0.85);
}
.live-vol-wrap {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
justify-content: flex-end;
max-width: min(280px, 58vw);
}
.live-vol-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.75);
flex-shrink: 0;
}
.live-vol-value {
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
width: 2.5em;
text-align: right;
flex-shrink: 0;
}
.live-vol-range {
flex: 1;
min-width: 72px;
height: 6px;
border-radius: 3px;
appearance: none;
background: rgba(255, 255, 255, 0.2);
accent-color: #00d4ff;
}
.live-vol-range::-webkit-slider-thumb {
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #00d4ff;
cursor: pointer;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.35);
}
.live-vol-range::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: #00d4ff;
cursor: pointer;
border: none;
}
.live-dm-layer {
pointer-events: none;
position: absolute;
@@ -674,28 +799,6 @@ onUnmounted(() => {
color: #ffb86c;
text-align: left;
}
.live-video-toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 12px;
}
.live-video-toolbtn {
padding: 8px 18px;
border-radius: 10px;
border: 1px solid rgba(0, 212, 255, 0.45);
background: rgba(0, 212, 255, 0.12);
color: #00d4ff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
.live-video-toolbtn:hover {
background: rgba(0, 212, 255, 0.22);
border-color: rgba(0, 212, 255, 0.75);
}
.live-room-actions {
margin-top: 8px;
}