直播:status 返回观看人数,后台开播页轮询展示
Made-with: Cursor
This commit is contained in:
@@ -5,6 +5,9 @@
|
|||||||
<span>官网视频直播(WebRTC)</span>
|
<span>官网视频直播(WebRTC)</span>
|
||||||
</template>
|
</template>
|
||||||
<p class="status">{{ status }}</p>
|
<p class="status">{{ status }}</p>
|
||||||
|
<p v-if="session" class="viewer-row">
|
||||||
|
<el-tag type="info" effect="plain">当前观看人数:{{ viewerCount }}</el-tag>
|
||||||
|
</p>
|
||||||
<div class="form-block">
|
<div class="form-block">
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="field-label">画面来源</span>
|
<span class="field-label">画面来源</span>
|
||||||
@@ -93,6 +96,11 @@ import { onBeforeRouteLeave } from 'vue-router'
|
|||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import { startPublishing } from '../../utils/liveWebRTC'
|
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 authStore = useAuthStore()
|
||||||
const token = computed(() => authStore.getToken() || '')
|
const token = computed(() => authStore.getToken() || '')
|
||||||
const previewWrapRef = ref(null)
|
const previewWrapRef = ref(null)
|
||||||
@@ -101,6 +109,8 @@ const previewScreenRef = ref(null)
|
|||||||
const previewCamRef = ref(null)
|
const previewCamRef = ref(null)
|
||||||
const status = ref('就绪')
|
const status = ref('就绪')
|
||||||
const session = ref(null)
|
const session = ref(null)
|
||||||
|
const viewerCount = ref(0)
|
||||||
|
let viewerPollTimer = null
|
||||||
const captureMode = ref('camera')
|
const captureMode = ref('camera')
|
||||||
const selectedCameraId = ref('')
|
const selectedCameraId = ref('')
|
||||||
const videoInputs = ref([])
|
const videoInputs = ref([])
|
||||||
@@ -200,6 +210,37 @@ function clearPreview() {
|
|||||||
if (previewCamRef.value) previewCamRef.value.srcObject = null
|
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() {
|
async function applyCaptureSwitch() {
|
||||||
if (!session.value?.switchMode) return
|
if (!session.value?.switchMode) return
|
||||||
switchingCapture.value = true
|
switchingCapture.value = true
|
||||||
@@ -250,6 +291,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
stopViewerPoll()
|
||||||
window.removeEventListener('beforeunload', onBeforeUnload)
|
window.removeEventListener('beforeunload', onBeforeUnload)
|
||||||
window.removeEventListener('pointermove', onPipPointerMove)
|
window.removeEventListener('pointermove', onPipPointerMove)
|
||||||
})
|
})
|
||||||
@@ -268,6 +310,9 @@ onBeforeRouteLeave(() => {
|
|||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
min-height: 1.5em;
|
min-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
.viewer-row {
|
||||||
|
margin: -6px 0 14px;
|
||||||
|
}
|
||||||
.form-block {
|
.form-block {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ func handleLiveStatus(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
live := h.publishConn != nil && h.pubPC != nil && len(h.forwarders) > 0
|
live := h.publishConn != nil && h.pubPC != nil && len(h.forwarders) > 0
|
||||||
|
viewers := len(h.viewers)
|
||||||
h.mu.RUnlock()
|
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) {
|
func handleLiveWS(c *gin.Context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user