import Flutter import UIKit /// iOS 悬浮窗插件 - ReplayKit 方案 /// 使用 ReplayKit + Broadcast Upload Extension 实现全局字幕悬浮窗 public class FloatingWindowPlugin: NSObject, FlutterPlugin { // MARK: - 常量 private static let channelName = "floating_window" // MARK: - 属性 private var methodChannel: FlutterMethodChannel? private let replayKitManager = ReplayKitManager.shared // MARK: - 插件注册 public static func register(with registrar: FlutterPluginRegistrar) { let instance = FloatingWindowPlugin() // 创建方法通道 let methodChannel = FlutterMethodChannel( name: channelName, binaryMessenger: registrar.messenger() ) instance.methodChannel = methodChannel registrar.addMethodCallDelegate(instance, channel: methodChannel) // 设置 ReplayKit 回调 instance.setupReplayKitCallbacks() print("FloatingWindowPlugin (ReplayKit) 注册成功") } // MARK: - 方法调用处理 public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "enableFloatingWindow": enableFloatingWindow(result: result) case "disableFloatingWindow": disableFloatingWindow(result: result) case "showFloatingWindow": showFloatingWindow(result: result) case "hideFloatingWindow": hideFloatingWindow(result: result) case "updateContent": if let arguments = call.arguments as? [String: Any] { updateContent(arguments: arguments, result: result) } else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "无效的参数", details: nil)) } case "setPosition": // ReplayKit 方案中位置固定,但保持接口兼容性 result(true) default: result(FlutterMethodNotImplemented) } } // MARK: - ReplayKit 回调设置 /// 设置 ReplayKit 回调 private func setupReplayKitCallbacks() { replayKitManager.onBroadcastStarted = { [weak self] in self?.methodChannel?.invokeMethod("onFloatingWindowStateChanged", arguments: ["state": "minimized"]) } replayKitManager.onBroadcastFinished = { [weak self] in self?.methodChannel?.invokeMethod("onFloatingWindowClosed", arguments: nil) } replayKitManager.onBroadcastError = { [weak self] error in self?.methodChannel?.invokeMethod("onFloatingWindowError", arguments: ["error": error.localizedDescription]) } } // MARK: - 悬浮窗控制方法 /// 启用悬浮窗功能(启动 ReplayKit 广播) private func enableFloatingWindow(result: @escaping FlutterResult) { replayKitManager.startBroadcast { success, error in if success { result(true) } else { result(FlutterError( code: "ENABLE_ERROR", message: "启用悬浮窗失败: \(error?.localizedDescription ?? "未知错误")", details: nil )) } } } /// 禁用悬浮窗功能(停止 ReplayKit 广播) private func disableFloatingWindow(result: @escaping FlutterResult) { replayKitManager.stopBroadcast { success, error in if success { result(true) } else { result(FlutterError( code: "DISABLE_ERROR", message: "禁用悬浮窗失败: \(error?.localizedDescription ?? "未知错误")", details: nil )) } } } /// 显示悬浮窗(ReplayKit 方案中自动显示) private func showFloatingWindow(result: @escaping FlutterResult) { if replayKitManager.isBroadcasting { result(true) } else { result(FlutterError(code: "NOT_BROADCASTING", message: "广播未启动", details: nil)) } } /// 隐藏悬浮窗(ReplayKit 方案中通过停止广播实现) private func hideFloatingWindow(result: @escaping FlutterResult) { disableFloatingWindow(result: result) } /// 更新悬浮窗内容 private func updateContent(arguments: [String: Any], result: @escaping FlutterResult) { let sourceText = arguments["sourceText"] as? String ?? "" let translatedText = arguments["translatedText"] as? String ?? "" let sourceLanguage = arguments["sourceLanguage"] as? String ?? "" let targetLanguage = arguments["targetLanguage"] as? String ?? "" let isIntermediate = arguments["isIntermediate"] as? Bool ?? false // 构建显示文本 var displayText = "" if !sourceText.isEmpty && !translatedText.isEmpty { displayText = "\(sourceText)\n\(translatedText)" } else if !translatedText.isEmpty { displayText = translatedText } else if !sourceText.isEmpty { displayText = sourceText } replayKitManager.updateSubtitle( text: displayText, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage, isIntermediate: isIntermediate ) result(true) }