324 lines
9.4 KiB
Markdown
324 lines
9.4 KiB
Markdown
|
|
# 无人机实时视频接口对接说明
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本模块实现了无人机实时视频流的获取功能,采用清洁架构设计,支持多种镜头类型切换(广角、变焦、红外),并兼容火山引擎和声网两种RTC SDK。
|
|||
|
|
|
|||
|
|
## API 接口
|
|||
|
|
|
|||
|
|
### 接口地址
|
|||
|
|
```
|
|||
|
|
POST http://1.95.137.212:59015/iot/UAV/changeLens
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 请求参数
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"sn": "1581F8HGX253U00A063U", // 无人机设备序列号(必填)
|
|||
|
|
"lensType": "", // 镜头类型:wide(广角)、zoom(变焦)、ir(红外),可为空
|
|||
|
|
"cameraIndex": "176-0-0", // 摄像头编号(必填)
|
|||
|
|
"qualityType": "adaptive", // 清晰度:adaptive(自适应)、low、medium、high(默认adaptive)
|
|||
|
|
"videoExpire": 720000000 // Token有效期(毫秒,默认720000000)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 返回数据
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"msg": "操作成功",
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"sn": "1581F8HGX253U00A063U",
|
|||
|
|
"camera_index": "176-0-0",
|
|||
|
|
"url": "app_id=xxx&expire_time=xxx&room_id=xxx&token=xxx&user_id=xxx",
|
|||
|
|
"expire_ts": 1781155234,
|
|||
|
|
"url_type": "volc" // volc(火山引擎) 或 agora(声网)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 架构设计
|
|||
|
|
|
|||
|
|
### 目录结构
|
|||
|
|
```
|
|||
|
|
lib/features/v2/device_list/
|
|||
|
|
├── domain/
|
|||
|
|
│ ├── entities/
|
|||
|
|
│ │ └── uav_video_stream_entity.dart # 实体类
|
|||
|
|
│ ├── repositories/
|
|||
|
|
│ │ ── drone_station_repository.dart # 仓库接口
|
|||
|
|
│ └── usecases/
|
|||
|
|
│ └── get_uav_video_stream_usecase.dart # 用例
|
|||
|
|
├── data/
|
|||
|
|
│ ├── datasources/
|
|||
|
|
│ │ ├── drone_station_datasource.dart # 数据源接口
|
|||
|
|
│ │ └── drone_station_datasource_impl.dart # 数据源实现
|
|||
|
|
│ └── repositories/
|
|||
|
|
│ └── drone_station_repository_impl.dart # 仓库实现
|
|||
|
|
── presentation/
|
|||
|
|
├── bloc/
|
|||
|
|
│ ├── drone_station_bloc.dart # BLoC状态管理
|
|||
|
|
│ ├── drone_station_event.dart # 事件定义
|
|||
|
|
│ └── drone_station_state.dart # 状态定义
|
|||
|
|
└── pages/
|
|||
|
|
── uav_live_video_page.dart # 示例页面
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 核心组件
|
|||
|
|
|
|||
|
|
#### 1. 实体类 (Entity)
|
|||
|
|
**文件**: `domain/entities/uav_video_stream_entity.dart`
|
|||
|
|
|
|||
|
|
定义了枚举类型和实体类:
|
|||
|
|
- `UavLensType`: 镜头类型枚举(wide、zoom、ir)
|
|||
|
|
- `VideoQualityType`: 视频质量枚举(adaptive、low、medium、high)
|
|||
|
|
- `UavVideoStreamEntity`: 视频流实体,包含解析RTC参数的方法
|
|||
|
|
|
|||
|
|
#### 2. 数据源 (DataSource)
|
|||
|
|
**文件**: `data/datasources/drone_station_datasource_impl.dart`
|
|||
|
|
|
|||
|
|
实现了API调用逻辑:
|
|||
|
|
- 发送POST请求到 `/iot/UAV/changeLens`
|
|||
|
|
- 处理响应并转换为实体对象
|
|||
|
|
- 包含详细的日志输出用于调试
|
|||
|
|
|
|||
|
|
#### 3. 仓库 (Repository)
|
|||
|
|
**文件**: `data/repositories/drone_station_repository_impl.dart`
|
|||
|
|
|
|||
|
|
使用 `fpdart` 的 `Either` 类型处理错误:
|
|||
|
|
- 成功时返回 `Right(UavVideoStreamEntity)`
|
|||
|
|
- 失败时返回 `Left(Failure)`
|
|||
|
|
|
|||
|
|
#### 4. 用例 (UseCase)
|
|||
|
|
**文件**: `domain/usecases/get_uav_video_stream_usecase.dart`
|
|||
|
|
|
|||
|
|
封装业务逻辑,供BLoC调用。
|
|||
|
|
|
|||
|
|
#### 5. BLoC 状态管理
|
|||
|
|
**文件**: `presentation/bloc/drone_station_bloc.dart`
|
|||
|
|
|
|||
|
|
新增事件和状态:
|
|||
|
|
- **事件**: `UavVideoStreamLoad` - 加载视频流
|
|||
|
|
- **状态**:
|
|||
|
|
- `UavVideoStreamLoading` - 加载中
|
|||
|
|
- `UavVideoStreamLoaded` - 加载成功
|
|||
|
|
- `UavVideoStreamError` - 加载失败
|
|||
|
|
|
|||
|
|
#### 6. 依赖注入
|
|||
|
|
**文件**: `core/di/injection.dart`
|
|||
|
|
|
|||
|
|
已注册以下单例:
|
|||
|
|
```dart
|
|||
|
|
sl.registerLazySingleton<GetUavVideoStreamUseCase>(
|
|||
|
|
() => GetUavVideoStreamUseCase(sl()),
|
|||
|
|
);
|
|||
|
|
sl.registerFactory<DroneStationBloc>(
|
|||
|
|
() => DroneStationBloc(sl(), sl(), sl(), sl()),
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用方式
|
|||
|
|
|
|||
|
|
### 方式一:直接使用示例页面
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/pages/uav_live_video_page.dart';
|
|||
|
|
|
|||
|
|
Navigator.push(
|
|||
|
|
context,
|
|||
|
|
MaterialPageRoute(
|
|||
|
|
builder: (context) => UavLiveVideoPage(
|
|||
|
|
droneSn: '1581F8HGX253U00A063U',
|
|||
|
|
cameraIndex: '176-0-0',
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方式二:在现有页面中使用
|
|||
|
|
|
|||
|
|
#### 1. 导入必要的类
|
|||
|
|
```dart
|
|||
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
|
|
import '../../../../../core/di/injection.dart';
|
|||
|
|
import '../../domain/entities/uav_video_stream_entity.dart';
|
|||
|
|
import '../bloc/drone_station_bloc.dart';
|
|||
|
|
import '../bloc/drone_station_event.dart';
|
|||
|
|
import '../bloc/drone_station_state.dart';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 初始化 BLoC
|
|||
|
|
```dart
|
|||
|
|
late DroneStationBloc _bloc;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_bloc = sl<DroneStationBloc>();
|
|||
|
|
// 加载视频流
|
|||
|
|
_loadVideoStream(UavLensType.wide);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_bloc.close();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 加载视频流
|
|||
|
|
```dart
|
|||
|
|
void _loadVideoStream(UavLensType lensType) {
|
|||
|
|
setState(() {
|
|||
|
|
_isLoading = true;
|
|||
|
|
_errorMessage = null;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
_bloc.add(
|
|||
|
|
UavVideoStreamLoad(
|
|||
|
|
sn: widget.droneSn,
|
|||
|
|
cameraIndex: widget.cameraIndex,
|
|||
|
|
lensType: lensType,
|
|||
|
|
qualityType: VideoQualityType.adaptive,
|
|||
|
|
videoExpire: 720000000,
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. 监听状态变化
|
|||
|
|
```dart
|
|||
|
|
BlocConsumer<DroneStationBloc, DroneStationState>(
|
|||
|
|
listener: (context, state) {
|
|||
|
|
if (state is UavVideoStreamLoaded) {
|
|||
|
|
final videoStream = state.videoStream;
|
|||
|
|
debugPrint('URL Type: ${videoStream.urlType}');
|
|||
|
|
debugPrint('AppId: ${videoStream.appId}');
|
|||
|
|
debugPrint('RoomId: ${videoStream.roomId}');
|
|||
|
|
debugPrint('UserId: ${videoStream.userId}');
|
|||
|
|
|
|||
|
|
// TODO: 根据 urlType 初始化对应的 RTC 引擎
|
|||
|
|
if (videoStream.urlType.toLowerCase() == 'volc') {
|
|||
|
|
// 使用火山引擎 RTC SDK
|
|||
|
|
} else if (videoStream.urlType.toLowerCase() == 'agora') {
|
|||
|
|
// 使用声网 RTC SDK
|
|||
|
|
}
|
|||
|
|
} else if (state is UavVideoStreamError) {
|
|||
|
|
setState(() {
|
|||
|
|
_errorMessage = state.message;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
builder: (context, state) {
|
|||
|
|
// 根据状态渲染UI
|
|||
|
|
if (state is UavVideoStreamLoading) {
|
|||
|
|
return const CircularProgressIndicator();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (state is UavVideoStreamLoaded) {
|
|||
|
|
// 显示视频画面
|
|||
|
|
return Container();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (state is UavVideoStreamError) {
|
|||
|
|
return Text('错误: ${state.message}');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Container();
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. 切换镜头类型
|
|||
|
|
```dart
|
|||
|
|
// 切换到广角镜头
|
|||
|
|
_loadVideoStream(UavLensType.wide);
|
|||
|
|
|
|||
|
|
// 切换到变焦镜头
|
|||
|
|
_loadVideoStream(UavLensType.zoom);
|
|||
|
|
|
|||
|
|
// 切换到红外镜头
|
|||
|
|
_loadVideoStream(UavLensType.ir);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## RTC 集成
|
|||
|
|
|
|||
|
|
获取到视频流后,需要根据 `urlType` 选择对应的 RTC SDK:
|
|||
|
|
|
|||
|
|
### 火山引擎 (volc)
|
|||
|
|
```dart
|
|||
|
|
final appId = videoStream.appId;
|
|||
|
|
final roomId = videoStream.roomId;
|
|||
|
|
final token = videoStream.token;
|
|||
|
|
final userId = videoStream.userId;
|
|||
|
|
|
|||
|
|
// 使用 volc_engine_rtc SDK
|
|||
|
|
final engine = await RTCEngine.createRTCEngine(
|
|||
|
|
RTCVideoContext(appId: appId, eventHandler: handler),
|
|||
|
|
);
|
|||
|
|
final room = await engine.createRTCRoom(roomId);
|
|||
|
|
await room.joinRoom(token: token, userId: userId);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 声网 (agora)
|
|||
|
|
```dart
|
|||
|
|
final appId = videoStream.appId;
|
|||
|
|
final channelId = videoStream.roomId;
|
|||
|
|
final token = videoStream.token;
|
|||
|
|
final uid = int.tryParse(videoStream.userId) ?? 0;
|
|||
|
|
|
|||
|
|
// 使用 agora_rtc_engine SDK
|
|||
|
|
final engine = createAgoraRtcEngine();
|
|||
|
|
await engine.initialize(RtcEngineContext(appId: appId));
|
|||
|
|
await engine.joinChannel(
|
|||
|
|
token: token,
|
|||
|
|
channelId: channelId,
|
|||
|
|
uid: uid,
|
|||
|
|
options: ChannelMediaOptions(...),
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
|
|||
|
|
1. **可扩展性**: 本模块采用清洁架构设计,所有业务逻辑与UI分离,便于在其他页面复用。
|
|||
|
|
|
|||
|
|
2. **错误处理**: 使用 `Either<Failure, T>` 模式统一处理错误,确保异常不会直接抛出。
|
|||
|
|
|
|||
|
|
3. **日志记录**: 数据源层包含详细的日志输出,方便调试和问题排查。
|
|||
|
|
|
|||
|
|
4. **默认值**:
|
|||
|
|
- `lensType` 可为空,后端会根据实际情况选择默认镜头
|
|||
|
|
- `qualityType` 默认为 `adaptive`(自适应)
|
|||
|
|
- `videoExpire` 默认为 `720000000` 毫秒
|
|||
|
|
|
|||
|
|
5. **Token 有效期**: `videoExpire` 参数单位为毫秒,建议设置较长的有效期以避免频繁刷新。
|
|||
|
|
|
|||
|
|
6. **多镜头支持**: 通过 `UavLensType` 枚举可以轻松扩展更多镜头类型。
|
|||
|
|
|
|||
|
|
## 后续工作
|
|||
|
|
|
|||
|
|
1. [ ] 集成 RTC SDK 显示实际视频画面
|
|||
|
|
2. [ ] 添加视频录制功能
|
|||
|
|
3. [ ] 添加截图功能
|
|||
|
|
4. [ ] 优化视频加载超时处理
|
|||
|
|
5. [ ] 添加视频质量切换功能
|
|||
|
|
6. [ ] 支持多路视频同时观看
|
|||
|
|
|
|||
|
|
## 相关文件清单
|
|||
|
|
|
|||
|
|
### 新增文件
|
|||
|
|
- `lib/features/v2/device_list/domain/entities/uav_video_stream_entity.dart`
|
|||
|
|
- `lib/features/v2/device_list/domain/usecases/get_uav_video_stream_usecase.dart`
|
|||
|
|
- `lib/features/v2/device_list/presentation/pages/uav_live_video_page.dart`
|
|||
|
|
|
|||
|
|
### 修改文件
|
|||
|
|
- `lib/core/consts/http_api_consts.dart` - 添加 API 常量
|
|||
|
|
- `lib/features/v2/device_list/data/datasources/drone_station_datasource.dart` - 添加接口方法
|
|||
|
|
- `lib/features/v2/device_list/data/datasources/drone_station_datasource_impl.dart` - 实现接口
|
|||
|
|
- `lib/features/v2/device_list/data/repositories/drone_station_repository_impl.dart` - 实现仓库
|
|||
|
|
- `lib/features/v2/device_list/domain/repositories/drone_station_repository.dart` - 添加仓库接口
|
|||
|
|
- `lib/features/v2/device_list/presentation/bloc/drone_station_event.dart` - 添加事件
|
|||
|
|
- `lib/features/v2/device_list/presentation/bloc/drone_station_state.dart` - 添加状态
|
|||
|
|
- `lib/features/v2/device_list/presentation/bloc/drone_station_bloc.dart` - 添加事件处理
|
|||
|
|
- `lib/core/di/injection.dart` - 注册依赖
|