299 lines
7.6 KiB
Markdown
299 lines
7.6 KiB
Markdown
# 无人机视频页面集成完成
|
||
|
||
## 📋 更新内容
|
||
|
||
已将新的无人机实时视频接口集成到现有的**无人机视频回传/远程控制**页面中。
|
||
|
||
---
|
||
|
||
## 🎯 集成位置
|
||
|
||
### 1. 无人机详情页面 → 无人机状态卡片
|
||
|
||
**文件**: `lib/features/v2/device_list/presentation/pages/drone_station_detail_page.dart`
|
||
|
||
**修改内容**:
|
||
```dart
|
||
// 点击"无人机状态"卡片时,传递设备序列号和摄像头索引
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (context) => DroneVideoControlPage(
|
||
droneSn: detail.deviceSn, // ✅ 传递设备序列号
|
||
cameraIndex: detail.gatewayCameraList != null &&
|
||
detail.gatewayCameraList!.isNotEmpty
|
||
? detail.gatewayCameraList!.first.cameraIndex
|
||
: '176-0-0', // 默认值
|
||
),
|
||
),
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 无人机视频控制页面
|
||
|
||
**文件**: `lib/features/v2/device_list/presentation/pages/drone_video_control_page.dart`
|
||
|
||
**新增功能**:
|
||
|
||
#### ✅ 接收参数
|
||
```dart
|
||
class DroneVideoControlPage extends StatefulWidget {
|
||
final String droneSn; // 无人机设备序列号
|
||
final String cameraIndex; // 摄像头编号
|
||
|
||
const DroneVideoControlPage({
|
||
super.key,
|
||
required this.droneSn,
|
||
required this.cameraIndex,
|
||
});
|
||
}
|
||
```
|
||
|
||
#### ✅ 自动加载视频流
|
||
```dart
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_bloc = sl<DroneStationBloc>();
|
||
// 默认加载广角镜头的视频流
|
||
_loadVideoStream(UavLensType.wide);
|
||
}
|
||
```
|
||
|
||
#### ✅ 镜头切换功能
|
||
在视频播放器右下角添加了镜头类型选择器:
|
||
- **广角** (wide)
|
||
- **变焦** (zoom)
|
||
- **红外** (ir)
|
||
|
||
点击可快速切换不同镜头的视频流。
|
||
|
||
#### ✅ 加载状态显示
|
||
```
|
||
┌─────────────────────────┐
|
||
│ │
|
||
│ ⏳ 正在加载... │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
#### ✅ 错误处理和重试
|
||
```
|
||
┌─────────────────────────┐
|
||
│ │
|
||
│ ❌ 加载失败 │
|
||
│ 错误信息: xxxxx │
|
||
│ │
|
||
│ [ 重试 ] │
|
||
│ │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 工作流程
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[无人机详情页] --> B[点击无人机状态卡片]
|
||
B --> C[打开视频控制页面]
|
||
C --> D[自动调用changeLens接口]
|
||
D --> E{加载结果}
|
||
E -->|成功| F[显示视频画面]
|
||
E -->|失败| G[显示错误+重试按钮]
|
||
F --> H[用户切换镜头]
|
||
H --> D
|
||
G -->|点击重试| D
|
||
```
|
||
|
||
---
|
||
|
||
## 📱 UI 展示
|
||
|
||
### 视频播放器区域
|
||
|
||
```
|
||
┌──────────────────────────────────────┐
|
||
│ ● REC 00:12:36 │
|
||
│ │
|
||
│ │
|
||
│ [视频画面/加载状态] │
|
||
│ │
|
||
│ │
|
||
│ ┌────────┐ │
|
||
│ │广角 ▼│ │ ← 镜头切换
|
||
│ └────────┘ │
|
||
└──────────────────────────────────────┘
|
||
```
|
||
|
||
### 镜头切换菜单
|
||
|
||
```
|
||
┌──────────┐
|
||
│ 广角 │ ← 当前选中
|
||
│ 变焦 │
|
||
│ 红外 │
|
||
└──────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 技术实现
|
||
|
||
### 1. BLoC 状态管理
|
||
```dart
|
||
// 监听视频流状态
|
||
BlocConsumer<DroneStationBloc, DroneStationState>(
|
||
listener: (context, state) {
|
||
if (state is UavVideoStreamLoaded) {
|
||
// 获取视频流数据
|
||
_videoStream = state.videoStream;
|
||
|
||
// TODO: 初始化 RTC 引擎并显示视频
|
||
debugPrint('AppId: ${state.videoStream.appId}');
|
||
debugPrint('RoomId: ${state.videoStream.roomId}');
|
||
debugPrint('UserId: ${state.videoStream.userId}');
|
||
} else if (state is UavVideoStreamError) {
|
||
// 显示错误信息
|
||
_errorMessage = state.message;
|
||
}
|
||
},
|
||
// ...
|
||
)
|
||
```
|
||
|
||
### 2. 视频流加载
|
||
```dart
|
||
void _loadVideoStream(UavLensType lensType) {
|
||
setState(() {
|
||
_isLoading = true;
|
||
_errorMessage = null;
|
||
_currentLensType = lensType;
|
||
});
|
||
|
||
_bloc.add(
|
||
UavVideoStreamLoad(
|
||
sn: widget.droneSn,
|
||
cameraIndex: widget.cameraIndex,
|
||
lensType: lensType,
|
||
qualityType: VideoQualityType.adaptive,
|
||
videoExpire: 720000000,
|
||
),
|
||
);
|
||
}
|
||
```
|
||
|
||
### 3. 镜头切换
|
||
```dart
|
||
PopupMenuButton<UavLensType>(
|
||
icon: Text(_getLensTypeName(_currentLensType)),
|
||
onSelected: (UavLensType lensType) {
|
||
if (lensType != _currentLensType) {
|
||
_loadVideoStream(lensType); // 重新加载视频流
|
||
}
|
||
},
|
||
itemBuilder: (context) => [
|
||
PopupMenuItem(value: UavLensType.wide, child: Text('广角')),
|
||
PopupMenuItem(value: UavLensType.zoom, child: Text('变焦')),
|
||
PopupMenuItem(value: UavLensType.ir, child: Text('红外')),
|
||
],
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 已完成的功能
|
||
|
||
- ✅ 从无人机详情页传递设备序列号和摄像头索引
|
||
- ✅ 自动调用 `changeLens` 接口获取视频流
|
||
- ✅ 支持三种镜头类型切换(广角/变焦/红外)
|
||
- ✅ 加载状态显示
|
||
- ✅ 错误处理和重试机制
|
||
- ✅ 打印视频流参数(AppId、RoomId、UserId等)
|
||
- ✅ 清洁架构设计,易于扩展和维护
|
||
|
||
---
|
||
|
||
## 🔜 下一步工作(TODO)
|
||
|
||
### 1. 集成 RTC SDK 显示实际视频
|
||
|
||
在 `_buildVideoPlayer()` 方法中,当 `_videoStream != null` 时:
|
||
|
||
```dart
|
||
if (_videoStream != null) {
|
||
// 根据 urlType 选择对应的 RTC SDK
|
||
if (_videoStream!.urlType == 'volc') {
|
||
// 使用火山引擎 RTC SDK
|
||
return VolcEngineRtcView(
|
||
appId: _videoStream!.appId,
|
||
roomId: _videoStream!.roomId,
|
||
token: _videoStream!.token,
|
||
userId: _videoStream!.userId,
|
||
);
|
||
} else if (_videoStream!.urlType == 'agora') {
|
||
// 使用声网 RTC SDK
|
||
return AgoraRtcView(
|
||
appId: _videoStream!.appId,
|
||
channelId: _videoStream!.roomId,
|
||
token: _videoStream!.token,
|
||
uid: int.parse(_videoStream!.userId),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 参考现有实现
|
||
|
||
可以参考 `drone_station_detail_page.dart` 中的以下方法:
|
||
- `_initFloatingVolcEngine()` - 火山引擎初始化
|
||
- `_initFloatingAgoraEngine()` - 声网引擎初始化
|
||
- `_disposeVolcEngine()` / `_disposeAgoraEngine()` - 资源释放
|
||
|
||
### 3. 添加更多控制功能
|
||
|
||
- 云台控制(上下左右)
|
||
- 变焦倍数调节
|
||
- 拍照/录像
|
||
- AI识别结果展示
|
||
|
||
---
|
||
|
||
## 📝 测试步骤
|
||
|
||
1. **进入无人机详情页**
|
||
- 确保无人机在线且有摄像头列表
|
||
|
||
2. **点击"无人机状态"卡片**
|
||
- 应该跳转到视频控制页面
|
||
|
||
3. **观察加载过程**
|
||
- 看到 "正在加载视频流..." 提示
|
||
- 成功后显示视频画面(当前为占位图)
|
||
|
||
4. **测试镜头切换**
|
||
- 点击右下角的镜头类型按钮
|
||
- 选择"广角"、"变焦"或"红外"
|
||
- 观察是否重新加载并切换成功
|
||
|
||
5. **测试错误处理**
|
||
- 断开网络连接
|
||
- 应该显示错误信息和重试按钮
|
||
- 点击重试应该重新加载
|
||
|
||
---
|
||
|
||
## 🎉 总结
|
||
|
||
✅ **集成完成!**
|
||
|
||
现在无人机视频控制页面已经:
|
||
- 正确接收设备参数
|
||
- 自动调用新的视频流接口
|
||
- 支持多镜头切换
|
||
- 完善的加载和错误处理
|
||
|
||
只需最后一步:**集成 RTC SDK 显示实际视频画面**,即可完整实现无人机实时视频监控功能!
|