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
d697b832
Commit
d697b832
authored
Jun 22, 2026
by
akari
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
http://git.ruanyiit.com/zhanghong/laki_icu_app
parents
e21c3fc0
29eea2f6
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
664 additions
and
3 deletions
+664
-3
monitoring_index_cubit.dart
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
+171
-0
monitoring_index_state.dart
lib/views/monitoring/index/cubit/monitoring_index_state.dart
+31
-0
monitoring_index_view.dart
lib/views/monitoring/index/monitoring_index_view.dart
+24
-2
monitoring_metric_card.dart
...iews/monitoring/index/widgets/monitoring_metric_card.dart
+1
-1
video_fullscreen_page.dart
...views/monitoring/index/widgets/video_fullscreen_page.dart
+90
-0
video_player_widget.dart
lib/views/monitoring/index/widgets/video_player_widget.dart
+290
-0
video_stream_switch.dart
lib/views/monitoring/index/widgets/video_stream_switch.dart
+57
-0
No files found.
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
View file @
d697b832
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:laki_icu_app/enums/video_stream_mode_enum.dart'
;
import
'package:laki_icu_app/services/monitoring_service.dart'
;
import
'package:laki_icu_app/services/p2p_video_service.dart'
;
import
'package:laki_icu_app/services/webrtc_service.dart'
;
import
'monitoring_index_state.dart'
;
/// 设备凭据 —— Mock 阶段使用占位值,后续接口接入后替换
/// TODO: 从舱详情接口获取真实的 cameraSn 和 wifiPwd
class
MonitoringIndexCubit
extends
Cubit
<
MonitoringIndexState
>
{
final
MonitoringService
_monitoringService
;
final
WebrtcService
_webrtcService
;
final
P2pVideoService
_p2pVideoService
;
StreamSubscription
<
WebrtcConnectionState
>?
_webrtcStateSub
;
StreamSubscription
<
P2pServiceState
>?
_p2pStateSub
;
/// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。
int
_switchGen
=
0
;
MonitoringIndexCubit
()
:
_monitoringService
=
MonitoringService
(),
_webrtcService
=
WebrtcService
(),
_p2pVideoService
=
P2pVideoService
(),
super
(
const
MonitoringIndexState
())
{
_listenWebrtcState
();
_listenP2pState
();
loadData
();
}
/// 监听 WebRTC 连接状态变化并同步到 State
void
_listenWebrtcState
()
{
_webrtcStateSub
=
_webrtcService
.
connectionStateStream
.
listen
((
state
)
{
if
(
isClosed
)
return
;
emit
(
this
.
state
.
copyWith
(
videoConnectionState:
state
));
});
}
/// 监听 P2P 视频服务状态变化并同步到 State
void
_listenP2pState
()
{
_p2pStateSub
=
_p2pVideoService
.
stateStream
.
listen
((
state
)
{
if
(
isClosed
)
return
;
emit
(
this
.
state
.
copyWith
(
isP2pConnected:
state
==
P2pServiceState
.
connected
,
));
});
}
Future
<
void
>
loadData
()
async
{
emit
(
state
.
copyWith
(
status:
MonitoringIndexStatus
.
loading
,
...
...
@@ -38,6 +78,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
menuItems:
menuItems
,
error:
null
,
));
// 数据加载完成后,按当前模式初始化视频连接
_initVideoByMode
();
}
catch
(
e
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
...
...
@@ -47,4 +90,132 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
));
}
}
/// 依据当前 [VideoStreamMode] 启动对应的视频连接
void
_initVideoByMode
()
{
if
(
state
.
videoStreamMode
==
VideoStreamMode
.
p2p
)
{
_initP2p
();
}
else
{
_initWebrtc
();
}
}
/// 初始化 WebRTC 视频连接
Future
<
void
>
_initWebrtc
()
async
{
await
_webrtcService
.
connect
(
deviceNo:
'VE10085871QOXG'
,
password:
'143548'
,
);
}
/// 手动重试 WebRTC
Future
<
void
>
retryWebrtc
()
async
{
await
_initWebrtc
();
}
/// 初始化 P2P 视频连接
Future
<
void
>
_initP2p
()
async
{
await
_p2pVideoService
.
connect
(
deviceId:
'VE10085871QOXG'
,
username:
'admin'
,
password:
'143548'
,
);
}
// ==================== 模式切换 ====================
/// 切换视频传输模式
///
/// 先停止当前模式 → 更新状态 → 启动新模式。
/// 使用 [_switchGen] 代际计数器防止旧连接的异步结果污染当前状态。
Future
<
void
>
switchStreamMode
(
VideoStreamMode
mode
)
async
{
if
(
state
.
isSwitchingMode
)
return
;
if
(
state
.
videoStreamMode
==
mode
)
return
;
final
currentGen
=
++
_switchGen
;
debugPrint
(
'[MonitoringIndexCubit] 切换模式:
${state.videoStreamMode}
→
$mode
(gen=
$currentGen
)'
);
// 1. 标记切换中
emit
(
state
.
copyWith
(
isSwitchingMode:
true
));
// 2. 停止当前模式(用 stop 停连接,保留 StreamController 可复用)
if
(
state
.
videoStreamMode
==
VideoStreamMode
.
p2p
)
{
await
_p2pVideoService
.
stop
();
}
else
{
await
_webrtcService
.
stop
();
}
// 3. 竞态检查:如果在清理期间又触发了一次切换则放弃本次
if
(
currentGen
!=
_switchGen
||
isClosed
)
return
;
// 4. 切换到新模式
emit
(
state
.
copyWith
(
videoStreamMode:
mode
,
isSwitchingMode:
false
,
isP2pConnected:
false
,
videoConnectionState:
WebrtcConnectionState
.
disconnected
,
));
// 5. 启动新模式视频连接
if
(
mode
==
VideoStreamMode
.
p2p
)
{
await
_initP2p
();
}
else
{
await
_initWebrtc
();
}
}
/// 手动重试当前模式的视频连接
Future
<
void
>
retryCurrentMode
()
async
{
if
(
state
.
videoStreamMode
==
VideoStreamMode
.
p2p
)
{
await
_p2pVideoService
.
stop
();
await
_initP2p
();
}
else
{
await
_webrtcService
.
stop
();
await
_initWebrtc
();
}
}
Future
<
void
>
refreshData
()
async
{
try
{
final
metrics
=
await
_monitoringService
.
getMetrics
();
if
(
isClosed
)
return
;
final
patientInfo
=
await
_monitoringService
.
getPatientInfo
();
if
(
isClosed
)
return
;
final
alerts
=
await
_monitoringService
.
getAlerts
();
if
(
isClosed
)
return
;
final
menuItems
=
await
_monitoringService
.
getMenuItems
();
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
status:
MonitoringIndexStatus
.
success
,
metrics:
metrics
,
patientInfo:
patientInfo
,
alerts:
alerts
,
menuItems:
menuItems
,
error:
null
,
));
}
catch
(
e
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
status:
MonitoringIndexStatus
.
failure
,
error:
e
.
toString
(),
));
}
}
/// 获取 WebRTC 渲染器供 UI 层使用
WebrtcService
get
webrtcService
=>
_webrtcService
;
/// 获取 P2P 视频播放控制器供 UI 层使用
P2pVideoService
get
p2pVideoService
=>
_p2pVideoService
;
@override
Future
<
void
>
close
()
{
_webrtcStateSub
?.
cancel
();
_p2pStateSub
?.
cancel
();
_webrtcService
.
dispose
();
_p2pVideoService
.
dispose
();
return
super
.
close
();
}
}
lib/views/monitoring/index/cubit/monitoring_index_state.dart
View file @
d697b832
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'
;
enum
MonitoringIndexStatus
{
initial
,
...
...
@@ -17,6 +19,18 @@ class MonitoringIndexState extends Equatable {
final
List
<
AlertInfoBO
>
alerts
;
final
List
<
MonitoringMenuItemBO
>
menuItems
;
/// WebRTC 视频连接状态
final
WebrtcConnectionState
videoConnectionState
;
/// 当前视频传输模式
final
VideoStreamMode
videoStreamMode
;
/// 是否正在切换传输模式(用于 UI loading 状态)
final
bool
isSwitchingMode
;
/// P2P 模式是否已连接
final
bool
isP2pConnected
;
const
MonitoringIndexState
({
this
.
status
=
MonitoringIndexStatus
.
initial
,
this
.
isLoading
=
false
,
...
...
@@ -25,6 +39,10 @@ class MonitoringIndexState extends Equatable {
this
.
patientInfo
,
this
.
alerts
=
const
[],
this
.
menuItems
=
const
[],
this
.
videoConnectionState
=
WebrtcConnectionState
.
disconnected
,
this
.
videoStreamMode
=
VideoStreamMode
.
p2p
,
this
.
isSwitchingMode
=
false
,
this
.
isP2pConnected
=
false
,
});
MonitoringIndexState
copyWith
({
...
...
@@ -35,6 +53,10 @@ class MonitoringIndexState extends Equatable {
PatientInfoBO
?
patientInfo
,
List
<
AlertInfoBO
>?
alerts
,
List
<
MonitoringMenuItemBO
>?
menuItems
,
WebrtcConnectionState
?
videoConnectionState
,
VideoStreamMode
?
videoStreamMode
,
bool
?
isSwitchingMode
,
bool
?
isP2pConnected
,
})
{
return
MonitoringIndexState
(
status:
status
??
this
.
status
,
...
...
@@ -44,6 +66,11 @@ class MonitoringIndexState extends Equatable {
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
,
);
}
...
...
@@ -56,5 +83,9 @@ class MonitoringIndexState extends Equatable {
patientInfo
,
alerts
,
menuItems
,
videoConnectionState
,
videoStreamMode
,
isSwitchingMode
,
isP2pConnected
,
];
}
lib/views/monitoring/index/monitoring_index_view.dart
View file @
d697b832
...
...
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:laki_icu_app/enums/video_stream_mode_enum.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
import
'cubit/monitoring_index_cubit.dart'
;
...
...
@@ -11,7 +12,7 @@ import 'widgets/monitoring_bottom_menu.dart';
import
'widgets/monitoring_metric_card.dart'
;
import
'widgets/monitoring_pet_info_card.dart'
;
import
'widgets/monitoring_top_status_bar.dart'
;
import
'widgets/
monitoring_video_card
.dart'
;
import
'widgets/
video_player_widget
.dart'
;
@RoutePage
()
class
MonitoringIndexView
extends
StatelessWidget
{
...
...
@@ -157,7 +158,18 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
SizedBox
(
width:
gap
),
Expanded
(
flex:
10
,
child:
const
MonitoringVideoCard
(),
child:
VideoPlayerWidget
(
streamMode:
state
.
videoStreamMode
,
isSwitchingMode:
state
.
isSwitchingMode
,
webrtcRenderer:
_monitoringIndexCubit
.
webrtcService
.
renderer
,
webrtcConnectionState:
state
.
videoConnectionState
,
p2pController:
_monitoringIndexCubit
.
p2pVideoService
.
playerController
,
isP2pConnected:
state
.
isP2pConnected
,
onRetry:
()
=>
_monitoringIndexCubit
.
retryCurrentMode
(),
onToggleMode:
_onToggleStreamMode
,
),
),
],
),
...
...
@@ -191,6 +203,16 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
/// 模式切换回调
void
_onToggleStreamMode
()
{
final
cubit
=
_monitoringIndexCubit
;
final
currentMode
=
cubit
.
state
.
videoStreamMode
;
final
nextMode
=
currentMode
==
VideoStreamMode
.
p2p
?
VideoStreamMode
.
webrtc
:
VideoStreamMode
.
p2p
;
cubit
.
switchStreamMode
(
nextMode
);
}
List
<
MonitoringMetricBO
>
_environmentMetrics
(
List
<
MonitoringMetricBO
>
metrics
)
{
final
matched
=
metrics
...
...
lib/views/monitoring/index/widgets/monitoring_metric_card.dart
View file @
d697b832
...
...
@@ -107,7 +107,7 @@ class MonitoringMetricCard extends StatelessWidget {
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
"设定24
度"
,
"设定24
"
+
metric
.
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
14
.
sp
,
...
...
lib/views/monitoring/index/widgets/video_fullscreen_page.dart
0 → 100644
View file @
d697b832
import
'package:flutter/material.dart'
;
import
'package:flutter_webrtc/flutter_webrtc.dart'
;
import
'package:vsdk/app_player.dart'
;
import
'../../../../enums/video_stream_mode_enum.dart'
;
/// 通用视频全屏播放页面(兼容 WebRTC + P2P)
///
/// 全屏展示摄像头实时画面,点击任意位置退出全屏。
///
/// 涉及页面:监控页 - 监控画面
class
VideoFullscreenPage
extends
StatelessWidget
{
/// 当前视频传输模式
final
VideoStreamMode
mode
;
/// WebRTC 模式下的视频渲染器
final
RTCVideoRenderer
?
webrtcRenderer
;
/// P2P 模式下的播放控制器
final
AppPlayerController
?
p2pController
;
const
VideoFullscreenPage
({
super
.
key
,
required
this
.
mode
,
this
.
webrtcRenderer
,
this
.
p2pController
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
black
,
body:
GestureDetector
(
onTap:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
Stack
(
fit:
StackFit
.
expand
,
children:
[
// 根据模式渲染不同的视频画面
_buildVideoContent
(),
// 底部提示栏
_buildBottomBar
(),
],
),
),
);
}
/// 按模式渲染视频内容
Widget
_buildVideoContent
()
{
if
(
mode
==
VideoStreamMode
.
p2p
&&
p2pController
!=
null
)
{
return
AppPlayerView
(
controller:
p2pController
!);
}
if
(
mode
==
VideoStreamMode
.
webrtc
&&
webrtcRenderer
!=
null
)
{
return
RTCVideoView
(
webrtcRenderer
!,
objectFit:
RTCVideoViewObjectFit
.
RTCVideoViewObjectFitContain
,
);
}
return
const
SizedBox
.
shrink
();
}
/// 底部提示栏(左下角)
Widget
_buildBottomBar
()
{
return
Positioned
(
bottom:
0
,
left:
0
,
right:
0
,
child:
Container
(
padding:
const
EdgeInsets
.
fromLTRB
(
20
,
12
,
20
,
48
),
decoration:
const
BoxDecoration
(
gradient:
LinearGradient
(
begin:
Alignment
.
bottomCenter
,
end:
Alignment
.
topCenter
,
colors:
[
Colors
.
black54
,
Colors
.
transparent
],
),
),
child:
const
Row
(
children:
[
Icon
(
Icons
.
fullscreen_exit
,
color:
Colors
.
white70
,
size:
20
),
SizedBox
(
width:
8
),
Text
(
'点击任意位置退出全屏'
,
style:
TextStyle
(
color:
Colors
.
white70
,
fontSize:
14
),
),
],
),
),
);
}
}
lib/views/monitoring/index/widgets/video_player_widget.dart
0 → 100644
View file @
d697b832
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:flutter_webrtc/flutter_webrtc.dart'
;
import
'package:vsdk/app_player.dart'
;
import
'../../../../enums/video_stream_mode_enum.dart'
;
import
'../../../../services/webrtc_service.dart'
;
import
'video_fullscreen_page.dart'
;
import
'video_stream_switch.dart'
;
/// 通用实时视频播放组件(兼容 WebRTC + P2P 双模式)
///
/// 职责:
/// - WebRTC 模式:通过 [RTCVideoView] 渲染远程摄像头实时画面
/// - P2P 模式:通过 [AppPlayerView] 渲染 Texture 视频画面
/// - 依据当前模式的连接状态显示对应的状态指示器
/// - 右下角提供模式切换按钮(P2P/RTC)和全屏按钮
///
/// 涉及页面:监控页 - 监控画面
class
VideoPlayerWidget
extends
StatelessWidget
{
/// 当前视频传输模式
final
VideoStreamMode
streamMode
;
/// 是否正在切换模式
final
bool
isSwitchingMode
;
/// WebRTC 视频渲染器(WebRTC 模式时使用)
final
RTCVideoRenderer
?
webrtcRenderer
;
/// WebRTC 连接状态
final
WebrtcConnectionState
webrtcConnectionState
;
/// P2P 视频播放控制器(P2P 模式时使用)
final
AppPlayerController
?
p2pController
;
/// P2P 模式是否已连接
final
bool
isP2pConnected
;
/// 连接失败时点击重试回调
final
VoidCallback
?
onRetry
;
/// 模式切换回调
final
VoidCallback
?
onToggleMode
;
const
VideoPlayerWidget
({
super
.
key
,
required
this
.
streamMode
,
this
.
isSwitchingMode
=
false
,
this
.
webrtcRenderer
,
required
this
.
webrtcConnectionState
,
this
.
p2pController
,
this
.
isP2pConnected
=
false
,
this
.
onRetry
,
this
.
onToggleMode
,
});
/// 当前模式视频是否正在显示
bool
get
_isVideoShowing
{
if
(
streamMode
==
VideoStreamMode
.
webrtc
)
{
return
webrtcConnectionState
==
WebrtcConnectionState
.
connected
;
}
return
isP2pConnected
&&
p2pController
!=
null
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
ClipRRect
(
child:
Container
(
padding:
EdgeInsets
.
only
(
top:
15
.
h
,
bottom:
35
.
h
,
left:
18
.
w
,
right:
18
.
w
),
decoration:
BoxDecoration
(
image:
const
DecorationImage
(
image:
AssetImage
(
'lib/assets/monitoring/box_bg_674x509.png'
),
fit:
BoxFit
.
fill
,
),
border:
Border
.
all
(
color:
Colors
.
red
,
width:
1
),
),
child:
Stack
(
fit:
StackFit
.
expand
,
children:
[
// 视频画面层
_buildVideoLayer
(),
// 状态指示层
_buildStatusOverlay
(),
// 模式切换按钮(右下角倒数第二个位置)
if
(
onToggleMode
!=
null
&&
!
isSwitchingMode
)
_buildToggleModeButton
(),
// 全屏按钮(右下角最后一个位置)
if
(
_isVideoShowing
)
_buildFullscreenButton
(
context
),
],
),
),
);
}
// ==================== 视频画面层 ====================
/// 视频画面
Widget
_buildVideoLayer
()
{
if
(
streamMode
==
VideoStreamMode
.
webrtc
)
{
if
(
webrtcConnectionState
==
WebrtcConnectionState
.
connected
&&
webrtcRenderer
!=
null
)
{
return
RTCVideoView
(
webrtcRenderer
!,
objectFit:
RTCVideoViewObjectFit
.
RTCVideoViewObjectFitContain
,
);
}
return
_buildPlaceholder
();
}
// P2P 模式
if
(
isP2pConnected
&&
p2pController
!=
null
)
{
return
AppPlayerView
(
controller:
p2pController
!);
}
return
_buildPlaceholder
();
}
// ==================== 状态遮罩层 ====================
/// 状态遮罩层
Widget
_buildStatusOverlay
()
{
if
(
isSwitchingMode
)
{
return
_buildSwitchingOverlay
();
}
if
(
streamMode
==
VideoStreamMode
.
webrtc
)
{
switch
(
webrtcConnectionState
)
{
case
WebrtcConnectionState
.
connecting
:
return
_buildLoadingOverlay
(
'正在连接摄像头...'
);
case
WebrtcConnectionState
.
reconnecting
:
return
_buildLoadingOverlay
(
'正在重新连接...'
);
case
WebrtcConnectionState
.
failed
:
return
_buildErrorOverlay
();
default
:
return
const
SizedBox
.
shrink
();
}
}
// P2P 模式:未连接显示加载
if
(!
isP2pConnected
)
{
return
_buildLoadingOverlay
(
'正在 P2P 连接...'
);
}
return
const
SizedBox
.
shrink
();
}
/// 模式切换中遮罩
Widget
_buildSwitchingOverlay
()
{
return
Container
(
color:
Colors
.
black26
,
child:
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
const
CircularProgressIndicator
(
color:
Colors
.
white70
),
SizedBox
(
height:
16
.
h
),
Text
(
'正在切换传输模式...'
,
style:
TextStyle
(
fontSize:
18
.
sp
,
color:
Colors
.
white70
),
),
],
),
),
);
}
/// 加载中遮罩
Widget
_buildLoadingOverlay
(
String
message
)
{
return
Container
(
color:
Colors
.
black26
,
child:
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
const
CircularProgressIndicator
(
color:
Colors
.
white70
),
SizedBox
(
height:
16
.
h
),
Text
(
message
,
style:
TextStyle
(
fontSize:
18
.
sp
,
color:
Colors
.
white70
),
),
],
),
),
);
}
/// 错误遮罩
Widget
_buildErrorOverlay
()
{
return
Container
(
color:
Colors
.
black38
,
child:
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Icon
(
Icons
.
error_outline
,
size:
48
.
w
,
color:
Colors
.
white54
),
SizedBox
(
height:
16
.
h
),
Text
(
'视频连接失败'
,
style:
TextStyle
(
fontSize:
20
.
sp
,
color:
Colors
.
white70
,
fontWeight:
FontWeight
.
w500
,
),
),
SizedBox
(
height:
24
.
h
),
ElevatedButton
.
icon
(
onPressed:
onRetry
,
icon:
const
Icon
(
Icons
.
refresh
),
label:
const
Text
(
'重新连接'
),
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
const
Color
(
0xFF3A87FF
),
foregroundColor:
Colors
.
white
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
),
),
],
),
),
);
}
// ==================== 叠加按钮 ====================
/// 全屏按钮(右下角)
Widget
_buildFullscreenButton
(
BuildContext
context
)
{
return
Positioned
(
right:
12
.
w
,
bottom:
12
.
h
,
child:
GestureDetector
(
onTap:
()
{
Navigator
.
of
(
context
).
push
(
MaterialPageRoute
(
builder:
(
_
)
=>
VideoFullscreenPage
(
mode:
streamMode
,
webrtcRenderer:
webrtcRenderer
,
p2pController:
p2pController
,
),
),
);
},
child:
Container
(
width:
40
.
w
,
height:
40
.
w
,
decoration:
BoxDecoration
(
color:
Colors
.
black38
,
borderRadius:
BorderRadius
.
circular
(
8
.
r
),
),
child:
Icon
(
Icons
.
fullscreen
,
color:
Colors
.
white
,
size:
22
.
w
,
),
),
),
);
}
/// 模式切换按钮(全屏按钮左侧 8px 间距)
Widget
_buildToggleModeButton
()
{
return
Positioned
(
right:
12
.
w
+
40
.
w
+
8
.
w
,
bottom:
12
.
h
,
child:
VideoStreamSwitch
(
currentMode:
streamMode
,
onToggle:
onToggleMode
!,
),
);
}
/// 未连接时的占位
Widget
_buildPlaceholder
()
{
return
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Icon
(
Icons
.
videocam_off
,
size:
48
.
w
,
color:
Colors
.
white38
),
SizedBox
(
height:
16
.
h
),
// Text(
// '暂无视频画面',
// style: TextStyle(
// fontSize: 20.sp,
// color: Colors.white54,
// fontWeight: FontWeight.w500,
// ),
// ),
],
),
);
}
}
lib/views/monitoring/index/widgets/video_stream_switch.dart
0 → 100644
View file @
d697b832
import
'package:flutter/material.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'../../../../enums/video_stream_mode_enum.dart'
;
/// P2P/WebRTC 模式切换控件
///
/// 显示当前传输模式(P2P / RTC),点击切换至另一种模式。
/// 位置:视频容器右下角,全屏按钮左侧。
///
/// 涉及页面:监控页 - 监控画面
class
VideoStreamSwitch
extends
StatelessWidget
{
/// 当前视频传输模式
final
VideoStreamMode
currentMode
;
/// 点击切换回调
final
VoidCallback
onToggle
;
const
VideoStreamSwitch
({
super
.
key
,
required
this
.
currentMode
,
required
this
.
onToggle
,
});
@override
Widget
build
(
BuildContext
context
)
{
final
isP2p
=
currentMode
==
VideoStreamMode
.
p2p
;
return
GestureDetector
(
onTap:
onToggle
,
child:
Container
(
width:
40
.
w
,
height:
40
.
w
,
decoration:
BoxDecoration
(
color:
isP2p
?
const
Color
(
0xFF3A87FF
).
withValues
(
alpha:
0.6
)
:
Colors
.
black38
,
borderRadius:
BorderRadius
.
circular
(
8
.
r
),
border:
Border
.
all
(
color:
isP2p
?
Colors
.
white54
:
Colors
.
white24
,
width:
1
,
),
),
child:
Center
(
child:
Text
(
isP2p
?
'P2P'
:
'RTC'
,
style:
TextStyle
(
fontSize:
10
.
sp
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w500
,
),
),
),
),
);
}
}
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