直播页:全屏包含底部弹幕区,可登录与发送
Made-with: Cursor
This commit is contained in:
@@ -18,53 +18,61 @@
|
||||
</select>
|
||||
</div>
|
||||
<p class="live-watch-status">{{ watchStatus }}</p>
|
||||
<div class="live-video-wrap">
|
||||
<video
|
||||
ref="watchVideoRef"
|
||||
class="live-room-video live-room-video--watch live-room-video--contain"
|
||||
playsinline
|
||||
autoplay
|
||||
></video>
|
||||
<div class="live-dm-layer" aria-hidden="true">
|
||||
<div
|
||||
v-for="d in dmItems"
|
||||
:key="d.id"
|
||||
class="live-dm-line"
|
||||
:style="{ top: d.top + '%' }"
|
||||
>
|
||||
<span class="live-dm-from">{{ d.from }}:</span>{{ d.text }}
|
||||
<div ref="liveStageRef" class="live-stage">
|
||||
<div class="live-stage-media">
|
||||
<div class="live-video-wrap">
|
||||
<video
|
||||
ref="watchVideoRef"
|
||||
class="live-room-video live-room-video--watch live-room-video--contain"
|
||||
playsinline
|
||||
autoplay
|
||||
></video>
|
||||
<div class="live-dm-layer" aria-hidden="true">
|
||||
<div
|
||||
v-for="d in dmItems"
|
||||
:key="d.id"
|
||||
class="live-dm-line"
|
||||
:style="{ top: d.top + '%' }"
|
||||
>
|
||||
<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">
|
||||
{{ stageFullscreen ? '退出全屏' : '全屏' }}
|
||||
</button>
|
||||
</div>
|
||||
</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="toggleVideoFullscreen">全屏</button>
|
||||
<div class="live-stage-footer">
|
||||
<div class="live-dm-auth-row">
|
||||
<template v-if="dmLoggedIn">
|
||||
<span class="live-dm-user">已登录:{{ dmDisplayName }}</span>
|
||||
<button type="button" class="live-dm-auth-btn" @click="logoutDmUser">退出</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link class="live-dm-auth-link" to="/live/login">登录发弹幕</router-link>
|
||||
<span class="live-dm-auth-sep">·</span>
|
||||
<router-link class="live-dm-auth-link" to="/live/register">注册</router-link>
|
||||
</template>
|
||||
</div>
|
||||
<div class="live-dm-bar">
|
||||
<input
|
||||
v-model="dmDraft"
|
||||
class="live-dm-input"
|
||||
type="text"
|
||||
maxlength="120"
|
||||
:placeholder="dmLoggedIn ? '发条弹幕…' : '登录后可发弹幕(仍可观看)'"
|
||||
autocomplete="off"
|
||||
:disabled="!dmLoggedIn"
|
||||
@keydown.enter.prevent="sendDm"
|
||||
/>
|
||||
<button type="button" class="live-dm-send" :disabled="!dmLoggedIn" @click="sendDm">发送</button>
|
||||
</div>
|
||||
<p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="live-dm-auth-row">
|
||||
<template v-if="dmLoggedIn">
|
||||
<span class="live-dm-user">已登录:{{ dmDisplayName }}</span>
|
||||
<button type="button" class="live-dm-auth-btn" @click="logoutDmUser">退出</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link class="live-dm-auth-link" to="/live/login">登录发弹幕</router-link>
|
||||
<span class="live-dm-auth-sep">·</span>
|
||||
<router-link class="live-dm-auth-link" to="/live/register">注册</router-link>
|
||||
</template>
|
||||
</div>
|
||||
<div class="live-dm-bar">
|
||||
<input
|
||||
v-model="dmDraft"
|
||||
class="live-dm-input"
|
||||
type="text"
|
||||
maxlength="120"
|
||||
:placeholder="dmLoggedIn ? '发条弹幕…' : '登录后可发弹幕(仍可观看)'"
|
||||
autocomplete="off"
|
||||
:disabled="!dmLoggedIn"
|
||||
@keydown.enter.prevent="sendDm"
|
||||
/>
|
||||
<button type="button" class="live-dm-send" :disabled="!dmLoggedIn" @click="sendDm">发送</button>
|
||||
</div>
|
||||
<p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
|
||||
</section>
|
||||
|
||||
<section class="live-block live-block--divider" aria-label="外链直播间">
|
||||
@@ -92,6 +100,8 @@ import { startViewing, liveDanmakuWsURL } from '../utils/liveWebRTC'
|
||||
import { getSiteDmToken, getSiteDmUsername, clearSiteDmSession } from '../utils/siteUserAuth'
|
||||
|
||||
const watchVideoRef = ref(null)
|
||||
const liveStageRef = ref(null)
|
||||
const stageFullscreen = ref(false)
|
||||
const rawLiveUrl = ref('')
|
||||
const pageTitle = ref('视频直播')
|
||||
const watchStatus = ref('正在检测本站直播…')
|
||||
@@ -177,20 +187,55 @@ function unmuteAndPlay() {
|
||||
v.play().catch(() => {})
|
||||
}
|
||||
|
||||
function toggleVideoFullscreen() {
|
||||
function syncStageFullscreenFlag() {
|
||||
const d = document
|
||||
const el = liveStageRef.value
|
||||
if (!el) {
|
||||
stageFullscreen.value = false
|
||||
return
|
||||
}
|
||||
stageFullscreen.value =
|
||||
d.fullscreenElement === el || d.webkitFullscreenElement === el
|
||||
}
|
||||
|
||||
/** 整页舞台全屏:含画面 + 底部登录与发弹幕(非仅 video,避免全屏后看不到输入框) */
|
||||
function toggleStageFullscreen() {
|
||||
const stage = liveStageRef.value
|
||||
const v = watchVideoRef.value
|
||||
if (!v) return
|
||||
if (!stage) return
|
||||
const doc = document
|
||||
if (doc.fullscreenElement || doc.webkitFullscreenElement) {
|
||||
const fsEl = doc.fullscreenElement || doc.webkitFullscreenElement
|
||||
if (fsEl) {
|
||||
doc.exitFullscreen?.() || doc.webkitExitFullscreen?.()
|
||||
return
|
||||
}
|
||||
if (typeof v.webkitEnterFullscreen === 'function') {
|
||||
v.webkitEnterFullscreen()
|
||||
if (typeof stage.requestFullscreen === 'function') {
|
||||
Promise.resolve(stage.requestFullscreen())
|
||||
.then(() => syncStageFullscreenFlag())
|
||||
.catch(() => {
|
||||
if (typeof stage.webkitRequestFullscreen === 'function') {
|
||||
try {
|
||||
stage.webkitRequestFullscreen()
|
||||
} catch (_) {
|
||||
if (v && typeof v.webkitEnterFullscreen === 'function') v.webkitEnterFullscreen()
|
||||
}
|
||||
} else if (v && typeof v.webkitEnterFullscreen === 'function') {
|
||||
v.webkitEnterFullscreen()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const el = v
|
||||
el.requestFullscreen?.() || el.webkitRequestFullscreen?.()
|
||||
if (typeof stage.webkitRequestFullscreen === 'function') {
|
||||
try {
|
||||
stage.webkitRequestFullscreen()
|
||||
} catch (_) {
|
||||
if (v && typeof v.webkitEnterFullscreen === 'function') v.webkitEnterFullscreen()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (v && typeof v.webkitEnterFullscreen === 'function') {
|
||||
v.webkitEnterFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
function pushDmLine(text, fromRaw) {
|
||||
@@ -327,6 +372,8 @@ function sendDm() {
|
||||
|
||||
onMounted(async () => {
|
||||
dmIntentionalClose = false
|
||||
document.addEventListener('fullscreenchange', syncStageFullscreenFlag)
|
||||
document.addEventListener('webkitfullscreenchange', syncStageFullscreenFlag)
|
||||
loadCaptureQualityPref()
|
||||
loadHomepage()
|
||||
await nextTick()
|
||||
@@ -340,6 +387,8 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('fullscreenchange', syncStageFullscreenFlag)
|
||||
document.removeEventListener('webkitfullscreenchange', syncStageFullscreenFlag)
|
||||
dmIntentionalClose = true
|
||||
dmSendQueue.length = 0
|
||||
if (dmReconnectTimer) {
|
||||
@@ -432,11 +481,72 @@ onUnmounted(() => {
|
||||
margin: 0 0 14px;
|
||||
min-height: 1.4em;
|
||||
}
|
||||
.live-stage {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.live-video-wrap {
|
||||
position: relative;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.live-stage:fullscreen,
|
||||
.live-stage:-webkit-full-screen {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 14px calc(12px + env(safe-area-inset-bottom, 0px));
|
||||
box-sizing: border-box;
|
||||
background: #0a0a12;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.live-stage:fullscreen .live-stage-media,
|
||||
.live-stage:-webkit-full-screen .live-stage-media {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.live-stage:fullscreen .live-video-wrap,
|
||||
.live-stage:-webkit-full-screen .live-video-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.live-stage:fullscreen .live-room-video.live-room-video--contain,
|
||||
.live-stage:-webkit-full-screen .live-room-video.live-room-video--contain {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
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;
|
||||
padding-top: 6px;
|
||||
}
|
||||
.live-stage:fullscreen .live-dm-auth-row,
|
||||
.live-stage:fullscreen .live-dm-bar,
|
||||
.live-stage:fullscreen .live-dm-hint,
|
||||
.live-stage:-webkit-full-screen .live-dm-auth-row,
|
||||
.live-stage:-webkit-full-screen .live-dm-bar,
|
||||
.live-stage:-webkit-full-screen .live-dm-hint {
|
||||
max-width: none;
|
||||
}
|
||||
.live-dm-layer {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user