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

Flutter集成Azure Communication Services多用户群呼面试APP开发求助

Flutter集成Azure Communication Services(ACS)实现多用户群聊通话方案

以下是针对Flutter适配Android/iOS、通过平台通道集成ACS的具体落地方案,重点解决平台通道通信、视频流渲染到Dart UI的核心问题:

一、基础平台通道通信搭建

用MethodChannel处理ACS初始化、登录、发起/加入通话等主动调用,EventChannel监听通话状态、视频流变化等被动事件。

Dart层代码

final MethodChannel _acsChannel = MethodChannel('com.yourapp/acs');
final EventChannel _acsEventChannel = EventChannel('com.yourapp/acs_events');
StreamSubscription? _eventSubscription;

// 初始化事件监听
void initAcsEventListeners() {
  _eventSubscription = _acsEventChannel.receiveBroadcastStream().listen((event) {
    switch (event['type']) {
      case 'callConnected':
        // 更新UI状态,比如隐藏加载框
        break;
      case 'videoStreamAdded':
        // 拿到新视频流ID,触发渲染逻辑
        final String streamId = event['streamId'];
        _addVideoStreamWidget(streamId);
        break;
      case 'videoStreamRemoved':
        // 移除对应视频流的UI
        break;
    }
  });
}

// 调用原生初始化ACS客户端
Future<void> initAcs(String connectionString) async {
  try {
    await _acsChannel.invokeMethod('initAcs', {'connectionString': connectionString});
  } catch (e) {
    // 处理初始化失败逻辑
  }
}

Android原生层(Kotlin)

// 单例管理ACS核心实例
object AcsManager {
    lateinit var callClient: CallClient
    lateinit var callAgent: CallAgent
    var currentCall: Call? = null
}

class AcsMethodCallHandler(private val context: Context) : MethodCallHandler {
    override fun onMethodCall(call: MethodCall, result: Result) {
        when (call.method) {
            "initAcs" -> {
                val connectionString = call.argument<String>("connectionString")
                initAcsClient(connectionString, result)
            }
            "joinCall" -> {
                val meetingLink = call.argument<String>("meetingLink")
                joinGroupCall(meetingLink, result)
            }
            // 其他方法:leaveCall、muteAudio等
            else -> result.notImplemented()
        }
    }

    private fun initAcsClient(connectionString: String?, result: Result) {
        try {
            AcsManager.callClient = CallClientBuilder().build()
            val tokenCredential = CommunicationTokenCredential(connectionString)
            AcsManager.callAgent = AcsManager.callClient.createCallAgent(context, tokenCredential).get()
            result.success(null)
        } catch (e: Exception) {
            result.error("INIT_FAILED", e.message, null)
        }
    }
}

iOS原生层(Swift)

// 单例管理ACS核心实例
class AcsManager {
    static let shared = AcsManager()
    var callClient: CallClient?
    var callAgent: CallAgent?
    var currentCall: Call?
}

class AcsMethodCallHandler: NSObject, FlutterMethodCallHandler {
    private let controller: FlutterViewController
    
    init(controller: FlutterViewController) {
        self.controller = controller
        super.init()
    }
    
    func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "initAcs":
            guard let args = call.arguments as? [String: String], let connStr = args["connectionString"] else {
                result(FlutterError(code: "INVALID_ARGS", message: "缺少连接字符串", details: nil))
                return
            }
            initAcsClient(connStr, result: result)
        case "joinCall":
            guard let args = call.arguments as? [String: String], let link = args["meetingLink"] else {
                result(FlutterError(code: "INVALID_ARGS", message: "缺少会议链接", details: nil))
                return
            }
            joinGroupCall(link, result: result)
        default:
            result(FlutterMethodNotImplemented)
        }
    }
    
    private func initAcsClient(_ connStr: String, result: FlutterResult) {
        CallClient.createClient { client, error in
            guard let client = client else {
                result(FlutterError(code: "INIT_FAILED", message: error?.localizedDescription, details: nil))
                return
            }
            AcsManager.shared.callClient = client
            let tokenCredential = try? CommunicationTokenCredential(token: connStr)
            client.createCallAgent(tokenCredential: tokenCredential!) { agent, error in
                guard let agent = agent else {
                    result(FlutterError(code: "AGENT_FAILED", message: error?.localizedDescription, details: nil))
                    return
                }
                AcsManager.shared.callAgent = agent
                result(nil)
            }
        }
    }
}

二、ACS视频流渲染到Flutter UI

核心是用PlatformView嵌入原生视频渲染控件,结合EventChannel通知Dart层新增的视频流。

Android端实现

  1. 自定义PlatformView承载ACS的SurfaceView
class AcsVideoView(context: Context, messenger: BinaryMessenger, id: Int, args: Map<String, Any>?) : PlatformView {
    private val surfaceView = SurfaceView(context)
    private val streamId = args?.get("streamId") as String

    init {
        // 从全局Call实例中获取对应视频流
        val videoStream = AcsManager.currentCall?.remoteVideoStreams?.find { it.id == streamId }
        videoStream?.addOnVideoStreamChangedListener { stream ->
            if (stream.state == VideoStreamState.ACTIVE) {
                stream.videoRenderer.setView(surfaceView)
            }
        }
    }

    override fun getView(): View = surfaceView
    override fun dispose() {
        // 清理渲染资源
        AcsManager.currentCall?.remoteVideoStreams?.find { it.id == streamId }?.videoRenderer?.setView(null)
    }
}

// 注册PlatformView工厂
class AcsVideoViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, id: Int, args: Any?): PlatformView {
        return AcsVideoView(context, messenger, id, args as Map<String, Any>)
    }
}
  1. 在MainActivity中注册
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    // 注册视频View工厂
    flutterEngine.platformViewsController.registry.registerViewFactory(
        "com.yourapp/acs_video_view",
        AcsVideoViewFactory(flutterEngine.dartExecutor.binaryMessenger)
    )
    // 注册Method/Event Channel
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.yourapp/acs")
        .setMethodCallHandler(AcsMethodCallHandler(this))
    EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.yourapp/acs_events")
        .setStreamHandler(AcsEventStreamHandler(this))
}

iOS端实现

  1. 自定义PlatformView承载ACS的VideoView
class AcsVideoView: NSObject, FlutterPlatformView {
    private let videoView = VideoView()
    private let streamId: String

    init(frame: CGRect, viewId: Int64, args: Any?, messenger: FlutterBinaryMessenger) {
        guard let argsMap = args as? [String: String], let id = argsMap["streamId"] else {
            streamId = ""
            super.init()
            return
        }
        streamId = id
        super.init()
        // 绑定视频流
        let videoStream = AcsManager.shared.currentCall?.remoteVideoStreams.first(where: { $0.id == streamId })
        videoStream?.addVideoStreamChangedListener { stream in
            if stream.state == .active {
                stream.videoRenderer = self.videoView
            }
        }
    }

    func view() -> UIView { videoView }
}

// 注册PlatformView工厂
class AcsVideoViewFactory: NSObject, FlutterPlatformViewFactory {
    private let messenger: FlutterBinaryMessenger

    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }

    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
        return AcsVideoView(frame: frame, viewId: viewId, args: args, messenger: messenger)
    }

    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}
  1. 在AppDelegate中注册
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        
        // 注册视频View工厂
        let videoFactory = AcsVideoViewFactory(messenger: controller.binaryMessenger)
        registrar(forPlugin: "com.yourapp.acs").register(videoFactory, withId: "com.yourapp/acs_video_view")
        
        // 注册Method/Event Channel
        let acsChannel = FlutterMethodChannel(name: "com.yourapp/acs", binaryMessenger: controller.binaryMessenger)
        acsChannel.setMethodCallHandler(AcsMethodCallHandler(controller: controller).handle)
        
        let acsEventChannel = FlutterEventChannel(name: "com.yourapp/acs_events", binaryMessenger: controller.binaryMessenger)
        acsEventChannel.setStreamHandler(AcsEventStreamHandler())
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

Dart层渲染视频流

Widget buildVideoStream(String streamId) {
  return Platform.isAndroid
      ? AndroidView(
          viewType: 'com.yourapp/acs_video_view',
          creationParams: {'streamId': streamId},
          creationParamsCodec: const StandardMessageCodec(),
        )
      : UiKitView(
          viewType: 'com.yourapp/acs_video_view',
          creationParams: {'streamId': streamId},
          creationParamsCodec: const StandardMessageCodec(),
        );
}

三、关键注意事项

  • 事件通道传递流信息:原生层监听ACS的remoteVideoStreamAdded事件,通过EventChannel向Dart层发送videoStreamAdded事件,携带streamId和用户标识。
  • 全局实例管理:用单例类(Android的AcsManager、iOS的AcsManager.shared)统一管理CallClient、CallAgent、当前Call对象,避免多次初始化或实例丢失。
  • 资源清理:PlatformView销毁时,务必解绑视频流的渲染控件,避免内存泄漏。

四、优质学习资料推荐

  • Flutter平台通道官方文档:重点钻研PlatformView生命周期、消息编解码逻辑,吃透原生与Dart的通信机制。
  • ACS原生SDK官方示例:分别跑通Android和iOS的ACS群聊通话Demo,理解原生层的流处理、渲染逻辑,再映射到Flutter集成场景。
  • 第三方音视频SDK的Flutter集成源码:参考Agora、Zoom等SDK的Flutter插件实现,他们的PlatformView视频渲染逻辑和ACS完全相通。

内容的提问来源于stack exchange,提问作者Tushar Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 12:45:36