路径规划孔洞和 远程遥控视频切换
This commit is contained in:
@@ -1729,8 +1729,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
isStartWork = false;
|
||||
});
|
||||
_saveDataToLocal();
|
||||
if (isStopWork || !isStartWork) return;
|
||||
_stopWork();
|
||||
|
||||
//if (isStopWork || !isStartWork) return;
|
||||
},
|
||||
icon: const Icon(Icons.close, color: Colors.grey, size: 20),
|
||||
padding: EdgeInsets.zero,
|
||||
|
||||
@@ -82,6 +82,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
streamUrl: _videoStreamUrl,
|
||||
showLeftPip: state.showLeftPip, // 从 Cubit 状态中读取
|
||||
showRightPip: state.showRightPip, // 从 Cubit 状态中读取
|
||||
isFrontMain: context.watch<RemoteControlCubit>().state.controlEntity.originY>= 0.5,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -9,11 +9,15 @@ class WebRTCLocalPlayer extends StatefulWidget {
|
||||
final bool showLeftPip;
|
||||
final bool showRightPip;
|
||||
|
||||
// 🔥 新增:外部传入是否显示前视角
|
||||
final bool isFrontMain;
|
||||
|
||||
const WebRTCLocalPlayer({
|
||||
super.key,
|
||||
required this.streamUrl,
|
||||
this.showLeftPip = true,
|
||||
this.showRightPip = true,
|
||||
required this.isFrontMain, // 🔥 必传
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -25,15 +29,10 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
RTCPeerConnection? _peerConnection;
|
||||
|
||||
bool _isInitialized = false;
|
||||
bool _isFrontMain = true;
|
||||
|
||||
// 使用 ValueNotifier 配合局部刷新,提升拖拽性能
|
||||
final ValueNotifier<Offset> _leftPosNotifier = ValueNotifier(
|
||||
const Offset(20, 80),
|
||||
);
|
||||
final ValueNotifier<Offset> _rightPosNotifier = ValueNotifier(
|
||||
const Offset(200, 80),
|
||||
);
|
||||
final ValueNotifier<Offset> _leftPosNotifier = ValueNotifier(const Offset(20, 80));
|
||||
final ValueNotifier<Offset> _rightPosNotifier = ValueNotifier(const Offset(200, 80));
|
||||
bool _isPosInitialized = false;
|
||||
|
||||
@override
|
||||
@@ -73,32 +72,21 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
};
|
||||
|
||||
try {
|
||||
RTCSessionDescription offer = await _peerConnection!.createOffer({
|
||||
'offerToReceiveVideo': 1,
|
||||
});
|
||||
RTCSessionDescription offer = await _peerConnection!.createOffer({'offerToReceiveVideo': 1});
|
||||
await _peerConnection!.setLocalDescription(offer);
|
||||
|
||||
Uri uri = Uri.parse(
|
||||
widget.streamUrl.replaceFirst('webrtc://', 'http://'),
|
||||
);
|
||||
Uri uri = Uri.parse(widget.streamUrl.replaceFirst('webrtc://', 'http://'));
|
||||
String apiUrl = "http://${uri.host}:1985/rtc/v1/play/";
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(apiUrl),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
"api": apiUrl,
|
||||
"streamurl": widget.streamUrl,
|
||||
"clientip": null,
|
||||
"sdp": offer.sdp,
|
||||
}),
|
||||
body: jsonEncode({"api": apiUrl, "streamurl": widget.streamUrl, "clientip": null, "sdp": offer.sdp}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
await _peerConnection!.setRemoteDescription(
|
||||
RTCSessionDescription(data['sdp'], 'answer'),
|
||||
);
|
||||
await _peerConnection!.setRemoteDescription(RTCSessionDescription(data['sdp'], 'answer'));
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("信令错误: $e");
|
||||
@@ -117,8 +105,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
|
||||
// 四分屏裁剪视图
|
||||
Widget _buildQuadrantView({required Alignment alignment}) {
|
||||
if (!_isInitialized || _renderer.srcObject == null)
|
||||
return Container(color: Colors.black);
|
||||
if (!_isInitialized || _renderer.srcObject == null) return Container(color: Colors.black);
|
||||
|
||||
return RepaintBoundary(
|
||||
child: ClipRect(
|
||||
@@ -126,11 +113,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
widthFactor: 2.0,
|
||||
heightFactor: 2.0,
|
||||
alignment: alignment,
|
||||
child: RTCVideoView(
|
||||
_renderer,
|
||||
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
|
||||
mirror: false,
|
||||
),
|
||||
child: RTCVideoView(_renderer, objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, mirror: false),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -143,12 +126,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
return const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.transparent,
|
||||
],
|
||||
colors: [Colors.transparent, Colors.white, Colors.white, Colors.transparent],
|
||||
stops: [0.0, 0.15, 0.85, 1.0],
|
||||
).createShader(bounds);
|
||||
},
|
||||
@@ -158,19 +136,12 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
return const LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.transparent,
|
||||
],
|
||||
colors: [Colors.transparent, Colors.white, Colors.white, Colors.transparent],
|
||||
stops: [0.0, 0.05, 0.95, 1.0],
|
||||
).createShader(bounds);
|
||||
},
|
||||
blendMode: BlendMode.dstIn,
|
||||
child: _buildQuadrantView(
|
||||
alignment: _isFrontMain ? Alignment.topLeft : Alignment.topRight,
|
||||
),
|
||||
child: _buildQuadrantView(alignment: widget.isFrontMain ? Alignment.topLeft : Alignment.topRight),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -199,10 +170,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
|
||||
if (!_isPosInitialized) {
|
||||
_leftPosNotifier.value = const Offset(20, 80);
|
||||
_rightPosNotifier.value = Offset(
|
||||
constraints.maxWidth - pipW - 20,
|
||||
80,
|
||||
);
|
||||
_rightPosNotifier.value = Offset(constraints.maxWidth - pipW - 20, 80);
|
||||
_isPosInitialized = true;
|
||||
}
|
||||
|
||||
@@ -212,11 +180,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
Positioned.fill(
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildQuadrantView(
|
||||
alignment: _isFrontMain
|
||||
? Alignment.topLeft
|
||||
: Alignment.topRight,
|
||||
),
|
||||
_buildQuadrantView(alignment: widget.isFrontMain ? Alignment.topLeft : Alignment.topRight),
|
||||
Positioned.fill(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
||||
@@ -227,35 +191,14 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
),
|
||||
),
|
||||
|
||||
// ② 主视频层
|
||||
// ② 主视频层(🔥 去掉双击,交给外部控制)
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () =>
|
||||
setState(() => _isFrontMain = !_isFrontMain),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: _buildMainViewWithFeathering(),
|
||||
),
|
||||
),
|
||||
child: AspectRatio(aspectRatio: 16 / 9, child: _buildMainViewWithFeathering()),
|
||||
),
|
||||
|
||||
// ③ 悬浮小窗 - 解决阻尼感的 1:1 稳定拖拽
|
||||
if (widget.showLeftPip)
|
||||
_buildFastPip(
|
||||
_leftPosNotifier,
|
||||
Alignment.bottomLeft,
|
||||
constraints,
|
||||
pipW,
|
||||
pipH,
|
||||
),
|
||||
if (widget.showRightPip)
|
||||
_buildFastPip(
|
||||
_rightPosNotifier,
|
||||
Alignment.bottomRight,
|
||||
constraints,
|
||||
pipW,
|
||||
pipH,
|
||||
),
|
||||
if (widget.showLeftPip) _buildFastPip(_leftPosNotifier, Alignment.bottomLeft, constraints, pipW, pipH),
|
||||
if (widget.showRightPip) _buildFastPip(_rightPosNotifier, Alignment.bottomRight, constraints, pipW, pipH),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -263,12 +206,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
);
|
||||
}
|
||||
|
||||
void _correctPosition(
|
||||
ValueNotifier<Offset> notifier,
|
||||
BoxConstraints constraints,
|
||||
double w,
|
||||
double h,
|
||||
) {
|
||||
void _correctPosition(ValueNotifier<Offset> notifier, BoxConstraints constraints, double w, double h) {
|
||||
double maxX = (constraints.maxWidth - w).clamp(0.0, double.infinity);
|
||||
double maxY = (constraints.maxHeight - h).clamp(0.0, double.infinity);
|
||||
double newX = notifier.value.dx.clamp(0.0, maxX);
|
||||
@@ -279,13 +217,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
}
|
||||
|
||||
// 极致丝滑的局部刷新组件
|
||||
Widget _buildFastPip(
|
||||
ValueNotifier<Offset> notifier,
|
||||
Alignment align,
|
||||
BoxConstraints constraints,
|
||||
double w,
|
||||
double h,
|
||||
) {
|
||||
Widget _buildFastPip(ValueNotifier<Offset> notifier, Alignment align, BoxConstraints constraints, double w, double h) {
|
||||
return ValueListenableBuilder<Offset>(
|
||||
valueListenable: notifier,
|
||||
builder: (context, pos, child) {
|
||||
@@ -296,14 +228,8 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPanUpdate: (details) {
|
||||
// 关键修正:使用 notifier.value.dx 替换 pos.dx,消除位移丢失导致的阻尼感
|
||||
final double nextX = (notifier.value.dx + details.delta.dx).clamp(
|
||||
0.0,
|
||||
(constraints.maxWidth - w).clamp(0.0, double.infinity),
|
||||
);
|
||||
final double nextY = (notifier.value.dy + details.delta.dy).clamp(
|
||||
0.0,
|
||||
(constraints.maxHeight - h).clamp(0.0, double.infinity),
|
||||
);
|
||||
final double nextX = (notifier.value.dx + details.delta.dx).clamp(0.0, (constraints.maxWidth - w).clamp(0.0, double.infinity));
|
||||
final double nextY = (notifier.value.dy + details.delta.dy).clamp(0.0, (constraints.maxHeight - h).clamp(0.0, double.infinity));
|
||||
notifier.value = Offset(nextX, nextY);
|
||||
},
|
||||
child: child!,
|
||||
@@ -315,13 +241,7 @@ class _WebRTCLocalPlayerState extends State<WebRTCLocalPlayer> {
|
||||
height: h,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(0, 8))],
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: _buildQuadrantView(alignment: align),
|
||||
|
||||
Reference in New Issue
Block a user