-
[{{ formatTime(log.time) }}]
+
+ [{{ formatTime(log.time) }}]
{{ log.message }}
-
@@ -352,8 +275,9 @@ import {
FileText,
AlertTriangle,
MoreHorizontal,
+ SwitchCamera,
} from "lucide-vue-next";
-import { VideoStreamClient, CameraConfigRequest, ResolutionConfigRequest } from "@/APIClient";
+import { VideoStreamClient, CameraConfigRequest, ResolutionConfigRequest, StreamInfoResult } from "@/APIClient";
import { useEquipments } from "@/stores/equipments";
const eqps = useEquipments();
@@ -366,6 +290,10 @@ const isPlaying = ref(false);
const hasVideoError = ref(false);
const videoStatus = ref('点击"播放视频流"按钮开始查看实时视频');
+// 视频流类型切换相关
+const streamType = ref<'usbCamera' | 'videoStream'>('videoStream');
+const isSwitchingStreamType = ref(false);
+
// 对焦相关状态
const isFocusing = ref(false);
const focusAnimationClass = ref('');
@@ -390,7 +318,7 @@ const statusInfo = ref({
clientEndpoints: [] as string[],
});
-const streamInfo = ref({
+const streamInfo = ref
(new StreamInfoResult({
frameRate: 30,
frameWidth: 640,
frameHeight: 480,
@@ -398,7 +326,8 @@ const streamInfo = ref({
htmlUrl: "",
mjpegUrl: "",
snapshotUrl: "",
-});
+ usbCameraUrl: "",
+}));
const currentVideoSource = ref("");
const logs = ref>([]);
@@ -462,6 +391,27 @@ const openInNewTab = (url: string) => {
addLog("info", `已在新标签打开视频页面: ${url}`);
};
+// 切换视频流类型
+const toggleStreamType = async () => {
+ if (isSwitchingStreamType.value) return;
+ isSwitchingStreamType.value = true;
+ try {
+ // 这里假设后端有API: setStreamType(type: string)
+ addLog('info', `正在切换视频流类型到${streamType.value === 'usbCamera' ? '视频流' : 'USB摄像头'}...`);
+ refreshStatus();
+
+ // 设置视频源
+ streamType.value = streamType.value === 'usbCamera' ? 'videoStream' : 'usbCamera';
+ addLog('success', `已切换到${streamType.value === 'usbCamera' ? 'USB摄像头' : '视频流'}`);
+ stopStream();
+ } catch (error) {
+ addLog('error', `切换视频流类型失败: ${error}`);
+ console.error('切换视频流类型失败:', error);
+ } finally {
+ isSwitchingStreamType.value = false;
+ }
+};
+
// 获取并下载快照
const takeSnapshot = async () => {
try {
@@ -585,7 +535,7 @@ const tryReconnect = () => {
// 执行对焦
const performFocus = async () => {
if (isFocusing.value || !isPlaying.value) return;
-
+
try {
isFocusing.value = true;
focusAnimationClass.value = 'focus-starting';
@@ -599,7 +549,7 @@ const performFocus = async () => {
// 对焦成功动画
focusAnimationClass.value = 'focus-success';
addLog("success", "自动对焦执行成功");
-
+
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
@@ -608,7 +558,7 @@ const performFocus = async () => {
// 对焦失败动画
focusAnimationClass.value = 'focus-error';
addLog("error", `自动对焦执行失败: ${result.message || '未知错误'}`);
-
+
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
@@ -619,7 +569,7 @@ const performFocus = async () => {
focusAnimationClass.value = 'focus-error';
addLog("error", `自动对焦执行失败: ${error}`);
console.error("自动对焦执行失败:", error);
-
+
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
@@ -636,10 +586,10 @@ const performFocus = async () => {
const handleVideoClick = (event: MouseEvent) => {
// 只在播放状态下才允许对焦
if (!isPlaying.value || hasVideoError.value) return;
-
+
// 防止重复点击
if (isFocusing.value) return;
-
+
performFocus();
};
@@ -654,7 +604,7 @@ const startStream = async () => {
await refreshStatus();
// 设置视频源
- currentVideoSource.value = streamInfo.value.mjpegUrl;
+ currentVideoSource.value = streamType.value === 'usbCamera' ? streamInfo.value.usbCameraUrl : streamInfo.value.mjpegUrl;
// 设置播放状态
isPlaying.value = true;
@@ -677,11 +627,11 @@ const refreshResolutions = async () => {
const resolutions = await videoClient.getSupportedResolutions();
supportedResolutions.value = resolutions.resolutions;
console.log("支持的分辨率列表:", supportedResolutions.value);
-
+
// 获取当前分辨率
const currentRes = await videoClient.getCurrentResolution();
selectedResolution.value = currentRes;
-
+
addLog("success", "分辨率列表获取成功");
} catch (error) {
addLog("error", `获取分辨率列表失败: ${error}`);
@@ -694,36 +644,36 @@ const refreshResolutions = async () => {
// 切换分辨率
const changeResolution = async () => {
if (!selectedResolution.value) return;
-
+
changingResolution.value = true;
const wasPlaying = isPlaying.value;
-
+
try {
addLog("info", `正在切换分辨率到 ${selectedResolution.value.width}×${selectedResolution.value.height}...`);
-
+
// 如果正在播放,先停止视频流
if (wasPlaying) {
stopStream();
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
}
-
+
// 设置新分辨率
const resolutionRequest = new ResolutionConfigRequest({
width: selectedResolution.value.width,
height: selectedResolution.value.height
});
const success = await videoClient.setResolution(resolutionRequest);
-
+
if (success) {
// 刷新流信息
await refreshStatus();
-
+
// 如果之前在播放,重新启动视频流
if (wasPlaying) {
await new Promise(resolve => setTimeout(resolve, 500)); // 短暂延迟
await startStream();
}
-
+
addLog("success", `分辨率已切换到 ${selectedResolution.value.width}×${selectedResolution.value.height}`);
} else {
addLog("error", "分辨率切换失败");
@@ -810,21 +760,27 @@ img {
0% {
border-color: transparent;
}
+
100% {
- border-color: #fbbf24; /* 黄色 */
+ border-color: #fbbf24;
+ /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
}
@keyframes focus-success-animation {
0% {
- border-color: #fbbf24; /* 黄色 */
+ border-color: #fbbf24;
+ /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
+
50% {
- border-color: #10b981; /* 绿色 */
+ border-color: #10b981;
+ /* 绿色 */
box-shadow: 0 0 20px rgba(16, 185, 129, 0.5);
}
+
100% {
border-color: transparent;
box-shadow: none;
@@ -833,13 +789,17 @@ img {
@keyframes focus-error-animation {
0% {
- border-color: #fbbf24; /* 黄色 */
+ border-color: #fbbf24;
+ /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
+
50% {
- border-color: #ef4444; /* 红色 */
+ border-color: #ef4444;
+ /* 红色 */
box-shadow: 0 0 20px rgba(239, 68, 68, 0.5);
}
+
100% {
border-color: transparent;
box-shadow: none;