import Foundation import ImageCaptureCore /// 自动识别 USB 相机品牌并委托给对应品牌服务。 @MainActor final class AutoDetectCameraService: NSObject, CameraServiceProtocol { private(set) var brand: CameraBrand = .sony private(set) var connectionState: CameraConnectionState = .disconnected var onConnectionStateChange: ((CameraConnectionState) -> Void)? var onNewAsset: ((CameraAsset) -> Void)? private var activeService: (any CameraServiceProtocol)? private var pendingCamera: ICCameraDevice? private let usbBrowser = SharedUSBCameraBrowserHub.controller private var isConnectingCamera = false private var isDisconnecting = false override init() { super.init() bindUSBBrowserCallbacks() } func connect() async { CameraTetheringLogger.log("AutoDetectCameraService.connect()") if let activeService { CameraTetheringLogger.log("AutoDetect: 已有活动服务,复用连接") connectionState = activeService.connectionState rebindActiveServiceCallbacks() flushPendingAssetsIfNeeded(on: activeService) onConnectionStateChange?(connectionState) return } isConnectingCamera = false connectionState = .searching onConnectionStateChange?(.searching) let started = await usbBrowser.start() guard started else { connectionState = .error("权限被拒绝") onConnectionStateChange?(.error("权限被拒绝")) return } } /// 页面离开:清空 UI 回调,保持 ICC/PTP 会话与 Browser。 func detachFromUI() { CameraTetheringLogger.log("AutoDetectCameraService.detachFromUI()") onConnectionStateChange = nil onNewAsset = nil activeService?.detachFromUI() } func shutdown() async { guard !isDisconnecting else { return } isDisconnecting = true defer { isDisconnecting = false } CameraTetheringLogger.log("AutoDetectCameraService.shutdown()") isConnectingCamera = false if let activeService { await activeService.shutdown() } activeService = nil pendingCamera = nil usbBrowser.stop() USBCameraReconnectHint.markDisconnected() try? await Task.sleep(nanoseconds: 300_000_000) connectionState = .disconnected onConnectionStateChange?(.disconnected) } /// 重新搜索 USB 相机(未连接时可手动触发,或 App 回到前台时调用)。 func restartDiscovery() async { CameraTetheringLogger.log("AutoDetectCameraService.restartDiscovery()") guard activeService == nil else { CameraTetheringLogger.log("AutoDetect: 已连接,跳过 restartDiscovery") return } isConnectingCamera = false connectionState = .searching onConnectionStateChange?(.searching) await usbBrowser.restart() } func listAssets() async throws -> [CameraAsset] { guard let activeService else { throw CameraServiceError.notConnected } return try await activeService.listAssets() } func downloadAsset(_ asset: CameraAsset) async throws -> URL { guard let activeService else { throw CameraServiceError.notConnected } return try await activeService.downloadAsset(asset) } private func bindUSBBrowserCallbacks() { usbBrowser.onCameraDiscovered = { [weak self] camera, source in Task { @MainActor in await self?.handleCameraDiscovered(camera, source: source) } } usbBrowser.onCameraRemoved = { [weak self] device in Task { @MainActor in await self?.handleCameraRemoved(device) } } usbBrowser.onAuthorizationDenied = { [weak self] in Task { @MainActor in guard let self else { return } self.connectionState = .error("权限被拒绝") self.onConnectionStateChange?(.error("权限被拒绝")) } } usbBrowser.onScanTimeout = { [weak self] in Task { @MainActor in guard let self, self.activeService == nil, !self.isConnectingCamera else { return } let message = "未发现 USB 相机。请确认:① 相机 USB 设为「照片传输/电脑遥控」② 存储卡内至少有 1 张照片 ③ 先连手机再连相机 ④ 进入页面后重新插拔 USB 线" self.connectionState = .error(message) self.onConnectionStateChange?(.error(message)) } } } private func handleCameraDiscovered(_ camera: ICCameraDevice, source: String) async { guard activeService == nil, !isConnectingCamera, !isDisconnecting else { CameraTetheringLogger.log("AutoDetect: 忽略重复发现 (\(source))") return } let vendorID = Int(camera.usbVendorID) guard let detectedBrand = CameraBrand.detect(from: camera) else { let message = "不支持的相机品牌 (Vendor ID: 0x\(String(vendorID, radix: 16)))" CameraTetheringLogger.log(message) connectionState = .error(message) onConnectionStateChange?(.error(message)) return } isConnectingCamera = true await activateService(for: camera, brand: detectedBrand) isConnectingCamera = false } private func handleCameraRemoved(_ device: ICDevice) async { guard !isDisconnecting else { return } guard activeService != nil else { return } guard device.uuidString == pendingCamera?.uuidString else { return } CameraTetheringLogger.log("AutoDetect: 相机已物理移除 \(device.name ?? "?")") pendingCamera = nil if let activeService { await activeService.shutdown() } activeService = nil usbBrowser.resetNotifiedDevices() connectionState = .disconnected onConnectionStateChange?(.disconnected) } private func activateService(for camera: ICCameraDevice, brand detectedBrand: CameraBrand) async { brand = detectedBrand let service = CameraServiceFactory.make(brand: detectedBrand) activeService = service pendingCamera = camera rebindActiveServiceCallbacks() connectionState = .connecting onConnectionStateChange?(.connecting) CameraTetheringLogger.log("AutoDetect: 挂载 \(detectedBrand.displayName) 服务 (source attach)…") switch detectedBrand { case .sony: if let sony = service as? SonyCameraService { await sony.attach(to: camera) } case .canon: if let canon = service as? CanonCameraService { await canon.attach(to: camera) } case .nikon: connectionState = .error("Nikon 相机尚未实现") onConnectionStateChange?(.error("Nikon 相机尚未实现")) } } private func rebindActiveServiceCallbacks() { guard let activeService else { return } activeService.onConnectionStateChange = { [weak self] state in Task { @MainActor in self?.connectionState = state self?.onConnectionStateChange?(state) } } activeService.onNewAsset = { [weak self] asset in self?.onNewAsset?(asset) } } private func flushPendingAssetsIfNeeded(on service: any CameraServiceProtocol) { if let sony = service as? SonyCameraService { sony.reattachToUI() } else if let canon = service as? CanonCameraService { canon.reattachToUI() } } }