From 3bf7ade8b40956fa997eba5a2e396cde409a15d5 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 22 Aug 2025 21:16:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AE=9A=E4=BD=8D=E5=92=8Cqq?= =?UTF-8?q?=E9=9F=B3=E4=B9=90=E7=8A=B6=E6=80=81=E6=A3=80=E6=B5=8B=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QQMusicSystemFunctionHandler.swift | 82 +++++---- .../location_service/LocationService.swift | 45 ++++- .../Sources/qq_music/QPlayAutoManager.swift | 155 ++++++++++++------ 3 files changed, 192 insertions(+), 90 deletions(-) diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift index 2e492fb3b..001176fe5 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift @@ -131,39 +131,63 @@ private class QQMusicSearchHandler: FunctionHandler { } return await withCheckedContinuation { continuation in - // 直接调用QPlayAutoManager的搜索接口 - QPlayAutoManager.shared.search(keyword: keyword, searchType: .song) { items in - var result: [String: Any] - - if let items = items, !items.isEmpty { - let songs = items.map { item -> [String: Any] in - return [ - "mid": item.mid ?? "", - "name": item.name ?? "", - "artist": item.singer?.name ?? "", - "album": item.album ?? "", - "duration": item.duration, - "pic": item.coverUri ?? "" + // 调用更新后的搜索接口,包含错误处理 + QPlayAutoManager.shared.search( + keyword: keyword, + searchType: .song, + completion: { items in + var result: [String: Any] + + if let items = items, !items.isEmpty { + let songs = items.map { item -> [String: Any] in + return [ + "mid": item.mid ?? "", + "name": item.name ?? "", + "artist": item.singer?.name ?? "", + "album": item.album ?? "", + "duration": item.duration, + "pic": item.coverUri ?? "" + ] + } + result = [ + "success": true, + "message": "Found \(items.count) songs", + "broadcast": false, + "data": songs ] + } else { + result = ["success": false, "error": "No songs found"] } - result = [ - "success": true, - "message": "Found \(items.count) songs and started playing", - "broadcast":false, - "data": songs + + do { + let jsonData = try JSONSerialization.data(withJSONObject: result) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" + continuation.resume(returning: jsonString) + } catch { + continuation.resume(returning: "{\"success\": false, \"error\": \"JSON serialization failed\"}") + } + }, + onError: { error in + // 处理搜索错误 + var errorMessage = "Search failed" + if let error = error { + errorMessage = "Search failed: \(error.localizedDescription)" + } + + let result: [String: Any] = [ + "success": false, + "error": errorMessage ] - } else { - result = ["success": false, "error": "No songs found"] - } - - do { - let jsonData = try JSONSerialization.data(withJSONObject: result) - let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" - continuation.resume(returning: jsonString) - } catch { - continuation.resume(returning: "{\"success\": false, \"error\": \"JSON serialization failed\"}") + + do { + let jsonData = try JSONSerialization.data(withJSONObject: result) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" + continuation.resume(returning: jsonString) + } catch { + continuation.resume(returning: "{\"success\": false, \"error\": \"Search failed and JSON serialization failed\"}") + } } - } + ) } } } diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift index a626a1e37..b55764459 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift @@ -195,8 +195,15 @@ public class LocationService: NSObject { // 检查是否已有请求在进行 if self.currentLocationCallback != nil { - completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "位置请求正在进行中"]))) - return + print("[LocationService] 检测到重复请求,重置之前的请求") + self.resetLocationRequest() + } + + // 实时检查权限状态 + let currentStatus = CLLocationManager.authorizationStatus() + if currentStatus != self.authorizationStatus { + print("[LocationService] 权限状态已变更,更新缓存") + self.authorizationStatus = currentStatus } // 检查权限 @@ -205,6 +212,12 @@ public class LocationService: NSObject { return } + // 检查位置服务是否启用 + if !CLLocationManager.locationServicesEnabled() { + completion(.failure(NSError(domain: "LocationService", code: -6, userInfo: [NSLocalizedDescriptionKey: "位置服务未启用"]))) + return + } + // 设置回调 self.currentLocationCallback = { result in switch result { @@ -228,7 +241,15 @@ public class LocationService: NSObject { } } - print("[LocationService] 开始正常定位") + // 设置超时定时器 + self.locationTimeoutTimer = Timer.scheduledTimer(withTimeInterval: self.locationTimeout, repeats: false) { [weak self] _ in + print("[LocationService] 位置请求超时") + let callback = self?.currentLocationCallback + self?.resetLocationRequest() + callback?(.failure(NSError(domain: "LocationService", code: -8, userInfo: [NSLocalizedDescriptionKey: "位置请求超时"]))) + } + + print("[LocationService] 开始正常定位,超时时间: \(self.locationTimeout)秒") // 使用单次定位 locationManager.requestLocation() } @@ -253,7 +274,7 @@ public class LocationService: NSObject { ) let callback = currentLocationCallback - currentLocationCallback = nil + resetLocationRequest() // 使用重置方法 callback?(.success(locationInfo)) } @@ -266,6 +287,18 @@ public class LocationService: NSObject { "timestamp": locationData.timestamp ] } + + // 添加超时定时器 + private var locationTimeoutTimer: Timer? + private let locationTimeout: TimeInterval = 15.0 // 15秒超时 + + // 添加重置方法 + private func resetLocationRequest() { + print("[LocationService] 重置位置请求状态") + currentLocationCallback = nil + locationTimeoutTimer?.invalidate() + locationTimeoutTimer = nil + } } // MARK: - CLLocationManagerDelegate @@ -304,7 +337,7 @@ extension LocationService: CLLocationManagerDelegate { print("[LocationService] 位置获取失败: \(error.localizedDescription)") let callback = currentLocationCallback - currentLocationCallback = nil + resetLocationRequest() // 使用重置方法 callback?(.failure(error)) } @@ -356,5 +389,3 @@ extension LocationService: CLLocationManagerDelegate { } } -// Remove the duplicate LocationData definition from here -// The LocationData struct is already defined in LocationData.swift diff --git a/local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift b/local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift index 04998a22c..f78bdf4c3 100644 --- a/local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift +++ b/local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift @@ -316,7 +316,27 @@ import QPlayAutoSDK } // MARK: - 登录状态管理 - // 在第206行之前添加新的方法 + // Add the error enum at the top of the class + enum QPlayAutoError: Error { + case qqMusicNotInstalled + case loginFailed + case connectionFailed + case searchFailed(String) // 新增搜索失败错误 + + var localizedDescription: String { + switch self { + case .qqMusicNotInstalled: + return "QQ音乐未安装" + case .loginFailed: + return "登录失败" + case .connectionFailed: + return "连接失败" + case .searchFailed(let reason): + return "搜索失败: \(reason)" + } + } + } + private func ensureQQMusicInstalledAndLoginAndExecute( _ operation: @escaping () -> Void, onQQMusicNotInstalled: (() -> Void)? = nil, @@ -330,15 +350,37 @@ import QPlayAutoSDK } // QQ音乐已安装,继续检查登录状态 - ensureLoginAndExecute(operation, onLoginFailed: onLoginFailed) + ensureLoginAndExecute(operation) { error in + switch error { + case .loginFailed: + onLoginFailed?() + case .qqMusicNotInstalled: + // This shouldn't happen since we already checked above + onQQMusicNotInstalled?() + default: + onLoginFailed?() + } + } } - // 修改现有的ensureLoginAndExecute方法(第206行开始) - private func ensureLoginAndExecute(_ operation: @escaping () -> Void, onLoginFailed: (() -> Void)? = nil) { + // 修改现有的ensureLoginAndExecute方法 + private func ensureLoginAndExecute( + _ operation: @escaping () -> Void, + onFailure: ((QPlayAutoError) -> Void)? = nil + ) { + // 首先检查QQ音乐是否已安装 + guard isQQMusicInstalled else { + print("QPlayAutoManager: QQ音乐未安装,无法执行操作") + onFailure?(.qqMusicNotInstalled) + return + } + if isLoginOK { // 已登录,直接执行操作 operation() } else { + + // 未登录,先登录 print("QPlayAutoManager: 未登录,正在尝试登录...") performLogin { [weak self] success in @@ -347,7 +389,7 @@ import QPlayAutoSDK operation() } else { print("QPlayAutoManager: 登录失败,无法执行操作") - onLoginFailed?() + onFailure?(.loginFailed) } } } @@ -533,33 +575,34 @@ import QPlayAutoSDK } @objc public func pause() { - ensureLoginAndExecute { + ensureLoginAndExecute({ QPlayAutoSDK.playerPlayPause() - } onLoginFailed: { - print("QPlayAutoManager: 暂停失败 - 登录失败") - } + }, onFailure: { error in + print("QPlayAutoManager: 暂停失败 - \(error)") + }) } + // Fix seek method (lines 571-578) @objc public func seek(to position: TimeInterval) { - ensureLoginAndExecute { - QPlayAutoSDK.playerSeek(Int(position)) - } onLoginFailed: { - print("QPlayAutoManager: 跳转播放位置失败 - 登录失败") - } + ensureLoginAndExecute { + QPlayAutoSDK.playerSeek(Int(position)) + } onFailure: { error in + print("QPlayAutoManager: 跳转播放位置失败 - \(error)") + } } // MARK: - 扩展功能方法(需要添加登录检查) /// 设置播放模式 @objc public func setPlayMode(_ playMode: QPlayAutoPlayMode, completion: @escaping (Bool) -> Void) { - ensureLoginAndExecute { - QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in - completion(success) - } - } onLoginFailed: { - print("QPlayAutoManager: 设置播放模式失败 - 登录失败") - completion(false) - } + ensureLoginAndExecute { + QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in + completion(success) + } + } onFailure: { error in + print("QPlayAutoManager: 设置播放模式失败 - \(error)") + completion(false) + } } /// 收藏/取消收藏歌曲 @@ -568,11 +611,12 @@ import QPlayAutoSDK QPlayAutoSDK.setFavoriteStateWithSong(song, isFavorite: isFavorite) { errorCode in completion(errorCode == 0) } - } onLoginFailed: { - print("QPlayAutoManager: 设置收藏状态失败 - 登录失败") - completion(false) + }onFailure: { error in + print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)") + completion(false) } - } + } + /// 获取相似歌曲 @objc public func requestSimilarSongs(song: QPlayAutoListItem, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) { @@ -585,12 +629,12 @@ import QPlayAutoSDK completion(nil, error) } } - } onLoginFailed: { - print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败") - let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"]) + }onFailure: { error in + print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)") completion(nil, error) - } - } + } + } + /// 获取歌词 @objc public func requestLyric(song: QPlayAutoListItem, completion: @escaping (QPlayAutoLyric?, Error?) -> Void) { @@ -603,12 +647,11 @@ import QPlayAutoSDK completion(nil, error) } } - } onLoginFailed: { - print("QPlayAutoManager: 请求歌词失败 - 登录失败") - let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"]) + } onFailure: { error in + print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)") completion(nil, error) - } - } + } + } /// 获取歌曲列表 @objc public func requestSonglist(type: QPlayAutoSongListType, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) { @@ -621,12 +664,11 @@ import QPlayAutoSDK completion(nil, error) } } - } onLoginFailed: { - print("QPlayAutoManager: 请求歌单失败 - 登录失败") - let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"]) + } onFailure: { error in + print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)") completion(nil, error) - } - } + } + } /// 播放指定歌曲列表 @objc public func playAtIndex(songList: [QPlayAutoListItem], playIndex: Int, completion: @escaping (Bool) -> Void) { @@ -634,11 +676,11 @@ import QPlayAutoSDK QPlayAutoSDK.play(atIndex: songList, play: playIndex) { errorCode in completion(errorCode == 0) } - } onLoginFailed: { - print("QPlayAutoManager: 播放指定索引歌曲失败 - QQ音乐未安装") + } onFailure: { error in + print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)") completion(false) - } - } + } + } // MARK: - 状态查询 @objc public var isConnected: Bool { @@ -729,17 +771,21 @@ import QPlayAutoSDK @objc public func search( keyword: String, searchType: QPlayAutoSearchType, - completion: @escaping ([QPlayAutoListItem]?) -> Void + completion: @escaping ([QPlayAutoListItem]?) -> Void, + onError: ((Error?) -> Void)? = nil ) { ensureLoginAndExecute { QPlayAutoSDK.search(keyword, type: searchType, firstPage: true) { (error, items) in completion(items) } - } onLoginFailed: { - print("QPlayAutoManager: 搜索失败 - 登录失败") - completion(nil) + }onFailure: { error in + print("QPlayAutoManager: 搜索失败 - 登录失败 - \(error)") + let loginError = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [ + NSLocalizedDescriptionKey: "登录失败: \(error)" + ]) + onError?(loginError) + } } - } @objc public func getItems( playlistId: String, @@ -756,11 +802,12 @@ import QPlayAutoSDK completion(nil) } } - } onLoginFailed: { - print("QPlayAutoManager: 获取播放列表失败 - 登录失败") + } onFailure: { error in + print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)") completion(nil) - } - } + } + } + // 添加公开的登录方法 @objc public func login(completion: @escaping (Bool) -> Void) {