You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
5.3 KiB

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)
}