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

如何在Flutter中为Android和iOS集成带画中画的WebRTC视频通话

解决方案:Flutter WebRTC 视频通话画中画实现

针对你的Flutter跨平台(Android/iOS)WebRTC视频通话画中画需求,推荐结合flutter_webrtc(主流WebRTC插件)+原生画中画(PiP)API实现,这比第三方PiP插件适配WebRTC流更可靠,分平台具体步骤如下:

Android 端实现

  1. 配置Manifest文件
    在android/app/src/main/AndroidManifest.xml中修改主Activity属性,添加PiP支持:

    <activity
        android:name=".MainActivity"
        android:supportsPictureInPicture="true"
        android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
        android:launchMode="singleTop">
        <!-- 保留原有配置 -->
    </activity>
    

    同时添加必要权限:

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    
  2. Flutter端调用原生PiP
    通过MethodChannel触发原生代码,传递RTCVideoRenderer的Surface标识:

    import 'package:flutter/services.dart';
    import 'package:flutter_webrtc/flutter_webrtc.dart';
    
    class PipManager {
      static const MethodChannel _channel = MethodChannel('pip_channel');
    
      static Future<void> enterPipMode(RTCVideoRenderer renderer) async {
        final surfaceId = renderer.textureId;
        await _channel.invokeMethod('enterPip', {'surfaceId': surfaceId});
      }
    }
    
  3. Android原生实现PiP逻辑
    在MainActivity.kt中处理MethodChannel调用,启动PiP模式:

    import android.app.PictureInPictureModeCompat
    import android.os.Bundle
    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    import io.flutter.plugin.common.MethodChannel
    
    class MainActivity : FlutterActivity() {
        private val CHANNEL = "pip_channel"
    
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            super.configureFlutterEngine(flutterEngine)
            MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
                if (call.method == "enterPip") {
                    val surfaceId = call.argument<Int>("surfaceId") ?: run {
                        result.error("INVALID_ARG", "Surface ID missing", null)
                        return@setMethodCallHandler
                    }
                    // 获取WebRTC渲染用的SurfaceView
                    val surfaceView = flutterEngine.platformViewsController.getPlatformViewById(surfaceId)
                    // 配置PiP窗口比例
                    val aspectRatio = android.util.Rational(16, 9)
                    val params = PictureInPictureModeCompat.Builder()
                        .setAspectRatio(aspectRatio)
                        .build()
                    // 进入PiP模式
                    enterPictureInPictureMode(params)
                    result.success(null)
                } else {
                    result.notImplemented()
                }
            }
        }
    }
    

iOS 端实现

  1. 配置Info.plist
    添加PiP及视频通话相关配置到ios/Runner/Info.plist:

    <key>UIBackgroundModes</key>
    <array>
        <string>voip</string> <!-- 后台维持视频通话需要 -->
    </array>
    <key>NSCameraUsageDescription</key>
    <string>需要访问摄像头进行视频通话</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>需要访问麦克风进行语音通话</string>
    <key>UIPictureInPictureEnabled</key>
    <true/>
    
  2. Flutter端调用原生PiP
    复用Android端的PipManager类,传递RTCVideoRenderer的textureId:

    static Future<void> enterPipMode(RTCVideoRenderer renderer) async {
      final textureId = renderer.textureId;
      await _channel.invokeMethod('enterPip', {'textureId': textureId});
    }
    
  3. iOS原生实现PiP逻辑
    在AppDelegate.swift中处理MethodChannel调用,启动PiP:

    import UIKit
    import Flutter
    import WebRTC
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
        private let CHANNEL = "pip_channel"
        private var pipController: AVPictureInPictureController?
    
        override func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            guard let controller = window?.rootViewController as? FlutterViewController else {
                return super.application(application, didFinishLaunchingWithOptions: launchOptions)
            }
            let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
            channel.setMethodCallHandler { [weak self] call, result in
                guard let self = self else { return }
                if call.method == "enterPip" {
                    guard let textureId = call.arguments as? Int else {
                        result.error("INVALID_ARG", "Texture ID missing", nil)
                        return
                    }
                    // 获取WebRTC的渲染视图
                    guard let videoView = controller.platformViewsController.view(for: textureId) as? RTCEAGLVideoView,
                          let videoLayer = videoView.layer else {
                        result.error("NO_LAYER", "Video layer not found", nil)
                        return
                    }
                    // 配置PiP内容源
                    let contentSource = AVPictureInPictureController.ContentSource(
                        activeVideoLayer: videoLayer,
                        contentSourceRect: videoLayer.bounds
                    )
                    // 初始化并启动PiP
                    self.pipController = AVPictureInPictureController(contentSource: contentSource)
                    self.pipController?.delegate = self
                    if self.pipController?.isPictureInPictureSupported ?? false {
                        self.pipController?.startPictureInPicture()
                        result.success(nil)
                    } else {
                        result.error("NOT_SUPPORTED", "PiP not supported on this device", nil)
                    }
                } else {
                    result.notImplemented()
                }
            }
            GeneratedPluginRegistrant.register(with: self)
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
    }
    
    extension AppDelegate: AVPictureInPictureControllerDelegate {
        func pictureInPictureControllerDidStopPictureInPicture(_ controller: AVPictureInPictureController) {
            pipController = nil
        }
    }
    

替代方案:适配pip_flutter插件

如果你想继续使用pip_flutter,可以尝试将WebRTC视频帧转换为VideoPlayerController可识别的流,但这种方式会有一定延迟,仅适合非实时要求极高的场景:

  • 通过flutter_webrtc的RTCVideoRenderer监听帧回调,将每一帧的CVPixelBuffer(iOS)或Bitmap(Android)编码为视频流
  • 将编码后的流作为VideoPlayerController的数据源,再传入pip_flutter的播放器

但更推荐第一种原生PiP结合flutter_webrtc的方案,实时性和稳定性更优。

内容的提问来源于stack exchange,提问作者Arbaz Shaikh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:22:51