Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
laki_icu_app
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张宏
laki_icu_app
Commits
ddc45594
Commit
ddc45594
authored
Jun 23, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
cc 合并到 master
parent
f0145cb5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
2 additions
and
510 deletions
+2
-510
usb_camera_service.dart
lib/services/usb_camera_service.dart
+0
-337
home_index_cubit.dart
lib/views/home/index/cubit/home_index_cubit.dart
+0
-0
home_index_state.dart
lib/views/home/index/cubit/home_index_state.dart
+0
-171
home_index_view.dart
lib/views/home/index/home_index_view.dart
+0
-0
bluetooth_bind_dialog.dart
lib/views/home/index/widgets/bluetooth_bind_dialog.dart
+0
-0
pubspec.yaml
pubspec.yaml
+2
-2
No files found.
lib/services/usb_camera_service.dart
deleted
100644 → 0
View file @
f0145cb5
import
'dart:async'
;
import
'dart:io'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_usbcamera/flutter_usbcamera.dart'
;
import
'package:path_provider/path_provider.dart'
;
/// USB 摄像头服务层
///
/// 职责:
/// - 封装 [UsbCameraController],对上层提供统一的业务接口
/// - 监听设备热插拔事件,自动管理摄像头生命周期
/// - 将事件流中的拍摄完成回调转换为 Future 返回值
///
/// 涉及页面:首页监护舱 - 监控画面
class
UsbCameraService
{
static
UsbCameraService
?
_instance
;
final
UsbCameraController
_controller
;
StreamSubscription
<
CameraEvent
>?
_eventSub
;
// 事件广播流(供 UI 层订阅设备状态变化)
final
StreamController
<
CameraEvent
>
_eventController
=
StreamController
<
CameraEvent
>.
broadcast
();
// 拍照/录像完成时的 Completer
Completer
<
String
?>?
_captureCompleter
;
UsbCameraService
.
_
()
:
_controller
=
UsbCameraController
();
/// 单例实例
static
UsbCameraService
get
instance
{
_instance
??=
UsbCameraService
.
_
();
return
_instance
!;
}
/// USB 设备事件流(设备插拔、摄像头状态、拍摄回调等)
Stream
<
CameraEvent
>
get
eventStream
=>
_eventController
.
stream
;
/// 当前控制器引用(供需要直接调用的场景)
UsbCameraController
get
controller
=>
_controller
;
/// 摄像头是否已打开
bool
_isOpened
=
false
;
bool
get
isOpened
=>
_isOpened
;
/// 是否正在录像
bool
_isRecording
=
false
;
bool
get
isRecording
=>
_isRecording
;
// ==================== 初始化与销毁 ====================
/// 初始化:注册 USB 监听,开始检测设备插拔
Future
<
void
>
initialize
()
async
{
if
(
_eventSub
!=
null
)
return
;
// 已初始化
// 监听插件事件流
_eventSub
=
UsbCameraController
.
events
.
listen
(
_handleEvent
);
// 注册 USB 监听
await
_controller
.
registerUsb
();
debugPrint
(
'[UsbCameraService] 初始化完成,USB 监听已注册'
);
}
/// 处理来自插件的所有事件
void
_handleEvent
(
CameraEvent
event
)
{
debugPrint
(
'[UsbCameraService] 收到事件:
${event.type.name}
, '
'deviceId=
${event.deviceId}
, state=
${event.state}
, path=
${event.path}
'
);
switch
(
event
.
type
)
{
case
CameraEventType
.
onAttachDev
:
// USB 摄像头插入 → 自动请求权限
if
(
event
.
deviceId
!=
null
)
{
_controller
.
requestPermission
(
event
.
deviceId
!);
}
break
;
case
CameraEventType
.
onDetachDev
:
case
CameraEventType
.
onDisConnectDev
:
// 设备拔出或断开
_isOpened
=
false
;
_isRecording
=
false
;
break
;
case
CameraEventType
.
onConnectDev
:
// 权限通过,自动打开摄像头
if
(
event
.
deviceId
!=
null
)
{
_openCameraInternal
(
event
.
deviceId
!);
}
break
;
case
CameraEventType
.
onCancelDev
:
// 权限被拒绝
_isOpened
=
false
;
break
;
case
CameraEventType
.
onCameraState
:
if
(
event
.
state
==
'opened'
)
{
_isOpened
=
true
;
}
else
if
(
event
.
state
==
'closed'
||
event
.
state
==
'error'
)
{
_isOpened
=
false
;
_isRecording
=
false
;
}
break
;
case
CameraEventType
.
onCaptureBegin
:
// 拍摄开始
break
;
case
CameraEventType
.
onCaptureComplete
:
// 拍摄完成 → 解析 Completer
_isRecording
=
false
;
if
(
_captureCompleter
!=
null
&&
!
_captureCompleter
!.
isCompleted
)
{
_captureCompleter
!.
complete
(
event
.
path
);
_captureCompleter
=
null
;
}
break
;
case
CameraEventType
.
onCaptureError
:
// 拍摄出错
_isRecording
=
false
;
if
(
_captureCompleter
!=
null
&&
!
_captureCompleter
!.
isCompleted
)
{
_captureCompleter
!.
completeError
(
Exception
(
event
.
error
??
'拍摄失败'
),
);
_captureCompleter
=
null
;
}
break
;
default
:
break
;
}
// 转发事件给所有订阅者
_eventController
.
add
(
event
);
}
/// 内部打开摄像头方法
Future
<
void
>
_openCameraInternal
(
int
deviceId
)
async
{
try
{
await
_controller
.
openCamera
(
deviceId
,
request:
const
CameraRequest
(
previewWidth:
1280
,
previewHeight:
720
,
renderMode:
RenderMode
.
opengl
,
previewFormat:
PreviewFormat
.
mjpeg
,
),
);
debugPrint
(
'[UsbCameraService] 摄像头已打开: deviceId=
$deviceId
'
);
}
catch
(
e
)
{
debugPrint
(
'[UsbCameraService] 打开摄像头失败:
$e
'
);
}
}
// ==================== 设备管理 ====================
/// 获取已连接的 USB 设备列表
Future
<
List
<
UsbDevice
>>
getDeviceList
()
async
{
try
{
return
await
_controller
.
getDeviceList
();
}
catch
(
e
)
{
debugPrint
(
'[UsbCameraService] getDeviceList error:
$e
'
);
return
[];
}
}
/// 手动打开指定摄像头
Future
<
bool
>
openCamera
(
int
deviceId
,
{
CameraRequest
?
request
})
async
{
try
{
await
_controller
.
openCamera
(
deviceId
,
request:
request
??
const
CameraRequest
(
previewWidth:
1280
,
previewHeight:
720
,
renderMode:
RenderMode
.
opengl
,
previewFormat:
PreviewFormat
.
mjpeg
,
),
);
return
true
;
}
catch
(
e
)
{
debugPrint
(
'[UsbCameraService] openCamera error:
$e
'
);
return
false
;
}
}
// ==================== 摄像头控制 ====================
/// 关闭摄像头
Future
<
void
>
closeCamera
()
async
{
try
{
await
_controller
.
closeCamera
();
_isOpened
=
false
;
_isRecording
=
false
;
}
catch
(
e
)
{
debugPrint
(
'[UsbCameraService] closeCamera error:
$e
'
);
}
}
/// 拍照
/// [customPath] 可选,指定照片保存路径;不传则自动生成
/// 返回照片文件路径
Future
<
String
?>
takePhoto
({
String
?
customPath
})
async
{
if
(!
_isOpened
)
{
debugPrint
(
'[UsbCameraService] 摄像头未打开,无法拍照'
);
return
null
;
}
try
{
_captureCompleter
=
Completer
<
String
?>();
final
path
=
customPath
??
await
_generatePhotoPath
();
final
success
=
await
_controller
.
captureImage
(
path:
path
);
if
(!
success
)
{
_captureCompleter
=
null
;
return
null
;
}
// 等待 onCaptureComplete 事件返回路径
final
result
=
await
_captureCompleter
!.
future
;
return
result
;
}
catch
(
e
)
{
_captureCompleter
=
null
;
debugPrint
(
'[UsbCameraService] takePhoto error:
$e
'
);
return
null
;
}
}
/// 开始录像
/// [customPath] 可选,指定视频保存路径
Future
<
bool
>
startRecord
({
String
?
customPath
})
async
{
if
(!
_isOpened
)
{
debugPrint
(
'[UsbCameraService] 摄像头未打开,无法录像'
);
return
false
;
}
try
{
final
path
=
customPath
??
await
_generateVideoPath
();
final
success
=
await
_controller
.
captureVideoStart
(
path:
path
);
if
(
success
)
{
_isRecording
=
true
;
}
return
success
;
}
catch
(
e
)
{
debugPrint
(
'[UsbCameraService] startRecord error:
$e
'
);
return
false
;
}
}
/// 停止录像
/// 返回视频文件路径
Future
<
String
?>
stopRecord
()
async
{
if
(!
_isRecording
)
{
debugPrint
(
'[UsbCameraService] 当前未在录像'
);
return
null
;
}
try
{
_captureCompleter
=
Completer
<
String
?>();
final
success
=
await
_controller
.
captureVideoStop
();
if
(!
success
)
{
_captureCompleter
=
null
;
_isRecording
=
false
;
return
null
;
}
// 等待 onCaptureComplete 事件返回路径
final
result
=
await
_captureCompleter
!.
future
;
_isRecording
=
false
;
return
result
;
}
catch
(
e
)
{
_captureCompleter
=
null
;
_isRecording
=
false
;
debugPrint
(
'[UsbCameraService] stopRecord error:
$e
'
);
return
null
;
}
}
// ==================== 路径生成 ====================
/// 生成照片保存路径
Future
<
String
>
_generatePhotoPath
()
async
{
final
dir
=
await
_getCameraDirectory
();
final
timestamp
=
_timestamp
();
return
'
${dir.path}
/IMG_
$timestamp
.jpg'
;
}
/// 生成视频保存路径
Future
<
String
>
_generateVideoPath
()
async
{
final
dir
=
await
_getCameraDirectory
();
final
timestamp
=
_timestamp
();
return
'
${dir.path}
/VID_
$timestamp
.mp4'
;
}
/// 获取 USB 摄像头存储目录
Future
<
Directory
>
_getCameraDirectory
()
async
{
final
baseDir
=
Platform
.
isAndroid
?
await
getExternalStorageDirectory
()
:
await
getApplicationDocumentsDirectory
();
final
cameraDir
=
Directory
(
'
${baseDir!.path}
/usb_camera'
);
if
(!
await
cameraDir
.
exists
())
{
await
cameraDir
.
create
(
recursive:
true
);
}
return
cameraDir
;
}
/// 生成时间戳字符串
String
_timestamp
()
{
final
now
=
DateTime
.
now
();
return
'
${now.year}${_pad(now.month)}${_pad(now.day)}
_'
'
${_pad(now.hour)}${_pad(now.minute)}${_pad(now.second)}
'
;
}
String
_pad
(
int
value
)
=>
value
.
toString
().
padLeft
(
2
,
'0'
);
// ==================== 销毁 ====================
/// 释放所有资源
Future
<
void
>
dispose
()
async
{
_captureCompleter
=
null
;
_isOpened
=
false
;
_isRecording
=
false
;
await
_eventSub
?.
cancel
();
_eventSub
=
null
;
await
_eventController
.
close
();
await
_controller
.
dispose
();
debugPrint
(
'[UsbCameraService] 资源已释放'
);
}
}
lib/views/home/index/cubit/home_index_cubit.dart
deleted
100644 → 0
View file @
f0145cb5
This diff is collapsed.
Click to expand it.
lib/views/home/index/cubit/home_index_state.dart
deleted
100644 → 0
View file @
f0145cb5
import
'package:equatable/equatable.dart'
;
import
'package:laki_icu_app/enums/video_stream_mode_enum.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
import
'package:laki_icu_app/services/webrtc_service.dart'
;
import
'package:laki_icu_app/utils/bluetooth/index.dart'
;
enum
HomeIndexStatus
{
initial
,
loading
,
success
,
failure
,
}
class
HomeIndexState
extends
Equatable
{
final
HomeIndexStatus
status
;
final
bool
isLoading
;
final
String
?
error
;
final
List
<
MonitoringMetricBO
>
metrics
;
final
PatientInfoBO
?
patientInfo
;
final
List
<
AlertInfoBO
>
alerts
;
final
List
<
MonitoringMenuItemBO
>
menuItems
;
/// WebRTC 视频连接状态
final
WebrtcConnectionState
videoConnectionState
;
/// 当前视频传输模式
final
VideoStreamMode
videoStreamMode
;
/// 是否正在切换传输模式(用于 UI loading 状态)
final
bool
isSwitchingMode
;
/// P2P 模式是否已连接
final
bool
isP2pConnected
;
/// 蓝牙扫描中
final
bool
isBluetoothScanning
;
/// 蓝牙绑定/连接处理中
final
bool
isBluetoothBinding
;
/// 正在绑定/连接的蓝牙设备 ID
final
String
bindingBluetoothDeviceId
;
/// 扫描到的蓝牙设备
final
List
<
BluetoothScanDevice
>
bluetoothDevices
;
/// 已绑定蓝牙设备 ID
final
String
?
boundBluetoothDeviceId
;
/// 已绑定蓝牙设备名称
final
String
?
boundBluetoothDeviceName
;
/// 蓝牙连接状态
final
BluetoothConnectionStatus
bluetoothConnectionStatus
;
/// 蓝牙状态提示
final
String
?
bluetoothMessage
;
/// 最近收到的蓝牙原始 Hex 数据
final
String
?
latestBluetoothRawHex
;
/// 最近解析出的 MCU 上报数据
final
McuReport
?
latestMcuReport
;
const
HomeIndexState
({
this
.
status
=
HomeIndexStatus
.
initial
,
this
.
isLoading
=
false
,
this
.
error
,
this
.
metrics
=
const
[],
this
.
patientInfo
,
this
.
alerts
=
const
[],
this
.
menuItems
=
const
[],
this
.
videoConnectionState
=
WebrtcConnectionState
.
disconnected
,
this
.
videoStreamMode
=
VideoStreamMode
.
webrtc
,
this
.
isSwitchingMode
=
false
,
this
.
isP2pConnected
=
false
,
this
.
isBluetoothScanning
=
false
,
this
.
isBluetoothBinding
=
false
,
this
.
bindingBluetoothDeviceId
=
''
,
this
.
bluetoothDevices
=
const
[],
this
.
boundBluetoothDeviceId
,
this
.
boundBluetoothDeviceName
,
this
.
bluetoothConnectionStatus
=
BluetoothConnectionStatus
.
disconnected
,
this
.
bluetoothMessage
,
this
.
latestBluetoothRawHex
,
this
.
latestMcuReport
,
});
HomeIndexState
copyWith
({
HomeIndexStatus
?
status
,
bool
?
isLoading
,
String
?
error
,
List
<
MonitoringMetricBO
>?
metrics
,
PatientInfoBO
?
patientInfo
,
List
<
AlertInfoBO
>?
alerts
,
List
<
MonitoringMenuItemBO
>?
menuItems
,
WebrtcConnectionState
?
videoConnectionState
,
VideoStreamMode
?
videoStreamMode
,
bool
?
isSwitchingMode
,
bool
?
isP2pConnected
,
bool
?
isBluetoothScanning
,
bool
?
isBluetoothBinding
,
String
?
bindingBluetoothDeviceId
,
List
<
BluetoothScanDevice
>?
bluetoothDevices
,
String
?
boundBluetoothDeviceId
,
String
?
boundBluetoothDeviceName
,
BluetoothConnectionStatus
?
bluetoothConnectionStatus
,
String
?
bluetoothMessage
,
String
?
latestBluetoothRawHex
,
McuReport
?
latestMcuReport
,
bool
clearBoundBluetoothDevice
=
false
,
bool
clearLatestMcuReport
=
false
,
})
{
return
HomeIndexState
(
status:
status
??
this
.
status
,
isLoading:
isLoading
??
this
.
isLoading
,
error:
error
??
this
.
error
,
metrics:
metrics
??
this
.
metrics
,
patientInfo:
patientInfo
??
this
.
patientInfo
,
alerts:
alerts
??
this
.
alerts
,
menuItems:
menuItems
??
this
.
menuItems
,
videoConnectionState:
videoConnectionState
??
this
.
videoConnectionState
,
videoStreamMode:
videoStreamMode
??
this
.
videoStreamMode
,
isSwitchingMode:
isSwitchingMode
??
this
.
isSwitchingMode
,
isP2pConnected:
isP2pConnected
??
this
.
isP2pConnected
,
isBluetoothScanning:
isBluetoothScanning
??
this
.
isBluetoothScanning
,
isBluetoothBinding:
isBluetoothBinding
??
this
.
isBluetoothBinding
,
bindingBluetoothDeviceId:
bindingBluetoothDeviceId
??
this
.
bindingBluetoothDeviceId
,
bluetoothDevices:
bluetoothDevices
??
this
.
bluetoothDevices
,
boundBluetoothDeviceId:
clearBoundBluetoothDevice
?
null
:
boundBluetoothDeviceId
??
this
.
boundBluetoothDeviceId
,
boundBluetoothDeviceName:
clearBoundBluetoothDevice
?
null
:
boundBluetoothDeviceName
??
this
.
boundBluetoothDeviceName
,
bluetoothConnectionStatus:
bluetoothConnectionStatus
??
this
.
bluetoothConnectionStatus
,
bluetoothMessage:
bluetoothMessage
??
this
.
bluetoothMessage
,
latestBluetoothRawHex:
latestBluetoothRawHex
??
this
.
latestBluetoothRawHex
,
latestMcuReport:
clearLatestMcuReport
?
null
:
latestMcuReport
??
this
.
latestMcuReport
,
);
}
@override
List
<
Object
?>
get
props
=>
[
status
,
isLoading
,
error
,
metrics
,
patientInfo
,
alerts
,
menuItems
,
videoConnectionState
,
videoStreamMode
,
isSwitchingMode
,
isP2pConnected
,
isBluetoothScanning
,
isBluetoothBinding
,
bindingBluetoothDeviceId
,
bluetoothDevices
,
boundBluetoothDeviceId
,
boundBluetoothDeviceName
,
bluetoothConnectionStatus
,
bluetoothMessage
,
latestBluetoothRawHex
,
latestMcuReport
,
];
}
lib/views/home/index/home_index_view.dart
deleted
100644 → 0
View file @
f0145cb5
This diff is collapsed.
Click to expand it.
lib/views/home/index/widgets/bluetooth_bind_dialog.dart
deleted
100644 → 0
View file @
f0145cb5
This diff is collapsed.
Click to expand it.
pubspec.yaml
View file @
ddc45594
...
...
@@ -50,8 +50,8 @@ dependencies:
logger
:
^2.7.0
fluttertoast
:
^9.0.0
permission_handler
:
^11.3.1
flutter_usbcamera
:
path
:
./third_party/flutter_usbcamera
#
flutter_usbcamera:
#
path: ./third_party/flutter_usbcamera
path_provider
:
^2.1.2
flutter_webrtc
:
path
:
./plugins/flutter_webrtc
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment