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.
116 lines
3.9 KiB
116 lines
3.9 KiB
import Flutter
|
|
import AVKit
|
|
import UIKit
|
|
|
|
@objc(FloatingUiPlugin)
|
|
public class FloatingUiPlugin: NSObject, FlutterPlugin {
|
|
private var pipContentViewArray: [PlayerView] = []
|
|
|
|
/**
|
|
* 更新音频会话配置,保证 PiP 播放场景的音频行为符合预期
|
|
* - Parameters: 无
|
|
* - Returns: 无
|
|
* - Throws: 无(内部捕获并记录 setCategory/setActive 可能抛出的异常)
|
|
*/
|
|
private static func updateAudioSession() {
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
do {
|
|
if #available(iOS 14.5, *) {
|
|
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [.overrideMutedMicrophoneInterruption, .mixWithOthers])
|
|
} else {
|
|
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [.mixWithOthers])
|
|
}
|
|
try audioSession.setActive(true)
|
|
} catch {
|
|
NSLog("Set audio session error: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flutter 插件注册入口
|
|
* - Parameters:
|
|
* - registrar: FlutterPluginRegistrar 实例,用于注册通道与原生视图
|
|
* - Returns: 无
|
|
* - Throws: 无(注册流程不抛出异常)
|
|
*/
|
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
|
let channel = FlutterMethodChannel(name: "native_plugin", binaryMessenger: registrar.messenger())
|
|
let instance = FloatingUiPlugin()
|
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
|
|
|
let factory = FLNativeViewFactory(messenger: registrar.messenger())
|
|
registrar.register(factory, withId: "native_view")
|
|
}
|
|
|
|
/**
|
|
* Flutter MethodChannel 调用处理入口
|
|
* - Parameters:
|
|
* - call: Flutter 发起的方法调用(方法名与参数)
|
|
* - result: 用于向 Flutter 回传结果的回调
|
|
* - Returns: 无
|
|
* - Throws: 无(内部逻辑不抛出异常,异常通过 result 或日志处理)
|
|
*/
|
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
switch call.method {
|
|
case "getPlatformVersion":
|
|
result("iOS " + UIDevice.current.systemVersion)
|
|
|
|
case "createPipContentView":
|
|
Self.updateAudioSession()
|
|
let playerView = PlayerView()
|
|
// 这里未来可以根据 PiP 的事件来控制播放状态
|
|
playerView.play()
|
|
pipContentViewArray.append(playerView)
|
|
let ptr = Unmanaged.passUnretained(playerView).toOpaque()
|
|
let viewId = UInt64(UInt(bitPattern: ptr))
|
|
result(NSNumber(value: viewId))
|
|
|
|
case "disposePipContentView":
|
|
guard let viewIdNum = call.arguments as? NSNumber else {
|
|
result(nil)
|
|
return
|
|
}
|
|
let targetId = viewIdNum.uint64Value
|
|
for (idx, playerView) in pipContentViewArray.enumerated() {
|
|
let currentId = UInt64(UInt(bitPattern: Unmanaged.passUnretained(playerView).toOpaque()))
|
|
if currentId == targetId {
|
|
playerView.pause()
|
|
pipContentViewArray.remove(at: idx)
|
|
break
|
|
}
|
|
}
|
|
result(nil)
|
|
|
|
case "updatePipContentView":
|
|
guard let data = call.arguments as? [String: Any] else {
|
|
result(nil)
|
|
return
|
|
}
|
|
let lang = data["lang"] as? String
|
|
let cn = data["cn"] as? String
|
|
let en = data["en"] as? String
|
|
let viewIdNum = data["viewId"] as? NSNumber
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self = self else { return }
|
|
if let viewIdNum = viewIdNum {
|
|
let targetId = viewIdNum.uint64Value
|
|
for playerView in self.pipContentViewArray {
|
|
let currentId = UInt64(UInt(bitPattern: Unmanaged.passUnretained(playerView).toOpaque()))
|
|
if currentId == targetId {
|
|
playerView.update(withLang: lang, cn: cn, en: en)
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
if let playerView = self.pipContentViewArray.last {
|
|
playerView.update(withLang: lang, cn: cn, en: en)
|
|
}
|
|
}
|
|
}
|
|
result(nil)
|
|
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
}
|
|
|