You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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后台配置:

  1. Xcode中开启后台模式:勾选Audio, AirPlay, and Picture in Picture
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 06:16:30