refactor: 重构设置页面并修复屏幕共享黑屏问题
将安全验证和输入设置从主界面迁移至独立的 SettingsActivity,并允许用户手动选择模拟点击方式。 修复 Android 10+ 上 MediaProjection Token 传递失效导致黑屏的问题,通过静态变量保存授权结果并增加捕获延时与心跳保活机制。 优化多端视频编解码器协商,统一在 Offer SDP 中将 VP8/VP9 优先排列,H264 仅作兜底,提升跨端解码兼容性。
This commit is contained in:
@@ -128,15 +128,82 @@ class WebRtcController {
|
||||
'optional': [],
|
||||
};
|
||||
final offer = await _pc!.createOffer(constraints);
|
||||
await _pc!.setLocalDescription(offer);
|
||||
_sendOffer(offer.sdp!, authType: authType, authValue: authValue);
|
||||
// 与 WebRTCControlled 的 optimizeSdp 保持一致:Offer 的 m=video 行优先 VP8/VP9,
|
||||
// H264 兜底,确保各控制端(桌面/浏览器/Chromium/Linux)都能稳定解码,
|
||||
// 即编码端(被控端)与解码端(控制端)协商出一致的、可解码的视频格式。
|
||||
final reorderedSdp = _preferVideoCodecs(offer.sdp!);
|
||||
final offerWithPref = RTCSessionDescription(reorderedSdp, offer.type);
|
||||
await _pc!.setLocalDescription(offerWithPref);
|
||||
_sendOffer(reorderedSdp, authType: authType, authValue: authValue);
|
||||
}
|
||||
|
||||
/// 重排 Offer SDP 中 `m=video` 行的视频编解码器顺序:VP8/VP9 优先,H264 兜底。
|
||||
///
|
||||
/// 与 Android 被控端 [WebRtcClient.optimizeSdp] 的编解码优先级保持一致,
|
||||
/// 保证协商出的编码格式控制端一定可解码(编码/解码一致性)。
|
||||
String _preferVideoCodecs(String sdp) {
|
||||
final lines = sdp.split('\r\n');
|
||||
final vp8 = <String>[];
|
||||
final vp9 = <String>[];
|
||||
final h264 = <String>[];
|
||||
for (final line in lines) {
|
||||
final t = line.trim();
|
||||
if (t.startsWith('a=rtpmap:')) {
|
||||
final payload = t.split(':')[1].split(' ')[0];
|
||||
if (t.contains('VP8/90000')) {
|
||||
vp8.add(payload);
|
||||
} else if (t.contains('VP9/90000')) {
|
||||
vp9.add(payload);
|
||||
} else if (t.contains('H264/90000')) {
|
||||
h264.add(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
final ordered = <String>[...vp8, ...vp9, ...h264];
|
||||
if (ordered.isEmpty) return sdp;
|
||||
|
||||
final result = <String>[];
|
||||
for (final line in lines) {
|
||||
final t = line.trim();
|
||||
if (t.startsWith('m=video')) {
|
||||
final parts = t.split(' ');
|
||||
if (parts.length > 3) {
|
||||
final head = '${parts[0]} ${parts[1]} ${parts[2]}';
|
||||
final rest = parts
|
||||
.skip(3)
|
||||
.where((p) => !ordered.contains(p))
|
||||
.toList();
|
||||
result.add([head, ...ordered, ...rest].join(' '));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(line);
|
||||
}
|
||||
return result.join('\r\n');
|
||||
}
|
||||
|
||||
void _onTrack(RTCTrackEvent event) {
|
||||
if (event.track.kind == 'video' && event.streams.isNotEmpty) {
|
||||
renderer.srcObject = event.streams[0];
|
||||
onRemoteStream?.call(renderer);
|
||||
if (event.track.kind != 'video') return;
|
||||
_bindRemoteVideo(event);
|
||||
}
|
||||
|
||||
/// 绑定远端视频轨道到渲染器。
|
||||
///
|
||||
/// 与 Web 端 WebRtcController.ontrack 的兜底逻辑保持一致:
|
||||
/// 某些平台/协商场景下(Unified Plan + recvonly)`event.streams` 可能为空,
|
||||
/// 此时必须用 `event.track` 自行构造 MediaStream,否则 renderer.srcObject
|
||||
/// 为空 -> 控制端拿不到画面(黑屏/一直显示“等待画面”),但控制通道不受影响。
|
||||
Future<void> _bindRemoteVideo(RTCTrackEvent event) async {
|
||||
MediaStream stream;
|
||||
if (event.streams.isNotEmpty) {
|
||||
stream = event.streams[0];
|
||||
} else {
|
||||
// event.streams 为空:用 track 自行构造 MediaStream。
|
||||
stream = await createLocalMediaStream('remoteVideo');
|
||||
await stream.addTrack(event.track);
|
||||
}
|
||||
renderer.srcObject = stream;
|
||||
onRemoteStream?.call(renderer);
|
||||
}
|
||||
|
||||
void _onDataChannel(RTCDataChannel channel) {
|
||||
|
||||
Reference in New Issue
Block a user