From 4112ea444719e1729b369a0a70b8f88a608b8bab Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Thu, 26 Mar 2026 15:30:15 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B4=E6=92=AD=EF=BC=9Astatus=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E8=A7=82=E7=9C=8B=E4=BA=BA=E6=95=B0=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=BC=80=E6=92=AD=E9=A1=B5=E8=BD=AE=E8=AF=A2=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- admin/src/views/sites/LiveBroadcast.vue | 45 +++++++++++++++++++++++++ server/pkg/weblive/ws.go | 3 +- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/admin/src/views/sites/LiveBroadcast.vue b/admin/src/views/sites/LiveBroadcast.vue index c9ce215..723d086 100644 --- a/admin/src/views/sites/LiveBroadcast.vue +++ b/admin/src/views/sites/LiveBroadcast.vue @@ -5,6 +5,9 @@ 官网视频直播(WebRTC)

{{ status }}

+

+ 当前观看人数:{{ viewerCount }} +

画面来源 @@ -93,6 +96,11 @@ import { onBeforeRouteLeave } from 'vue-router' import { useAuthStore } from '../../stores/auth' import { startPublishing } from '../../utils/liveWebRTC' +function liveStatusUrl() { + const base = (import.meta.env.VITE_API_BASE || '').replace(/\/$/, '') + return base ? `${base}/api/web/live/status` : '/api/web/live/status' +} + const authStore = useAuthStore() const token = computed(() => authStore.getToken() || '') const previewWrapRef = ref(null) @@ -101,6 +109,8 @@ const previewScreenRef = ref(null) const previewCamRef = ref(null) const status = ref('就绪') const session = ref(null) +const viewerCount = ref(0) +let viewerPollTimer = null const captureMode = ref('camera') const selectedCameraId = ref('') const videoInputs = ref([]) @@ -200,6 +210,37 @@ function clearPreview() { if (previewCamRef.value) previewCamRef.value.srcObject = null } +async function fetchViewerCount() { + try { + const r = await fetch(liveStatusUrl(), { cache: 'no-store' }) + if (!r.ok) return + const j = await r.json() + if (typeof j.viewers === 'number') viewerCount.value = j.viewers + } catch (_) {} +} + +function startViewerPoll() { + stopViewerPoll() + fetchViewerCount() + viewerPollTimer = window.setInterval(fetchViewerCount, 2500) +} + +function stopViewerPoll() { + if (viewerPollTimer != null) { + clearInterval(viewerPollTimer) + viewerPollTimer = null + } +} + +watch(session, (s) => { + if (s) { + startViewerPoll() + } else { + stopViewerPoll() + viewerCount.value = 0 + } +}) + async function applyCaptureSwitch() { if (!session.value?.switchMode) return switchingCapture.value = true @@ -250,6 +291,7 @@ onMounted(() => { }) onUnmounted(() => { + stopViewerPoll() window.removeEventListener('beforeunload', onBeforeUnload) window.removeEventListener('pointermove', onPipPointerMove) }) @@ -268,6 +310,9 @@ onBeforeRouteLeave(() => { margin-bottom: 14px; min-height: 1.5em; } +.viewer-row { + margin: -6px 0 14px; +} .form-block { margin-bottom: 16px; } diff --git a/server/pkg/weblive/ws.go b/server/pkg/weblive/ws.go index 0647268..c404326 100644 --- a/server/pkg/weblive/ws.go +++ b/server/pkg/weblive/ws.go @@ -45,8 +45,9 @@ func handleLiveStatus(c *gin.Context) { } h.mu.RLock() live := h.publishConn != nil && h.pubPC != nil && len(h.forwarders) > 0 + viewers := len(h.viewers) h.mu.RUnlock() - c.JSON(http.StatusOK, gin.H{"live": live}) + c.JSON(http.StatusOK, gin.H{"live": live, "viewers": viewers}) } func handleLiveWS(c *gin.Context) {