USB 摄像头适配iPadOS17+

This commit is contained in:
2026-10-22 12:53:24 +08:00
parent 582242e9aa
commit 58a23ef218
18 changed files with 288 additions and 140 deletions

View File

@@ -96,6 +96,8 @@
"battery": "Battery",
"chip_temp": "Chip Temp",
"knife_speed": "Knife Speed",
"linear_speed": "Linear Speed",
"angular_speed": "Angular Speed",
"control_mode": "Control Mode",
"longitude": "Longitude",
"latitude": "Latitude",
@@ -552,7 +554,11 @@
"path_planning": "Path Planning",
"path_planning_desc": "View and edit the robot patrol path",
"release_permission_success": "Remote control permission released",
"release_permission_failed": "Failed to release control permission"
"release_permission_failed": "Failed to release control permission",
"stat_online_devices": "Online Devices",
"stat_working": "Working",
"stat_standby": "Standby",
"stat_offline": "Offline"
},
"alarm_center": {

View File

@@ -96,6 +96,8 @@
"battery": "电量",
"chip_temp": "芯片温度",
"knife_speed": "割刀速度",
"linear_speed": "线速度",
"angular_speed": "角速度",
"control_mode": "控制模式",
"longitude": "经度",
"latitude": "纬度",
@@ -565,7 +567,11 @@
"path_planning": "路径规划",
"path_planning_desc": "查看和编辑机器人巡检路径",
"release_permission_success": "已释放远程控制权限",
"release_permission_failed": "控制权限释放失败"
"release_permission_failed": "控制权限释放失败",
"stat_online_devices": "在线设备",
"stat_working": "作业中",
"stat_standby": "待命",
"stat_offline": "离线数"
},
"alarm_center": {

View File

@@ -376,6 +376,28 @@ class _DeviceStatusModalState extends State<DeviceStatusModal> {
_buildTableCell('--'),
],
),
TableRow(
children: [
_buildTableCell(
AppLocalizations.of(
context,
).translate('running_status.linear_speed') +
"(m/s)",
),
_buildTableCell(status.linearSpeed.toStringAsFixed(2)),
],
),
TableRow(
children: [
_buildTableCell(
AppLocalizations.of(
context,
).translate('running_status.angular_speed') +
"(rad/s)",
),
_buildTableCell(status.angularSpeed.toStringAsFixed(2)),
],
),
TableRow(
children: [
_buildTableCell(

View File

@@ -28,6 +28,8 @@ class HostRealtimeMessageEntity extends Equatable {
final double rightMotorRpm; // 实时右电机转速
final double roll; // 实时翻滚角度
final String controlMode; // 当前控制模式(遥控器拨杆对应)
final double linearSpeed; // 实时线速度 m/s(前进为正,后退为负)
final double angularSpeed; // 实时角速度 rad/s(左转为正,右转(偏航)为负)
/// 原始 data 报文,便于后端补字段时兜底
final Map<String, dynamic> rawData;
@@ -56,6 +58,8 @@ class HostRealtimeMessageEntity extends Equatable {
this.rightMotorRpm = 0,
this.roll = 0,
this.controlMode = '',
this.linearSpeed = 0,
this.angularSpeed = 0,
this.rawData = const {},
});
@@ -85,6 +89,8 @@ class HostRealtimeMessageEntity extends Equatable {
rightMotorRpm: _toDouble(data['right_motor_rpm']),
roll: _toDouble(data['roll']),
controlMode: data['control_mode']?.toString() ?? '',
linearSpeed: _toDouble(data['linear_speed']),
angularSpeed: _toDouble(data['angular_speed']),
rawData: data,
);
}
@@ -128,5 +134,7 @@ class HostRealtimeMessageEntity extends Equatable {
rightMotorRpm,
roll,
controlMode,
linearSpeed,
angularSpeed,
];
}

View File

@@ -44,6 +44,8 @@ class RunningStatusEntity extends Equatable {
// === 主机实时话题(device/{sn}/host/realTime/message)扩展字段(新增,纯附加) ===
final double totalWorkDistance; // host 累计作业里程(供"巡检里程"展示)
final double linearSpeed; // host 实时线速度 m/s(前进为正,后退为负)
final double angularSpeed; // host 实时角速度 rad/s(左转为正,右转为负)
final bool hostDataReceived; // 是否已收到 host 实时数据(未收到时 UI 显示"未知")
const RunningStatusEntity({
@@ -72,6 +74,8 @@ class RunningStatusEntity extends Equatable {
this.workingArea = '0',
this.obstacleFlag = '0',
this.totalWorkDistance = 0.0,
this.linearSpeed = 0.0,
this.angularSpeed = 0.0,
this.hostDataReceived = false,
});
@@ -173,6 +177,8 @@ class RunningStatusEntity extends Equatable {
String? workingArea,
String? obstacleFlag,
double? totalWorkDistance,
double? linearSpeed,
double? angularSpeed,
bool? hostDataReceived,
}) {
return RunningStatusEntity(
@@ -201,6 +207,8 @@ class RunningStatusEntity extends Equatable {
workingArea: workingArea ?? this.workingArea,
obstacleFlag: obstacleFlag ?? this.obstacleFlag,
totalWorkDistance: totalWorkDistance ?? this.totalWorkDistance,
linearSpeed: linearSpeed ?? this.linearSpeed,
angularSpeed: angularSpeed ?? this.angularSpeed,
hostDataReceived: hostDataReceived ?? this.hostDataReceived,
);
}
@@ -232,6 +240,8 @@ class RunningStatusEntity extends Equatable {
workingArea,
obstacleFlag,
totalWorkDistance,
linearSpeed,
angularSpeed,
hostDataReceived,
];

View File

@@ -327,6 +327,9 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
workingArea: host.totalWorkArea.toString(),
// 🔥 供全功能模式(pv_control)展示:累计作业里程 + host 已推数据标记
totalWorkDistance: host.totalWorkDistance,
// 🔥 车体实测线速度/角速度(后端新增 linear_speed / angular_speed)
linearSpeed: host.linearSpeed,
angularSpeed: host.angularSpeed,
hostDataReceived: true,
);
@@ -334,7 +337,7 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
_cachedGps ??= GPSEntity(_cachedStatus!.latitude, _cachedStatus!.longitude);
debugPrint(
'${LogTime.now()} 🛰️ [MQTT-主机实时] 已合并 → 电量=${host.batteryPercentage}% 电压=${host.batteryVoltage}V 控制模式=${host.controlMode}',
'${LogTime.now()} 🛰️ [MQTT-主机实时] 已合并 → 电量=${host.batteryPercentage}% 电压=${host.batteryVoltage}V 控制模式=${host.controlMode} 线速度=${host.linearSpeed}m/s 角速度=${host.angularSpeed}rad/s',
);
// 500ms 节流:与 location/车辆实时 发射链路一致

View File

@@ -426,25 +426,33 @@ class _RobotListViewState extends State<RobotListView> {
children: [
_buildStatusItem(
value: '$onlineCount',
label: '在线设备',
label: AppLocalizations.of(
context,
).translate('device_list_v2.stat_online_devices'),
valueColor: context.appColors.primary,
),
Container(width: 1, height: 40, color: context.appColors.divider),
_buildStatusItem(
value: '$workingCount',
label: '作业中',
label: AppLocalizations.of(
context,
).translate('device_list_v2.stat_working'),
valueColor: context.appColors.success,
),
Container(width: 1, height: 40, color: context.appColors.divider),
_buildStatusItem(
value: '$standbyCount',
label: '待命',
label: AppLocalizations.of(
context,
).translate('device_list_v2.stat_standby'),
valueColor: context.appColors.warning,
),
Container(width: 1, height: 40, color: context.appColors.divider),
_buildStatusItem(
value: '$faultCount',
label: '离线数',
label: AppLocalizations.of(
context,
).translate('device_list_v2.stat_offline'),
valueColor: context.appColors.danger,
),
],

View File

@@ -157,10 +157,27 @@ class DeviceItemWidget extends StatelessWidget {
color: bgColor,
borderRadius: BorderRadius.circular(12.0),
),
child: Text(status, style: TextStyle(fontSize: 12, color: textColor)),
child: Text(
_displayStatus(context, status),
style: TextStyle(fontSize: 12, color: textColor),
),
);
}
/// 🔥 仅做显示层映射:把内部语义状态值转成当前语言文案,未命中则原样显示
/// 数据层与颜色 switch 仍以 '在线'/'离线'/'异常' 为比较键,此处不改数据
String _displayStatus(BuildContext context, String status) {
final loc = AppLocalizations.of(context);
switch (status) {
case '在线':
return loc.translate('device_list_v2.online');
case '离线':
return loc.translate('device_list_v2.offline');
default:
return status;
}
}
Widget _buildDeviceData(BuildContext context, DeviceEntity device) {
final dataItems = <Widget>[];

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../domain/entities/drone_station_entity.dart';
@@ -99,7 +100,7 @@ class DroneStationItemCard extends StatelessWidget {
),
),
// 机场状态徽章
_buildStatusBadge(station.isOnline),
_buildStatusBadge(context, station.isOnline),
],
),
),
@@ -230,7 +231,7 @@ class DroneStationItemCard extends StatelessWidget {
}
/// 状态徽章
Widget _buildStatusBadge( bool isOnline) {
Widget _buildStatusBadge(BuildContext context, bool isOnline) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
@@ -263,7 +264,9 @@ class DroneStationItemCard extends StatelessWidget {
),
const SizedBox(width: 5),
Text(
'${isOnline ? '在线' : '离线'}',
AppLocalizations.of(context).translate(
isOnline ? 'device_list_v2.online' : 'device_list_v2.offline',
),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 10,

View File

@@ -208,10 +208,26 @@ class RobotItemCard extends StatelessWidget {
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
child: Text(status, style: TextStyle(fontSize: 12, color: textColor)),
child: Text(
_displayStatus(context, status),
style: TextStyle(fontSize: 12, color: textColor),
),
);
}
/// 🔥 仅做显示层映射:把内部语义状态值转成当前语言文案,未命中则原样显示
String _displayStatus(BuildContext context, String status) {
final loc = AppLocalizations.of(context);
switch (status) {
case '在线':
return loc.translate('device_list_v2.online');
case '离线':
return loc.translate('device_list_v2.offline');
default:
return status;
}
}
/// 🔥 仅做显示层映射:把内部语义状态值转成当前语言文案,未命中则原样显示
/// 注意:model 默认值与列表过滤逻辑仍使用 '待机中'/'充电中',此处不改数据
String _displayTask(BuildContext context, String task) {

View File

@@ -25,23 +25,12 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
String? _errorMessage;
DeviceRunParamEntity? _param;
// 表单控制器(仅可编辑的5个字段)
late TextEditingController _runSpeedController;
late TextEditingController _leftForwardGainController;
late TextEditingController _leftBackwardGainController;
late TextEditingController _rightForwardGainController;
late TextEditingController _rightBackwardGainController;
// 表单控制器:字段名 -> 输入框控制器(所有展示字段均可编辑,数据驱动)
final Map<String, TextEditingController> _controllers = {};
String get _deviceId => widget.robot['name'] as String? ?? '';
// ============ 字段定义 ============
static const _editableKeys = {
'runSpeed',
'leftForwardGain',
'leftBackwardGain',
'rightForwardGain',
'rightBackwardGain',
};
static const _skipKeys = {
'createBy', 'createTime', 'updateBy', 'updateTime', 'delFlag', 'remark',
@@ -49,10 +38,38 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
static const _fieldLabels = {
'id': 'ID',
'deviceId': '设备ID',
'deviceId': '设备序列号',
'siteId': '场站ID',
'orgId': '组织ID',
'runSpeed': '运行速度',
// ===== 设备参数实体字段(取自后端 @Excel 中文名)=====
'productModel': '产品型号',
'deviceType': '设备类型',
'rtkMode': 'RTK模式',
'cameraNum': '摄像头数量',
'powerType': '动力类型',
'wheelType': '车轮类型',
'engineType': '发动机类型',
'hasHost': '是否搭载上位机',
'walkingDriveType': '行走驱动类型',
'cutterDriveType': '割刀驱动类型',
'rtkAntennasNum': 'RTK天线数',
'handleType': '手柄类型',
'pcbVersion': 'PCB硬件版本',
'dimensions': '长宽高',
'bladeWidth': '割刀宽度',
'speedRatio': '转速比',
'cellularInfo': '蜂窝网络通信信息',
'bluetoothName': '蓝牙广播名',
'hardwareIdentifiers': '硬件标识信息',
'imuModel': 'IMU型号',
'tempHumiditySensor': '温湿度传感器',
'barometerPresent': '气压计在位',
'ultrasonicPresent': '超声波在位',
'dacPresent': 'DAC在位',
'operator': '质检员',
'inspectionNo': '检测报告号',
'dealer': '经销商',
'header1': 'Header1',
'header2': 'Header2',
'cmd': 'CMD',
@@ -106,56 +123,33 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_initControllers();
_loadData();
}
void _initControllers() {
_runSpeedController = TextEditingController();
_leftForwardGainController = TextEditingController();
_leftBackwardGainController = TextEditingController();
_rightForwardGainController = TextEditingController();
_rightBackwardGainController = TextEditingController();
}
/// 根据接口返回的全部字段动态创建输入控制器(跳过 _skipKeys),全部可编辑
void _fillControllers(DeviceRunParamEntity param) {
_runSpeedController.text = param.runSpeed.toStringAsFixed(0);
_leftForwardGainController.text = param.leftForwardGain.toString();
_leftBackwardGainController.text = param.leftBackwardGain.toString();
_rightForwardGainController.text = param.rightForwardGain.toString();
_rightBackwardGainController.text = param.rightBackwardGain.toString();
}
TextEditingController? _controllerForKey(String key) {
switch (key) {
case 'runSpeed': return _runSpeedController;
case 'leftForwardGain': return _leftForwardGainController;
case 'leftBackwardGain': return _leftBackwardGainController;
case 'rightForwardGain': return _rightForwardGainController;
case 'rightBackwardGain': return _rightBackwardGainController;
default: return null;
for (final c in _controllers.values) {
c.dispose();
}
_controllers.clear();
for (final entry in param.rawData.entries) {
if (_skipKeys.contains(entry.key)) continue;
_controllers[entry.key] = TextEditingController(
text: entry.value?.toString() ?? '',
);
}
}
void _showReadonlyTip() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('暂不支持修改'),
duration: Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.only(bottom: 80, left: 80, right: 80),
),
);
}
/// 数值字段用数字键盘,其余用文本键盘(依据接口返回值的原始类型判断)
bool _isNumericField(String key) => _param?.rawData[key] is num;
@override
void dispose() {
_tabController.dispose();
_runSpeedController.dispose();
_leftForwardGainController.dispose();
_leftBackwardGainController.dispose();
_rightForwardGainController.dispose();
_rightBackwardGainController.dispose();
for (final c in _controllers.values) {
c.dispose();
}
_controllers.clear();
super.dispose();
}
@@ -193,7 +187,9 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
setState(() => _isSaving = true);
try {
// 🔐 前置权限校验:只有 code=200 && data=true 才允许保存
// ⛔ 业务变更:设备参数保存不再校验权限,保留代码备查
// (原逻辑:先调 GET /iot/device/hasPermission,通过后再弹"权限通过"确认框)
/*
final permissionService = GetIt.I<DevicePermissionService>();
final hasPermission = await permissionService.checkPermission(_deviceId);
@@ -204,7 +200,6 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
return;
}
// 权限通过提示
await showDialog(
context: context,
barrierDismissible: false,
@@ -226,30 +221,32 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
),
);
if (!mounted) return;
*/
final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id ?? 0;
final orgId = GetIt.I<AppUserCubit>().state.user?.orgId ?? 0;
final useCase = GetIt.I<SaveDeviceRunParamUseCase>();
final params = <String, dynamic>{
'id': _param!.id,
'deviceId': _deviceId,
'siteId': siteId,
'orgId': orgId,
'runSpeed': int.tryParse(_runSpeedController.text) ?? _param!.runSpeed,
'leftForwardGain':
double.tryParse(_leftForwardGainController.text) ??
_param!.leftForwardGain,
'leftBackwardGain':
double.tryParse(_leftBackwardGainController.text) ??
_param!.leftBackwardGain,
'rightForwardGain':
double.tryParse(_rightForwardGainController.text) ??
_param!.rightForwardGain,
'rightBackwardGain':
double.tryParse(_rightBackwardGainController.text) ??
_param!.rightBackwardGain,
};
// 以接口原始数据为底,叠加各输入框编辑后的值(数值字段按原类型回转)
final params = <String, dynamic>{..._param!.rawData};
for (final entry in _controllers.entries) {
final original = _param!.rawData[entry.key];
final text = entry.value.text.trim();
if (original is int) {
params[entry.key] = int.tryParse(text) ?? original;
} else if (original is double) {
params[entry.key] = double.tryParse(text) ?? original;
} else if (original is num) {
params[entry.key] = num.tryParse(text) ?? original;
} else {
params[entry.key] = text;
}
}
// 关键字段兜底:接口未返回时用当前上下文补齐
params['id'] ??= _param!.id;
params['deviceId'] ??= _deviceId;
params['siteId'] ??= siteId;
params['orgId'] ??= orgId;
final result = await useCase.execute(params);
@@ -411,14 +408,11 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
child: Column(
children: List.generate(keys.length, (i) {
final key = keys[i];
final label = _fieldLabels[key] ?? key;
final isEditable = _editableKeys.contains(key);
final isLast = i == keys.length - 1;
final value = raw[key];
return Column(
children: [
_buildFieldRow(key, label, value, isEditable),
_buildFieldRow(key),
if (!isLast) _buildDivider(),
],
);
@@ -446,65 +440,54 @@ class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
}
// ============ 单行字段 ============
Widget _buildFieldRow(
String key,
String label,
dynamic value,
bool editable,
) {
final controller = editable ? _controllerForKey(key) : null;
final displayValue = value?.toString() ?? '-';
Widget _buildFieldRow(String key) {
final controller = _controllers[key];
final isNumeric = _isNumericField(key);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 90,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF4E5969),
fontWeight: FontWeight.w500,
),
width: 130,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 主行:始终显示字段名(英文 key),避免中英混排
Text(
key,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF4E5969),
fontWeight: FontWeight.w500,
),
),
// 副行:有中文映射才显示(小字灰色),没有则不显示
if (_fieldLabels[key] != null)
Text(
_fieldLabels[key]!,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF86909C),
),
),
],
),
),
const SizedBox(width: 8),
Expanded(
child: editable
? TextField(
controller: controller,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[\d.]')),
],
style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129)),
decoration: _inputDecoration(),
)
: GestureDetector(
onTap: _showReadonlyTip,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE5E6EB)),
),
child: Text(
displayValue,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF86909C),
),
),
),
),
child: TextField(
controller: controller,
keyboardType: isNumeric
? const TextInputType.numberWithOptions(decimal: true)
: TextInputType.text,
inputFormatters: isNumeric
? [FilteringTextInputFormatter.allow(RegExp(r'[\d.\-]'))]
: null,
style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129)),
decoration: _inputDecoration(),
),
),
],
),

View File

@@ -39,6 +39,17 @@ class UsbCameraCubit extends Cubit<UsbCameraState> {
Future<void> start() async {
_safeEmit(state.copyWith(phase: UsbCameraPhase.scanning));
try {
// 平台/机型能力探测:iPhone 系统层面不开放外接摄像头输入,直接给专属提示,
// 避免枚举到空列表后显示含糊的「未检测到 USB 摄像头」。
final supported = await plugin.UsbCameraAndroid.isSupported();
if (!supported) {
_safeEmit(state.copyWith(
phase: UsbCameraPhase.openFailed,
message: 'iPhone 不支持 USB 摄像头输入\n请改用 iPad(USB-C 接口,iPadOS 17 及以上)连接采集卡',
));
return;
}
final devices = await _service.enumerateDevices();
if (devices.isEmpty) {

View File

@@ -39,6 +39,9 @@ public class UsbCameraAndroidPlugin implements FlutterPlugin, MethodChannel.Meth
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "isSupported":
result.success(true);
break;
case "getDeviceList":
result.success(engine.getDeviceList());
break;

View File

@@ -46,6 +46,19 @@ class UsbCameraAndroid {
return _eventStream!;
}
/// 当前平台/设备是否支持 USB(UVC)摄像头。
/// - Android:始终 true。
/// - iOS:仅 iPadOS 17+ 的 iPad 为 true;iPhone 或 iOS<17 为 false。
/// 通道未注册(异常)时保守返回 true,交由上层继续走原有枚举/错误流程。
static Future<bool> isSupported() async {
try {
final result = await _methodChannel.invokeMethod('isSupported');
return result as bool? ?? true;
} catch (_) {
return true;
}
}
/// 枚举当前连接的 UVC 设备。
static Future<List<UsbCameraDeviceInfo>> getDeviceList() async {
final List<dynamic> result =

View File

@@ -1,7 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// Android 原生 UVC 预览画面。
/// 原生 UVC 预览画面(Android: AndroidView / iOS: UiKitView)。
class UsbCameraPreviewView extends StatelessWidget {
const UsbCameraPreviewView({super.key});
@@ -9,6 +10,13 @@ class UsbCameraPreviewView extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (defaultTargetPlatform == TargetPlatform.iOS) {
return const UiKitView(
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParamsCodec: StandardMessageCodec(),
);
}
return const AndroidView(
viewType: _viewType,
layoutDirection: TextDirection.ltr,

View File

@@ -13,6 +13,8 @@ flutter:
android:
package: com.maibu.usb_camera_android
pluginClass: UsbCameraAndroidPlugin
ios:
pluginClass: UsbCameraAndroidPlugin
dependencies:
flutter:

View File

@@ -0,0 +1,20 @@
# video\_player\_android
The Android implementation of [`video_player`][1].
## Usage
This package is [endorsed][2], which means you can simply use `video_player`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.
However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.
## Known issues
Using `VideoViewType.platformView` is not currently recommended on Android due to a known [issue][3] affecting platform views on Android.
[1]: https://pub.dev/packages/video_player
[2]: https://flutter.dev/to/endorsed-federated-plugin
[3]: https://github.com/flutter/flutter/issues/164899

View File

@@ -0,0 +1,9 @@
# Platform Implementation Test App
This is a test app for manual testing and automated integration testing
of this platform implementation. It is not intended to demonstrate actual use of
this package, since the intent is that plugin clients use the app-facing
package.
Unless you are making changes to this implementation package, this example is
very unlikely to be relevant.