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