Flutter WebRTC音视频通话按返回键终止问题求助
实现Flutter WebRTC通话转悬浮窗(类似WhatsApp)
1. 拦截返回键,避免通话终止
用WillPopScope包裹通话页面,拦截返回事件,不触发页面销毁,而是切换到悬浮窗模式:
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { // 触发悬浮窗逻辑,而非关闭页面 await _switchToFloatingMode(); // 返回false阻止默认返回行为 return false; }, child: Scaffold( body: _buildCallInterface(), // 你的通话UI(视频渲染、控制按钮等) ), ); }
2. 跨平台悬浮窗/画中画实现
Android端:原生悬浮窗承载通话
首先申请悬浮窗权限(SYSTEM_ALERT_WINDOW),再通过MethodChannel调用原生代码创建悬浮窗:
- Flutter侧调用:
Future<void> _switchToFloatingMode() async { try { await MethodChannel('com.yourapp/call_floating').invokeMethod('showFloatingWindow'); // 跳转到聊天页面 Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => ChatPage())); } on PlatformException catch (e) { print('悬浮窗创建失败: ${e.message}'); } }
- Android原生(Kotlin)示例:
class FloatingCallService : Service() { private lateinit var windowManager: WindowManager private lateinit var floatingView: View override fun onCreate() { super.onCreate() windowManager = getSystemService(WINDOW_SERVICE) as WindowManager floatingView = LayoutInflater.from(this).inflate(R.layout.floating_call_layout, null) // 配置悬浮窗参数 val params = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ) params.gravity = Gravity.TOP or Gravity.START params.x = 0 params.y = 100 // 将WebRTC的视频渲染视图绑定到悬浮窗 val videoSurface = floatingView.findViewById<SurfaceView>(R.id.floating_video) // 通过MethodChannel同步Flutter侧的WebRTC Renderer实例到原生,完成视图绑定 windowManager.addView(floatingView, params) } override fun onBind(intent: Intent): IBinder? = null }
在AndroidManifest.xml中注册权限和Service:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <service android:name=".FloatingCallService" />
iOS端:系统画中画(PiP)
利用iOS原生画中画能力,配合WebRTC后台配置:
- Xcode中开启后台模式:勾选
Audio, AirPlay, and Picture in Picture - Flutter侧触发画中画:
Future<void> _switchToFloatingMode() async { final localRenderer = _localVideoRenderer; // 你的RTCVideoRenderer实例 await localRenderer.enablePictureInPicture(); // 跳转到聊天页面 Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => ChatPage())); }
iOS的PiP会自动保持音频后台活跃,视频流在小窗中显示。
3. 保持WebRTC会话不中断
将RTCPeerConnection、MediaStream等核心实例放在全局单例或状态管理容器(如GetX、Riverpod)中,避免页面销毁时被释放:
class CallSession { static final CallSession _instance = CallSession._internal(); factory CallSession() => _instance; CallSession._internal(); RTCPeerConnection? peerConnection; MediaStream? localStream; MediaStream? remoteStream; // 初始化连接、处理信令等方法 }
注意:除非用户主动结束通话,否则不要调用peerConnection?.close()或localStream?.dispose()。
4. 处理生命周期变化
监听App前后台切换,确保音频流不被中断:
@override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { // 强制保持音频活跃 CallSession.instance.localStream?.getAudioTracks().forEach((track) { track.enabled = true; }); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); }
关键注意事项
- Android悬浮窗权限需要动态申请,Android 6.0以上需引导用户手动授权
- iOS画中画仅支持全屏视频切换,需确保渲染视图符合系统要求
- 后台音视频播放需配置对应权限和后台模式,避免被系统回收
内容的提问来源于stack exchange,提问作者Sagar R Anghan
相关产品推荐
相关产品推荐

