Compare commits

...

3 Commits

Author SHA1 Message Date
f3bb7eaae4 fix: 前端显示真实发送频率和渲染频率 2026-04-02 22:48:52 +08:00
f6d33d6b56 fix: 不能“sender 里先按 fps 睡再去 DQBUF” 2026-04-02 22:31:40 +08:00
77681329dc fix: requirements.txt 2026-04-02 00:30:58 +08:00
3 changed files with 51 additions and 53 deletions

4
backend/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Django>=5,<6
djangorestframework>=3.15,<4
django-cors-headers>=4,<5
channels>=4,<5

View File

@@ -1,76 +1,53 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { buildVideoFrameUrl } from '@/lib/api' import { buildVideoStreamUrl } from '@/lib/api'
import type { VideoStatus } from '@/types' import type { VideoStatus } from '@/types'
const props = defineProps<{ const props = defineProps<{
video: VideoStatus | null video: VideoStatus | null
}>() }>()
const frameUrl = ref(buildVideoFrameUrl(0)) const streamUrl = ref('')
const currentFps = computed(() => props.video?.fps ?? 30)
const canRequestFrames = computed(() => props.video?.available === true) const canRequestFrames = computed(() => props.video?.available === true)
const modeLabel = computed(() => { const streamFps = computed(() => Math.max(props.video?.fps ?? 0, 30))
const senderProfileLabel = computed(() => {
if (!props.video) { if (!props.video) {
return '正在获取视频状态' return '发送端 Profile: --'
} }
if (props.video.source_mode === 'omnisocket-jpeg-live') { return `发送端 Profile: ${props.video.fps} FPS`
return `${props.video.fps} FPS 实时接收`
}
if (props.video.source_mode === 'omnisocket-waiting') {
return '未实时获取真实值'
}
return `${props.video.fps} FPS`
}) })
const displayModeLabel = computed(() => {
if (!props.video) {
return '前端显示: 等待状态'
}
if (!canRequestFrames.value) {
return '前端显示: 等待新视频帧'
}
return `前端显示: MJPEG Stream (${streamFps.value} FPS 请求)`
})
const placeholderText = computed(() => { const placeholderText = computed(() => {
if (!props.video) { if (!props.video) {
return '正在获取视频状态...' return '正在获取视频状态...'
} }
return '未实时获取真实值' return '尚未收到实时视频帧'
}) })
let frameTimer: number | null = null function refreshStreamUrl() {
let frameKey = 0
function refreshFrame() {
if (!canRequestFrames.value) {
return
}
frameKey += 1
frameUrl.value = buildVideoFrameUrl(frameKey)
}
function startFrameLoop() {
if (frameTimer != null) {
window.clearInterval(frameTimer)
frameTimer = null
}
if (!canRequestFrames.value) { if (!canRequestFrames.value) {
streamUrl.value = ''
return return
} }
refreshFrame() // Keep the browser attached to a higher-frequency local MJPEG stream
const intervalMs = Math.max(33, Math.round(1000 / currentFps.value)) // so the dashboard does not add an extra 0-100 ms polling delay.
frameTimer = window.setInterval(() => { streamUrl.value = buildVideoStreamUrl(streamFps.value, Date.now())
refreshFrame()
}, intervalMs)
} }
onMounted(() => { watch([streamFps, canRequestFrames], refreshStreamUrl, { immediate: true })
startFrameLoop()
})
onUnmounted(() => {
if (frameTimer != null) {
window.clearInterval(frameTimer)
}
})
watch([currentFps, canRequestFrames], () => {
startFrameLoop()
})
</script> </script>
<template> <template>
@@ -89,7 +66,7 @@ watch([currentFps, canRequestFrames], () => {
<img <img
v-if="canRequestFrames" v-if="canRequestFrames"
class="video-frame" class="video-frame"
:src="frameUrl" :src="streamUrl"
alt="Robot jpeg frame stream" alt="Robot jpeg frame stream"
/> />
<div v-else class="video-placeholder"> <div v-else class="video-placeholder">
@@ -104,13 +81,16 @@ watch([currentFps, canRequestFrames], () => {
</div> </div>
<div class="stat-card"> <div class="stat-card">
<span>当前模式</span> <span>当前模式</span>
<strong>{{ modeLabel }}</strong> <div class="stat-lines">
<strong>{{ senderProfileLabel }}</strong>
<strong class="secondary">{{ displayModeLabel }}</strong>
</div>
</div> </div>
</div> </div>
<p class="hint"> <p class="hint">
这里只有在后端已经收到 OmniSocket 的真实 JPEG 帧时才会开始逐帧请求并显示画面 视频可用时页面会直接连接后端的 MJPEG stream而不是按当前发送 fps 逐帧轮询
如果当前没有真实帧页面会保持占位提示不再回退测试视频流 这样能减少 dashboard 本身带来的额外显示延迟
</p> </p>
<p class="hint subtle"> <p class="hint subtle">
@@ -213,6 +193,16 @@ h2 {
font-size: 18px; font-size: 18px;
} }
.stat-lines {
display: grid;
gap: 6px;
}
.stat-lines .secondary {
font-size: 15px;
color: #a8b4ce;
}
.hint { .hint {
margin: 0; margin: 0;
color: #8d99b3; color: #8d99b3;

View File

@@ -19,3 +19,7 @@ export function fetchDashboardSnapshot() {
export function buildVideoFrameUrl(frameKey: number) { export function buildVideoFrameUrl(frameKey: number) {
return `${API_BASE}/api/video/frame/?frame=${frameKey}&t=${Date.now()}` return `${API_BASE}/api/video/frame/?frame=${frameKey}&t=${Date.now()}`
} }
export function buildVideoStreamUrl(fps: number, token: number) {
return `${API_BASE}/api/video/stream/?fps=${fps}&t=${token}`
}