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) {