643 lines
16 KiB
Markdown
643 lines
16 KiB
Markdown
|
|
# 无人机实时视频接口使用示例
|
|||
|
|
|
|||
|
|
## 快速开始
|
|||
|
|
|
|||
|
|
本指南展示如何在你的项目中快速集成无人机实时视频功能。
|
|||
|
|
|
|||
|
|
## 1. 基础用法 - 直接导航到视频页面
|
|||
|
|
|
|||
|
|
最简单的方式是直接导航到 `UavLiveVideoPage`:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/pages/uav_live_video_page.dart';
|
|||
|
|
|
|||
|
|
// 在任意地方调用
|
|||
|
|
void _openVideo() {
|
|||
|
|
Navigator.push(
|
|||
|
|
context,
|
|||
|
|
MaterialPageRoute(
|
|||
|
|
builder: (context) => UavLiveVideoPage(
|
|||
|
|
droneSn: '1581F8HGX253U00A063U', // 无人机序列号
|
|||
|
|
cameraIndex: '176-0-0', // 摄像头编号
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 2. 高级用法 - 在自定义页面中集成
|
|||
|
|
|
|||
|
|
如果你需要在自己的页面中集成视频功能,可以按照以下步骤操作:
|
|||
|
|
|
|||
|
|
### 步骤 1: 创建页面并初始化 BLoC
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
import 'package:flutter/material.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';
|
|||
|
|
|
|||
|
|
class MyCustomVideoPage extends StatefulWidget {
|
|||
|
|
final String droneSn;
|
|||
|
|
final String cameraIndex;
|
|||
|
|
|
|||
|
|
const MyCustomVideoPage({
|
|||
|
|
super.key,
|
|||
|
|
required this.droneSn,
|
|||
|
|
required this.cameraIndex,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<MyCustomVideoPage> createState() => _MyCustomVideoPageState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _MyCustomVideoPageState extends State<MyCustomVideoPage> {
|
|||
|
|
late DroneStationBloc _bloc;
|
|||
|
|
UavVideoStreamEntity? _videoStream;
|
|||
|
|
bool _isLoading = false;
|
|||
|
|
String? _errorMessage;
|
|||
|
|
UavLensType? _currentLensType;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_bloc = sl<DroneStationBloc>();
|
|||
|
|
// 默认加载广角镜头
|
|||
|
|
_loadVideoStream(UavLensType.wide);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_bloc.close();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤 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, // Token有效期(毫秒)
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤 3: 构建 UI 并监听状态变化
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
return BlocProvider.value(
|
|||
|
|
value: _bloc,
|
|||
|
|
child: Scaffold(
|
|||
|
|
backgroundColor: Colors.black,
|
|||
|
|
appBar: AppBar(
|
|||
|
|
backgroundColor: Colors.black,
|
|||
|
|
elevation: 0,
|
|||
|
|
leading: IconButton(
|
|||
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|||
|
|
onPressed: () => Navigator.pop(context),
|
|||
|
|
),
|
|||
|
|
title: const Text(
|
|||
|
|
'无人机实时视频',
|
|||
|
|
style: TextStyle(color: Colors.white),
|
|||
|
|
),
|
|||
|
|
centerTitle: true,
|
|||
|
|
actions: [
|
|||
|
|
// 镜头切换按钮
|
|||
|
|
PopupMenuButton<UavLensType>(
|
|||
|
|
icon: const Icon(Icons.videocam, color: Colors.white),
|
|||
|
|
tooltip: '切换镜头',
|
|||
|
|
onSelected: (lensType) {
|
|||
|
|
_loadVideoStream(lensType);
|
|||
|
|
},
|
|||
|
|
itemBuilder: (context) => [
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: UavLensType.wide,
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.videocam, size: 20),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('广角镜头'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: UavLensType.zoom,
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.zoom_in, size: 20),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('变焦镜头'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
const PopupMenuItem(
|
|||
|
|
value: UavLensType.ir,
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
Icon(Icons.thermostat, size: 20),
|
|||
|
|
SizedBox(width: 8),
|
|||
|
|
Text('红外镜头'),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
body: BlocConsumer<DroneStationBloc, DroneStationState>(
|
|||
|
|
listener: (context, state) {
|
|||
|
|
// 监听状态变化
|
|||
|
|
if (state is UavVideoStreamLoaded) {
|
|||
|
|
setState(() {
|
|||
|
|
_videoStream = state.videoStream;
|
|||
|
|
_isLoading = false;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
debugPrint('=== 视频流加载成功 ===');
|
|||
|
|
debugPrint('URL Type: ${state.videoStream.urlType}');
|
|||
|
|
debugPrint('AppId: ${state.videoStream.appId}');
|
|||
|
|
debugPrint('RoomId: ${state.videoStream.roomId}');
|
|||
|
|
debugPrint('UserId: ${state.videoStream.userId}');
|
|||
|
|
|
|||
|
|
// TODO: 这里可以初始化 RTC 引擎并显示视频
|
|||
|
|
_initRtcEngine(state.videoStream);
|
|||
|
|
} else if (state is UavVideoStreamError) {
|
|||
|
|
setState(() {
|
|||
|
|
_errorMessage = state.message;
|
|||
|
|
_isLoading = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
builder: (context, state) {
|
|||
|
|
// 根据状态渲染不同的 UI
|
|||
|
|
|
|||
|
|
// 加载中状态
|
|||
|
|
if (_isLoading || state is UavVideoStreamLoading) {
|
|||
|
|
return const Center(
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
CircularProgressIndicator(color: Colors.white),
|
|||
|
|
SizedBox(height: 16),
|
|||
|
|
Text(
|
|||
|
|
'正在加载视频流...',
|
|||
|
|
style: TextStyle(color: Colors.white),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 错误状态
|
|||
|
|
if (_errorMessage != null || state is UavVideoStreamError) {
|
|||
|
|
return Center(
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
const Icon(
|
|||
|
|
Icons.error_outline,
|
|||
|
|
size: 48,
|
|||
|
|
color: Colors.red,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Text(
|
|||
|
|
_errorMessage ?? (state as UavVideoStreamError).message,
|
|||
|
|
style: const TextStyle(color: Colors.white),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
ElevatedButton(
|
|||
|
|
onPressed: () {
|
|||
|
|
if (_currentLensType != null) {
|
|||
|
|
_loadVideoStream(_currentLensType!);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
child: const Text('重试'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 无视频信号
|
|||
|
|
if (_videoStream == null) {
|
|||
|
|
return const Center(
|
|||
|
|
child: Text(
|
|||
|
|
'暂无视频信号',
|
|||
|
|
style: TextStyle(color: Colors.white),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 视频已加载,显示视频画面
|
|||
|
|
return Container(
|
|||
|
|
width: double.infinity,
|
|||
|
|
height: double.infinity,
|
|||
|
|
color: Colors.black,
|
|||
|
|
child: Center(
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|||
|
|
children: [
|
|||
|
|
const Icon(
|
|||
|
|
Icons.videocam_off,
|
|||
|
|
size: 64,
|
|||
|
|
color: Colors.grey,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Text(
|
|||
|
|
'视频流已获取\nURL Type: ${_videoStream!.urlType}\nCamera: $_currentLensType',
|
|||
|
|
style: const TextStyle(color: Colors.white),
|
|||
|
|
textAlign: TextAlign.center,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
Text(
|
|||
|
|
'请集成 RTC SDK 后在此处显示视频画面',
|
|||
|
|
style: TextStyle(
|
|||
|
|
color: Colors.grey[600],
|
|||
|
|
fontSize: 12,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤 4: 初始化 RTC 引擎(可选)
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
/// 初始化 RTC 引擎
|
|||
|
|
Future<void> _initRtcEngine(UavVideoStreamEntity videoStream) async {
|
|||
|
|
final appId = videoStream.appId;
|
|||
|
|
final roomId = videoStream.roomId;
|
|||
|
|
final token = videoStream.token;
|
|||
|
|
final userId = videoStream.userId.isNotEmpty
|
|||
|
|
? videoStream.userId
|
|||
|
|
: 'user_${DateTime.now().millisecondsSinceEpoch}';
|
|||
|
|
|
|||
|
|
if (appId.isEmpty || roomId.isEmpty || token.isEmpty) {
|
|||
|
|
debugPrint('RTC 参数缺失');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
debugPrint('=== RTC 初始化 ===');
|
|||
|
|
debugPrint('AppId: $appId');
|
|||
|
|
debugPrint('RoomId: $roomId');
|
|||
|
|
debugPrint('UserId: $userId');
|
|||
|
|
debugPrint('URL Type: ${videoStream.urlType}');
|
|||
|
|
|
|||
|
|
// 根据 urlType 选择不同的 RTC SDK
|
|||
|
|
final sdkType = videoStream.urlType.toLowerCase() == 'agora'
|
|||
|
|
? RtcSdkType.agora
|
|||
|
|
: RtcSdkType.volcengine;
|
|||
|
|
|
|||
|
|
if (sdkType == RtcSdkType.agora) {
|
|||
|
|
await _initAgoraEngine(appId, roomId, token, userId);
|
|||
|
|
} else {
|
|||
|
|
await _initVolcEngine(appId, roomId, token, userId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TODO: 实现具体的 RTC 引擎初始化逻辑
|
|||
|
|
// 参考 drone_station_detail_page.dart 中的实现
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 常见场景示例
|
|||
|
|
|
|||
|
|
### 场景 1: 从列表页跳转到视频页
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 在设备列表中点击某个设备
|
|||
|
|
void _onDeviceTap(Device device) {
|
|||
|
|
Navigator.push(
|
|||
|
|
context,
|
|||
|
|
MaterialPageRoute(
|
|||
|
|
builder: (context) => UavLiveVideoPage(
|
|||
|
|
droneSn: device.sn,
|
|||
|
|
cameraIndex: device.cameraIndex,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景 2: 支持多个摄像头切换
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
class MultiCameraVideoPage extends StatefulWidget {
|
|||
|
|
final String droneSn;
|
|||
|
|
final List<String> cameraIndices;
|
|||
|
|
|
|||
|
|
const MultiCameraVideoPage({
|
|||
|
|
super.key,
|
|||
|
|
required this.droneSn,
|
|||
|
|
required this.cameraIndices,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<MultiCameraVideoPage> createState() => _MultiCameraVideoPageState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _MultiCameraVideoPageState extends State<MultiCameraVideoPage> {
|
|||
|
|
late DroneStationBloc _bloc;
|
|||
|
|
int _currentCameraIndex = 0;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_bloc = sl<DroneStationBloc>();
|
|||
|
|
_loadCurrentCamera();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _loadCurrentCamera() {
|
|||
|
|
_bloc.add(
|
|||
|
|
UavVideoStreamLoad(
|
|||
|
|
sn: widget.droneSn,
|
|||
|
|
cameraIndex: widget.cameraIndices[_currentCameraIndex],
|
|||
|
|
lensType: UavLensType.wide,
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _switchCamera(int index) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentCameraIndex = index;
|
|||
|
|
});
|
|||
|
|
_loadCurrentCamera();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
return Scaffold(
|
|||
|
|
appBar: AppBar(
|
|||
|
|
title: Text('摄像头 ${_currentCameraIndex + 1}/${widget.cameraIndices.length}'),
|
|||
|
|
actions: [
|
|||
|
|
// 切换摄像头按钮
|
|||
|
|
IconButton(
|
|||
|
|
icon: const Icon(Icons.switch_camera),
|
|||
|
|
onPressed: () {
|
|||
|
|
final nextIndex = (_currentCameraIndex + 1) % widget.cameraIndices.length;
|
|||
|
|
_switchCamera(nextIndex);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
body: BlocBuilder<DroneStationBloc, DroneStationState>(
|
|||
|
|
builder: (context, state) {
|
|||
|
|
// ... 根据状态渲染UI
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景 3: 自动刷新视频流(Token 过期时)
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
class AutoRefreshVideoPage extends StatefulWidget {
|
|||
|
|
final String droneSn;
|
|||
|
|
final String cameraIndex;
|
|||
|
|
|
|||
|
|
const AutoRefreshVideoPage({
|
|||
|
|
super.key,
|
|||
|
|
required this.droneSn,
|
|||
|
|
required this.cameraIndex,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<AutoRefreshVideoPage> createState() => _AutoRefreshVideoPageState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _AutoRefreshVideoPageState extends State<AutoRefreshVideoPage> {
|
|||
|
|
late DroneStationBloc _bloc;
|
|||
|
|
Timer? _refreshTimer;
|
|||
|
|
static const _tokenRefreshInterval = Duration(minutes: 55); // 每55分钟刷新一次(Token有效期约1小时)
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_bloc = sl<DroneStationBloc>();
|
|||
|
|
_loadVideoStream();
|
|||
|
|
|
|||
|
|
// 启动定时刷新
|
|||
|
|
_startAutoRefresh();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _startAutoRefresh() {
|
|||
|
|
_refreshTimer?.cancel();
|
|||
|
|
_refreshTimer = Timer.periodic(_tokenRefreshInterval, (timer) {
|
|||
|
|
debugPrint('⏰ 自动刷新视频流 Token');
|
|||
|
|
_loadVideoStream();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _loadVideoStream() {
|
|||
|
|
_bloc.add(
|
|||
|
|
UavVideoStreamLoad(
|
|||
|
|
sn: widget.droneSn,
|
|||
|
|
cameraIndex: widget.cameraIndex,
|
|||
|
|
lensType: UavLensType.wide,
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_refreshTimer?.cancel();
|
|||
|
|
_bloc.close();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 最佳实践
|
|||
|
|
|
|||
|
|
### ✅ 推荐做法
|
|||
|
|
|
|||
|
|
1. **始终检查错误状态**
|
|||
|
|
```dart
|
|||
|
|
if (state is UavVideoStreamError) {
|
|||
|
|
// 显示友好的错误提示
|
|||
|
|
showSnackBar(context, '视频加载失败: ${state.message}');
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **提供重试机制**
|
|||
|
|
```dart
|
|||
|
|
ElevatedButton(
|
|||
|
|
onPressed: () => _loadVideoStream(_currentLensType!),
|
|||
|
|
child: const Text('重试'),
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **记录关键日志**
|
|||
|
|
```dart
|
|||
|
|
debugPrint('视频流加载成功: URL Type=${videoStream.urlType}');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **合理设置 Token 有效期**
|
|||
|
|
```dart
|
|||
|
|
videoExpire: 720000000, // 约8天,避免频繁刷新
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5. **及时释放资源**
|
|||
|
|
```dart
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_bloc.close();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ❌ 避免的做法
|
|||
|
|
|
|||
|
|
1. **不要在页面外直接调用 API**
|
|||
|
|
```dart
|
|||
|
|
// 错误:绕过 BLoC 直接调用
|
|||
|
|
final result = await repository.getUavVideoStream(...);
|
|||
|
|
|
|||
|
|
// ✅ 正确:通过 BLoC 管理状态
|
|||
|
|
_bloc.add(UavVideoStreamLoad(...));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **不要忘记处理加载状态**
|
|||
|
|
```dart
|
|||
|
|
// ❌ 错误:没有加载指示器
|
|||
|
|
if (state is UavVideoStreamLoaded) { ... }
|
|||
|
|
|
|||
|
|
// ✅ 正确:显示加载状态
|
|||
|
|
if (state is UavVideoStreamLoading) {
|
|||
|
|
return CircularProgressIndicator();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **不要硬编码参数**
|
|||
|
|
```dart
|
|||
|
|
// 错误:硬编码
|
|||
|
|
sn: '1581F8HGX253U00A063U',
|
|||
|
|
|
|||
|
|
// ✅ 正确:使用变量
|
|||
|
|
sn: widget.droneSn,
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 故障排查
|
|||
|
|
|
|||
|
|
### 问题 1: 视频流加载失败
|
|||
|
|
|
|||
|
|
**可能原因:**
|
|||
|
|
- 网络问题
|
|||
|
|
- 设备序列号错误
|
|||
|
|
- 摄像头编号错误
|
|||
|
|
- Token 过期
|
|||
|
|
|
|||
|
|
**解决方案:**
|
|||
|
|
1. 检查网络连接
|
|||
|
|
2. 验证 `droneSn` 和 `cameraIndex` 是否正确
|
|||
|
|
3. 查看控制台日志输出
|
|||
|
|
4. 尝试重新加载
|
|||
|
|
|
|||
|
|
### 问题 2: 视频画面不显示
|
|||
|
|
|
|||
|
|
**可能原因:**
|
|||
|
|
- RTC SDK 未正确初始化
|
|||
|
|
- RTC 参数解析失败
|
|||
|
|
- SDK 版本不兼容
|
|||
|
|
|
|||
|
|
**解决方案:**
|
|||
|
|
1. 检查 `urlType` 字段,确认使用正确的 SDK
|
|||
|
|
2. 验证 AppId、RoomId、Token、UserId 是否正确解析
|
|||
|
|
3. 查看 RTC SDK 的日志输出
|
|||
|
|
4. 参考 `drone_station_detail_page.dart` 中的实现
|
|||
|
|
|
|||
|
|
### 问题 3: 镜头切换无效
|
|||
|
|
|
|||
|
|
**可能原因:**
|
|||
|
|
- 后端不支持该镜头类型
|
|||
|
|
- 摄像头不支持指定的镜头
|
|||
|
|
|
|||
|
|
**解决方案:**
|
|||
|
|
1. 检查后端返回的错误信息
|
|||
|
|
2. 尝试其他镜头类型
|
|||
|
|
3. 联系后端确认支持的镜头类型
|
|||
|
|
|
|||
|
|
## 6. 扩展功能
|
|||
|
|
|
|||
|
|
### 添加视频录制功能
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// TODO: 集成 RTC SDK 的录制功能
|
|||
|
|
Future<void> _startRecording() async {
|
|||
|
|
// 根据使用的 RTC SDK 调用相应的录制 API
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _stopRecording() async {
|
|||
|
|
// 停止录制并保存文件
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 添加截图功能
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// TODO: 集成 RTC SDK 的截图功能
|
|||
|
|
Future<void> _takeScreenshot() async {
|
|||
|
|
// 根据使用的 RTC SDK 调用相应的截图 API
|
|||
|
|
// 保存截图到相册
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 添加视频质量切换
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
void _changeQuality(VideoQualityType quality) {
|
|||
|
|
_bloc.add(
|
|||
|
|
UavVideoStreamLoad(
|
|||
|
|
sn: widget.droneSn,
|
|||
|
|
cameraIndex: widget.cameraIndex,
|
|||
|
|
lensType: _currentLensType,
|
|||
|
|
qualityType: quality, // 切换清晰度
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 相关文档
|
|||
|
|
|
|||
|
|
- [完整集成指南](./UAV_VIDEO_INTEGRATION_GUIDE.md)
|
|||
|
|
- [API 接口文档](../lib/core/consts/http_api_consts.dart)
|
|||
|
|
- [实体类定义](../lib/features/v2/device_list/domain/entities/uav_video_stream_entity.dart)
|
|||
|
|
- [BLoC 状态管理](../lib/features/v2/device_list/presentation/bloc/)
|
|||
|
|
|
|||
|
|
## 8. 技术支持
|
|||
|
|
|
|||
|
|
如遇到问题,请:
|
|||
|
|
1. 查看控制台日志输出
|
|||
|
|
2. 参考示例代码 `uav_live_video_page.dart`
|
|||
|
|
3. 查阅集成指南文档
|
|||
|
|
4. 联系开发团队
|