epuck camera node test--> error

本文介绍了epuck机器人中摄像头驱动出现的错误及其解决方案。主要问题是驱动无法正确获取摄像头的帧宽度和高度,并出现了VIDIOC_QUERYMENU: Invalid argument错误。通过调整摄像头的访问权限和重新安装v4lc-utils及重新编译OpenCV来解决这些问题。

1. 在epuck@epuck3:~/fuerte/epuck/epuck_opencv_cam_driver

Test the "driver_node", it is executable but without frame width and height. The result is as follow:
------------------------------------------------------------------
$ rosrun epuck_opencv_cam_driver driver_node 
camera opened
frame width = 0
frame height = 0

-----------------------------------------------------------------


以上错误,要改摄像头的访问权限就好。如下

$ sudo chmod 777 /dev/video0


Then the error is: 

------------------------------------------------------------------

VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
camera opened
frame width = 640
frame height = 480
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
VIDIOC_QUERYMENU: Invalid argument
select timeout
select timeout
------------------------------------------------------------------

***

Possible solution:

http://help.simplecv.org/question/722/vidioc_querymenu-invalid-argument-and-select-timeout-with-simplecv/


I think you too should re-install v4lc-utils(try to install latest stable version) and re-compile OpenCV. Refer to this post and see if you have missed something while installing OpenCV. http://jayrambhia.wordpress.com/2012/06/20/install-opencv-2-4-in-ubuntu-12-04-precise-pangolin/

<template> <view class="user-profile-container"> <!-- 顶部背景图 --> <view class="profile-banner"> <image class="banner-image" src="/static/banner.jpg" mode="aspectFill" /> <uni-icons type="arrowleft" size="30" color="#fff"></uni-icons> </view> <!-- 用户信息卡片 --> <view class="profile-card"> <view class="avatar-section"> <image class="avatar" :src="userInfo.avatar || '/static/default-avatar.png'" mode="aspectFill" @tap="changeAvatar" /> <view class="edit-icon" @tap="changeAvatar"> <uni-icons type="camera" size="22" color="#fff"></uni-icons> </view> </view> <view class="user-details"> <view class="name-section"> <text class="username">{{ userInfo.nickname || '未设置昵称' }}</text> <uni-icons type="compose" size="22" color="#5e8fff" @tap="editNickname" ></uni-icons> </view> <text class="user-id">ID: {{ userInfo.userId }}</text> </view> </view> <!-- 权限管理 --> <view class="info-section"> <view class="section-header" @click="goToAuthorityManagement"> <text>权限管理</text> </view> <template v-slot:header> <uni-icons type="info" size="20" color="#5e8fff"></uni-icons> </template> </view> <!-- 其他设置区域 --> <view class="info-section"> <view class="section-header"> <text>其他设置</text> </view> <template v-slot:header> <uni-icons type="info" size="20" color="#5e8fff"></uni-icons> </template> </uni-list-item> </uni-list> </view> <!-- 退出登录按钮 --> <view class="logout-btn" @tap="logout"> <text>退出登录</text> </view> </view> </template> <script> export default { data() { return { userInfo: { userId: "U20230715001", nickname: "仓库管理员小王", avatar: "/static/user-avatar.jpg", phone: "138****5678", level: 3, vipLevel: 2, realNameVerified: true, lastLogin: "2023-07-15 10:30" } } }, methods: { goToAuthorityManagement(){ uni.navigateTo({ url: '/pages/tabbar/authorityManagement/authorityManagement' }) }, navigateBack() { uni.navigateBack() }, editNickname() { uni.showModal({ title: '修改昵称', content: '请输入新的昵称', editable: true, placeholderText: this.userInfo.nickname, success: (res) => { if (res.confirm && res.content) { this.userInfo.nickname = res.content uni.showToast({ title: '昵称修改成功' }) } } }) }, logout() { uni.showModal({ title: '确认退出', content: '确定要退出当前账号吗?', success: (res) => { if (res.confirm) { uni.reLaunch({ url: '/pages/tabbar/login/login' }) } } }) } } } </script> <style lang="scss"> .user-profile-container { background-color: #f5f7fa; min-height: 100vh; padding-bottom: 60rpx; } .profile-banner { position: relative; height: 320rpx; .banner-image { width: 100%; height: 100%; filter: brightness(0.85); } .back-button { position: absolute; top: 80rpx; left: 30rpx; width: 70rpx; height: 70rpx; border-radius: 50%; background: rgba(0, 0, 0, 0.2); display: flex; justify-content: center; align-items: center; z-index: 10; } } .profile-card { position: relative; margin: -90rpx 30rpx 30rpx; background: #ffffff; border-radius: 20rpx; box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08); padding: 130rpx 30rpx 40rpx; z-index: 5; } .avatar-section { position: absolute; top: -90rpx; left: 50%; transform: translateX(-50%); width: 180rpx; height: 180rpx; border-radius: 50%; border: 6rpx solid #fff; box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15); .avatar { width: 100%; height: 100%; border-radius: 50%; background-color: #eee; } .edit-icon { position: absolute; bottom: 0; right: 0; width: 60rpx; height: 60rpx; border-radius: 50%; background: #5e8fff; display: flex; justify-content: center; align-items: center; } } .user-details { text-align: center; .name-section { display: flex; justify-content: center; align-items: center; margin-bottom: 15rpx; .username { font-size: 42rpx; font-weight: 600; color: #333; margin-right: 15rpx; max-width: 400rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } .user-id { font-size: 28rpx; color: #999; margin-bottom: 25rpx; display: block; } .tags { display: flex; justify-content: center; gap: 20rpx; .vip-tag { background: linear-gradient(to right, #ffd700, #ffb400); color: #8a6100; padding: 6rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; font-weight: 500; } .level-tag { background: #e6f7ff; color: #1890ff; padding: 6rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; font-weight: 500; } } } .info-section { background: #fff; border-radius: 20rpx; margin: 30rpx; overflow: hidden; box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.05); .section-header { padding: 30rpx; font-size: 34rpx; font-weight: 600; color: #333; border-bottom: 1rpx solid #f0f2f7; } } ::v-deep .uni-list { border-top: none !important; border-bottom: none !important; } ::v-deep .uni-list-item__content { padding-left: 20rpx !important; } .logout-btn { margin: 50rpx 30rpx 0; height: 90rpx; background: #fff; border-radius: 12rpx; display: flex; justify-content: center; align-items: center; font-size: 34rpx; color: #ff4d4f; font-weight: 500; box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.05); transition: all 0.3s; &:active { background: #fffafa; transform: scale(0.98); } } </style>Module Error (from ./node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/loaders/templateLoader.js): 19:25:02.864 (Emitted value instead of an instance of Error) 19:25:02.864 Errors compiling template: 19:25:02.864 <template v-slot> can only appear at the root level inside the receiving component 19:25:02.864 41 | <text>权限管理</text> 19:25:02.864 42 | </view> 19:25:02.864 43 | <template v-slot:header> 19:25:02.864 | ^^^^^^^^^^^^^^^^^^^^^^^^ 19:25:02.864 44 | <uni-icons type="info" size="20" color="#5e8fff"></uni-icons> 19:25:02.864 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19:25:02.864 45 | </template> 19:25:02.864 | ^^^^^^^^^^^^ 19:25:02.864 <template v-slot> can only appear at the root level inside the receiving component 19:25:02.864 52 | </view> 19:25:02.864 53 | 19:25:02.864 54 | <template v-slot:header> 19:25:02.864 | ^^^^^^^^^^^^^^^^^^^^^^^^ 19:25:02.864 55 | <uni-icons type="info" size="20" color="#5e8fff"></uni-icons> 19:25:02.864 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19:25:02.864 56 | </template> 19:25:02.864 | ^^^^^^^^^^^^^^^^^^^^^ 19:25:02.864 at E:\webProject\weixin-test-starter\pages\tabbar\userInfo\userInfo.nvue:0 19:25:03.031 已停止运行...
11-18
int alarm_start_snapshot(WORKER_CTX *worker_ctx, ALARM_PRODUCER *producer, void *iot_info_p) { MEDIACENTER_CTX *mediacenter_ctx = NULL; ALARM_CONSUMER *snapshot_consumer = NULL; STORAGE_SESSION *storage_session = NULL; DEV_INFO *dev_info = NULL; time_t timestamp; bool is_image_supplement = false, is_pushable = true, is_repeated_event = false; time_t record_timestamp = -1; IOT_INFO * iot_info = (IOT_INFO *) iot_info_p; if (!worker_ctx || !producer) { DBG_ERR("arg NULL\n"); return -1; } DBG_DBG("[alarm snapshot] ip=%s id=%d\n", producer->dev_ip, producer->count_id); mediacenter_ctx = (MEDIACENTER_CTX*)worker_ctx->top_ctx; dev_info = get_dev_info(mediacenter_ctx, producer->dev_id, producer->dev_ip); if (!dev_info) { DBG_ERR("find device failed\n"); return -1; } /* 已推送门铃文本消息,需补充带图门铃事件,其它侦测(防盗报警除外)图片修改为门铃类型 */ if (!dev_info->general_camera && RING_SNAPSHOT_NEEDED == dev_info->need_image_supplyment && EVENT_TYPE_ANTI_THEFT != producer->event_type) { DBG_DBG("set producer->event_type(%d) to RING, this snapshot is a supplement\n", producer->event_type); alarm_producer_set_event_type(producer, "RING"); is_image_supplement = true; } if (!iot_info) { /* get iot info */ iot_info = iot_info_find(worker_ctx->iot_info_server, dev_info->dev_id, producer->count_id); } /* calculate timestamp */ if (producer->event_time) { timestamp = producer->event_time; } else { timestamp = producer->count_id; } if (!dev_info->general_camera) { timestamp -= common_get_time_diff(); if (timestamp < (time(NULL)-60*60)) { // DBG_INFO("KC time error\n"); #ifndef TAPO_CAMERA // FIXME: iot_info can only record event timestamp once if (iot_info) { DBG_INFO("Use record time\n"); timestamp = iot_info->event_timestamp; } else { DBG_INFO("Use KH time\n"); timestamp = time(NULL); } #else // DBG_INFO("Use KH time\n"); timestamp = time(NULL); #endif } else { DBG_DBG("KC time OK\n"); } } /* consumer */ #ifdef TAPO_CAMERA if (dev_info->cloud_storage != TAPOCARE_ENABLE) { if (dev_info->general_camera) { if (EVENT_TYPE_DOORBELL_RING == producer->event_type) { record_timestamp = dev_info->pRecord ? dev_info->pRecord->fileTimeUtcForRing : -1; if (ring_event_info_filter(&dev_info->ring_info_list, record_timestamp, NULL)) { ring_event_info_new(NULL, &dev_info->ring_info_list, timestamp, record_timestamp, producer->count_id, dev_info->cloud_storage); } } DBG_DBG("general camera not push\n"); return 0; } // no tapocare, normal msg push only is_pushable = is_alarm_need_notify(dev_info, producer->event_type, false); if (EVENT_TYPE_DOORBELL_RING == producer->event_type) { ring_alarm_handler_simple(dev_info, timestamp, producer->count_id); } #ifdef MULTILENS_CAMERA if (alarm_normal_msg_push_request(dev_info, producer->event_type, is_pushable, 0, dev_info->lens_id)) #else if (alarm_normal_msg_push_request(dev_info, producer->event_type, is_pushable, 0)) #endif { DBG_ERR("normal msg_push failed\n"); return -1; } return 0; } if (producer->content_len < 1) { // no snapshot, will not trigger cloud connection return 1; } #ifdef AI_ENHANCE #ifdef MULTILENS_CAMERA int i = 0; if (dev_info->dev_lens_num > 1) { for(i = 0; i < dev_info->dev_lens_num ;i++) { if(dev_info->multilens_info[i].lenschn_id == dev_info->lens_id) { if (dev_info->multilens_info[i].ai_enhanced == 1) { if (EVENT_TYPE_MOTION == producer->event_type) { if (!(dev_info->multilens_info[i].cam_bitmap & CAM_MD_BITMAP)) { DBG_WARN("dev[%s][%d] not enable MD, don't push snapshot\n", dev_info->dev_mac,i); /* return 1 for save snapshot to sd card or disk */ return 1; } } } break; } } } else #endif { if (dev_info->ai_enhanced == 1) { if (EVENT_TYPE_MOTION == producer->event_type) { if (!(dev_info->cam_bitmap & CAM_MD_BITMAP)) { DBG_WARN("dev[%s] not enable MD, don't push snapshot\n", dev_info->dev_mac); /* return 1 for save snapshot to sd card or disk */ return 1; } } } } #endif #endif if (dev_info->alarm_snapshot_push_status) { DBG_DBG("dev %s in alarm snapshot push status, save:%d\n", dev_info->dev_id, producer->event_type); } if (dev_info->general_camera) { switch (dev_info->general_ring_status) { case RING_SNAPSHOT_NEEDED: /* ring call 周期内,第一个带图事件标记为RING */ if(producer->event_type != EVENT_TYPE_DOORBELL_RING) { DBG_ERR("set producer event type to RING\n"); alarm_producer_set_event_type(producer, "RING"); } break; default: break; } } if (producer->event_type == EVENT_TYPE_PANORAMA) { /* PANORAMA需在推送PANORAMA_START成功后推送 */ if (!dev_info->panorama_start_status) { DBG_ERR("not sent PANORAMA_START yet\n"); return -1; } dev_info->panorama_start_status = 0;/* PANORAMA_START状态清除 */ } if (!(snapshot_consumer = alarm_snapshot_consumer_no_socket_new(worker_ctx->alarm_server))) { DBG_ERR("get snapshot_consumer failed\n"); return -1; } DBG_DBG("OOO: snapshot consumer: %p", snapshot_consumer); MYDEBUG("Device Id = %s, snapshot consumer = %p\n", dev_info->dev_id, snapshot_consumer); alarm_consumer_set_content_len(snapshot_consumer, producer->content_len); alarm_consumer_set_dev_ip(snapshot_consumer, producer->dev_ip); alarm_consumer_set_dev_id(snapshot_consumer, dev_info->dev_id); alarm_consumer_set_dev_model(snapshot_consumer, dev_info->dev_model); alarm_consumer_set_dev_alias(snapshot_consumer, dev_info->dev_alias); alarm_consumer_set_count_id(snapshot_consumer, producer->count_id); alarm_consumer_set_event_timestamp(snapshot_consumer, timestamp); alarm_consumer_set_region(snapshot_consumer, &producer->region); alarm_consumer_set_chunked(snapshot_consumer, 0); alarm_consumer_set_event_type(snapshot_consumer, producer->event_type); alarm_consumer_set_ts_encoded(snapshot_consumer, 0); alarm_consumer_set_image_supplement_flag(snapshot_consumer, is_image_supplement); // FIX: ring_timer_status need update is_pushable = is_alarm_need_notify(dev_info, snapshot_consumer->event_type, snapshot_consumer->is_image_supplement); alarm_consumer_set_silent_push_flag(snapshot_consumer, !is_pushable); if (dev_info->general_camera) { alarm_consumer_set_general_doorbell_params(snapshot_consumer, dev_info); } #ifdef MULTILENS_CAMERA snapshot_consumer->lens_id = dev_info->lens_id; #endif /* set consumer type to support image/alert/summary */ DBG_NOTICE(" MULTILENS_TEST : lensid[%d] ready:%d, event_type:%d ",snapshot_consumer->lens_id, iot_info ? iot_info->ready:0, producer->event_type); #ifdef MULTILENS_CAMERA if (alarm_consumer_set_consumer_type(snapshot_consumer, iot_info, dev_info, producer->event_type, snapshot_consumer->lens_id) < 0) #else if (alarm_consumer_set_consumer_type(snapshot_consumer, iot_info, dev_info, producer->event_type) < 0) #endif { DBG_ERR("invalid consumer type, with event (%d)\n", producer->event_type); alarm_consumer_free(snapshot_consumer); return -1; } #ifdef TAPO_CAMERA /* request count of snapshot to cloud: int */ if (snapshot_consumer->consumer_type >= 0 && snapshot_consumer->consumer_type < CONSUMER_TYPE_MAX) { dev_info->telemetry.snapshot[!!mediacenter_ctx->eth_up].request_count[snapshot_consumer->consumer_type]++; } #endif if (dev_info->general_camera) { /* related ID由WiFi camera发送 */ snprintf(snapshot_consumer->related_id, LEN_MAX_EVENT_ID, "%s", dev_info->ring_alarm_id); if (snapshot_consumer->event_type == EVENT_TYPE_DOORBELL_RING && dev_info->general_ring_status == RING_SNAPSHOT_NEEDED) { /* 开始发送RING事件后,Ring Call周期内后续事件需要静默 */ dev_info->general_ring_status = RING_SNAPSHOT_EXIST; } } else if (producer->event_type == EVENT_TYPE_DOORBELL_RING || dev_info->ring_timer_status != RING_EVENT_TIMER_IDLE) { /* related ID由设备端自己构造并赋值 */ if (EVENT_TYPE_DOORBELL_RING == producer->event_type) { is_repeated_event = fast_post_find_fuzzy(dev_info, producer->count_id); if (false == is_repeated_event) { /* subg门铃事件未到或丢失,由wifi门铃事件触发ring call状态更改 */ ring_alarm_handler_simple(dev_info, time(NULL), producer->count_id); } snapshot_consumer->is_repeated_event = is_repeated_event; } snprintf(snapshot_consumer->related_id, LEN_MAX_EVENT_ID, "%s", dev_info->ring_alarm_id); } DBG_DBG("snapshot consumer %p type: %d\n", snapshot_consumer, snapshot_consumer->consumer_type); alarm_producer_add_consumer(producer, snapshot_consumer); #ifdef ALARM_STORAGE storage_delete_posted_session(worker_ctx->storage_server, dev_info->dev_id, STORAGE_SNAPSHOT); /* storage */ storage_session = alarm_snapshot_storage(producer, dev_info->dev_id, worker_ctx->storage_server, timestamp); /* combine */ if (storage_session) { MYDEBUG("Device Id = %s, storage session = %p\n", dev_info->dev_id, storage_session); storage_session_set_event_timestamp(storage_session, timestamp); storage_session_set_region(storage_session, &producer->region); storage_session_set_event_type(storage_session, producer->event_type); storage_session->iot_consumer = snapshot_consumer; } #endif /* iot */ #ifdef TAPO_CAMERA /* do not retry to prevent users from receiving repeated notifications */ alarm_consumer_set_retry_flag(snapshot_consumer, false); #endif snapshot_consumer->storage_session = storage_session; if (iot_info) { iot_info->flag |= IOT_INFO_SNAPSHOT_FLAG; #ifdef TAPO_CAMERA if (iot_info->xtoken[0]) { snprintf(snapshot_consumer->xtoken, LEN_MAX_TOKEN, "%s", iot_info->xtoken); } if (EVENT_TYPE_DOORBELL_RING == producer->event_type) { iot_info->flag |= IOT_INFO_TRANSITION_FLAG; } /* refresh iot_info */ iot_info_set_dev_ip(iot_info, snapshot_consumer->dev_ip); iot_info_set_dev_id(iot_info, snapshot_consumer->dev_id); iot_info_set_count_id(iot_info, snapshot_consumer->count_id); iot_info_set_event_timestamp(iot_info, timestamp); #ifdef MULTILENS_CAMERA iot_info->dev_lens_num = dev_info->dev_lens_num; int lens_index = 0; if(snapshot_consumer->lens_id == 2 && iot_info->dev_lens_num > 1) { lens_index = 1; } iot_info->event_type_summary[lens_index] |= (1 << (producer->event_type + 4)); #ifdef EVENT_TYPE_INFOS iot_info_record_alarm_event_infos(iot_info, 1 << (producer->event_type + 4), timestamp, lens_index); #endif #else /*MULTILENS_CAMERA*/ // iot_info->snapshot_consumer = snapshot_consumer; iot_info->event_type_summary |= (1 << (producer->event_type + 4)); #ifdef EVENT_TYPE_INFOS iot_info_record_alarm_event_infos(iot_info, 1 << (producer->event_type + 4), timestamp); #endif #endif /*MULTILENS_CAMERA*/ #else iot_info->snapshot_consumer = snapshot_consumer; #endif } else { iot_info = iot_info_new(worker_ctx->iot_info_server); if (!iot_info) { DBG_ERR("iot_info_new falied\n"); alarm_consumer_free(snapshot_consumer); return -1; } #ifdef MULTILENS_CAMERA iot_info->dev_lens_num = dev_info->dev_lens_num; #endif iot_info->flag |= IOT_INFO_SNAPSHOT_FLAG; iot_info_set_dev_ip(iot_info, snapshot_consumer->dev_ip); iot_info_set_dev_id(iot_info, snapshot_consumer->dev_id); iot_info_set_count_id(iot_info, snapshot_consumer->count_id); iot_info_set_event_timestamp(iot_info, timestamp); #ifdef TAPO_CAMERA if (EVENT_TYPE_DOORBELL_RING == producer->event_type) { iot_info->flag |= IOT_INFO_TRANSITION_FLAG; } #ifdef MULTILENS_CAMERA int lens_index = 0; if(snapshot_consumer->lens_id == 2 && iot_info->dev_lens_num > 1) { lens_index = 1; } iot_info->event_type_summary[lens_index] |= (1 << (producer->event_type + 4)); #ifdef EVENT_TYPE_INFOS iot_info_record_alarm_event_infos(iot_info, 1 << (producer->event_type + 4), timestamp, lens_index); #endif #else /*MULTILENS_CAMERA*/ iot_info->event_type_summary |= (1 << (producer->event_type + 4)); #ifdef EVENT_TYPE_INFOS iot_info_record_alarm_event_infos(iot_info, 1 << (producer->event_type + 4), timestamp); #endif #endif /*MULTILENS_CAMERA*/ #endif } ALARM_CONSUMER_NODE *pnode = (ALARM_CONSUMER_NODE *)malloc(sizeof(ALARM_CONSUMER_NODE)); if (!pnode) { DBG_ERR("malloc failed\n"); alarm_consumer_free(snapshot_consumer); iot_info_free(iot_info); return -1; } pnode->consumer = snapshot_consumer; list_add_tail(&pnode->list, &iot_info->snapshot_consumer_list); DBG_DBG("add consumer:%p into iot_info:%p list\n", snapshot_consumer, iot_info); if (snapshot_consumer->consumer_type == CONSUMER_XTOKEN_SNAPSHOT) { if (iot_info->ready == IOT_XTOKEN_READY) { DBG_ERR("========critical========\n"); } iot_info->ready = IOT_XTOKEN_WAITING; } iot_info->idle_for_post = false; snapshot_consumer->iot_info = iot_info; return 0; } 解释代码
最新发布
12-25
<template> <div class="camera-container"> <!-- 视频播放区域 --> <video ref="videoPlayer" controls autoplay muted playsinline></video><!--autoplay是规定一旦视频就绪马上开始播放。muted 输出视频静音,playsinline指明视频将内嵌(inline)播放,controls会在视频底部提供一个控制面板--> <!-- 控制面板 --> <div class="controls"> <input v-model="cameraUrl" placeholder="rtsp://username:password@ip:port/stream" class="url-input" /> <button @click="connectCamera" :disabled="isConnecting"> {{ isConnecting ? '连接中...' : '连接摄像头' }} </button> <button @click="disconnectCamera" :disabled="!isConnected">断开连接</button> <!-- 状态指示器 --> <div class="status-indicator" :class="connectionStatusClass"> {{ connectionStatusText }} </div> </div> <!-- 错误提示 --> <div v-if="errorMessage" class="error-message"> {{ errorMessage }} </div> </div> </template> <script lang="ts"> import { defineComponent, ref, computed, onBeforeUnmount } from 'vue'; import flvjs from 'flv.js'; // 安装: npm install flv.js // 定义摄像头连接状态类型 type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'; export default defineComponent({ name: 'IpCameraViewer', setup() { // 使用TypeScript类型声明 const videoPlayer = ref<HTMLVideoElement | null>(null); const flvPlayer = ref<any>(null); const cameraUrl = ref('rtsp://admin:12345@192.168.1.100:554/Streaming/Channels/101'); const connectionStatus = ref<ConnectionStatus>('disconnected'); const errorMessage = ref<string>(''); const reconnectAttempts = ref(0); const maxReconnectAttempts = 3; // 计算属性:连接状态文本 const connectionStatusText = computed(() => { switch (connectionStatus.value) { case 'disconnected': return '未连接'; case 'connecting': return '连接中...'; case 'connected': return '已连接'; case 'error': return '连接错误'; default: return '未知状态'; } }); // 计算属性:连接状态对应的CSS类 const connectionStatusClass = computed(() => { return `status-${connectionStatus.value}`; }); // 计算属性:是否正在连接 const isConnecting = computed(() => { return connectionStatus.value === 'connecting'; }); // 计算属性:是否已连接 const isConnected = computed(() => { return connectionStatus.value === 'connected'; }); // 连接摄像头 const connectCamera = async () => { try { // 重置状态 errorMessage.value = ''; connectionStatus.value = 'connecting'; reconnectAttempts.value = 0; // 断开现有连接 disconnectCamera(); // 验证URL格式 if (!validateCameraUrl(cameraUrl.value)) { throw new Error('摄像头URL格式无效'); } // 检查FLV支持 if (!flvjs.isSupported()) { throw new Error('当前浏览器不支持FLV播放'); } // 创建FLV播放器 flvPlayer.value = flvjs.createPlayer({ type: 'flv', isLive: true, url: getStreamProxyUrl(cameraUrl.value) }, { enableStashBuffer: false, // 禁用缓冲以减少延迟 autoCleanupSourceBuffer: true }); // 添加事件监听器 flvPlayer.value.on(flvjs.Events.ERROR, handlePlayerError); flvPlayer.value.on(flvjs.Events.METADATA_ARRIVED, () => { console.log('流媒体元数据已接收'); }); // 绑定视频元素并播放 if (videoPlayer.value) { flvPlayer.value.attachMediaElement(videoPlayer.value); flvPlayer.value.load(); // 尝试播放 const playPromise = flvPlayer.value.play(); if (playPromise !== undefined) { playPromise.catch((err: unknown) => { handlePlayerError(flvjs.ErrorTypes.NETWORK_ERROR, err); }); } connectionStatus.value = 'connected'; } } catch (error) { handleConnectionError(error as Error); } }; // 断开连接 const disconnectCamera = () => { if (flvPlayer.value) { flvPlayer.value.pause(); flvPlayer.value.unload(); flvPlayer.value.detachMediaElement(); flvPlayer.value.destroy(); flvPlayer.value = null; } connectionStatus.value = 'disconnected'; }; // 处理播放器错误 const handlePlayerError = (type: string, detail: any) => { console.error('FLV播放器错误:', type, detail); // 根据错误类型处理 if (type === flvjs.ErrorTypes.NETWORK_ERROR) { errorMessage.value = '网络错误: 无法连接到视频流'; } else if (type === flvjs.ErrorTypes.MEDIA_ERROR) { errorMessage.value = '媒体错误: 流格式不受支持'; } else { errorMessage.value = `播放器错误: ${detail || '未知错误'}`; } connectionStatus.value = 'error'; // 尝试重新连接 if (reconnectAttempts.value < maxReconnectAttempts) { reconnectAttempts.value += 1; setTimeout(() => { if (connectionStatus.value !== 'connected') { connectCamera(); } }, 2000 * reconnectAttempts.value); // 指数退避重连 } }; // 处理连接错误 const handleConnectionError = (error: Error) => { console.error('摄像头连接错误:', error); errorMessage.value = error.message || '连接摄像头失败'; connectionStatus.value = 'error'; // 尝试重新连接 if (reconnectAttempts.value < maxReconnectAttempts) { reconnectAttempts.value += 1; setTimeout(connectCamera, 3000); } }; // 验证摄像头URL格式 const validateCameraUrl = (url: string): boolean => { try { const rtspRegex = /^rtsp:\/\/.+/i; return rtspRegex.test(url); } catch { return false; } }; // 获取流代理URL(实际项目中应调用后端API) const getStreamProxyUrl = (originalUrl: string): string => { // 在实际应用中,这里应该调用后端服务将RTSP转为HTTP-FLV // 示例中仅做URL编码处理 return `http://your-proxy-server/live?url=${encodeURIComponent(originalUrl)}`; }; // 组件卸载时自动断开连接 onBeforeUnmount(() => { disconnectCamera(); }); return { videoPlayer, cameraUrl, connectionStatus, connectionStatusText, connectionStatusClass, errorMessage, isConnecting, isConnected, connectCamera, disconnectCamera }; } }); </script> <style scoped> .camera-container { max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; border-radius: 8px; } video { width: 100%; background: #000; border-radius: 4px; } .controls { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } .url-input { flex: 1; min-width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; } button { padding: 8px 15px; background: #42b983; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background 0.3s; } button:disabled { background: #cccccc; cursor: not-allowed; } button:not(:disabled):hover { background: #359c6f; } .status-indicator { padding: 5px 10px; border-radius: 4px; font-size: 0.9em; } .status-disconnected { background: #ffdddd; color: #cc0000; } .status-connecting { background: #fff8dd; color: #e6b800; } .status-connected { background: #ddffdd; color: #00aa00; } .status-error { background: #ffdddd; color: #cc0000; } .error-message { margin-top: 10px; padding: 10px; background: #ffebee; color: #b71c1c; border-radius: 4px; } </style>
07-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值