Browse Source

Merge commit '70eddaaa7d8496099c5120360ed74672aece111e' into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
a648a7c336
  1. 7
      lib/modules/agent/controllers/agent_controller.dart
  2. 291
      lib/modules/agent/views/agent_view.dart
  3. 6
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  4. 330
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift
  5. 178
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  6. 82
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
  7. 39
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt
  8. 45
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift
  9. 155
      local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift

7
lib/modules/agent/controllers/agent_controller.dart

@ -507,9 +507,10 @@ class AgentController extends GetxController {
} else { } else {
// 只有在非按住说话模式且非打电话模式下才自动切换到文字模式 // 只有在非按住说话模式且非打电话模式下才自动切换到文字模式
// 按住说话模式下应该保持在语音模式,等待下次按住 // 按住说话模式下应该保持在语音模式,等待下次按住
if (!isPhoneCallMode.value) { // if (!isPhoneCallMode.value && !isPushToTalkActive.value) {
isTextInputMode.value = true; // Logger.i(TAG, '切换文本输入模式');
} // isTextInputMode.value = false;
// }
} }
Logger.i(TAG, '识别停止'); Logger.i(TAG, '识别停止');

291
lib/modules/agent/views/agent_view.dart

@ -196,7 +196,17 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
IntroStepBuilder( IntroStepBuilder(
group: 'ai', group: 'ai',
order: 3, order: 3,
getOverlayPosition: IntroOverlayBuilder.overlayPosition, getOverlayPosition: ({
required Offset offset,
required Size screenSize,
required Size size,
}) {
return OverlayPosition(
top: 200.h,
width: screenSize.width,
crossAxisAlignment: CrossAxisAlignment.end,
);
},
overlayBuilder: (params) { overlayBuilder: (params) {
return Obx( return Obx(
() => IntroOverlayBuilder.homeBuilder( () => IntroOverlayBuilder.homeBuilder(
@ -242,8 +252,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
? IntroStepBuilder( ? IntroStepBuilder(
group: 'ai', group: 'ai',
order: 4, order: 4,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) { overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder( return IntroOverlayBuilder.aiBuilder(
params, params,
@ -503,8 +511,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return IntroStepBuilder( return IntroStepBuilder(
group: 'ai', group: 'ai',
order: 1, order: 1,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) { overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder( return IntroOverlayBuilder.aiBuilder(
params, params,
@ -667,19 +673,30 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
: Colors.blue[100]) : Colors.blue[100])
: (isDarkMode : (isDarkMode
? Colors.grey[800] ? Colors.grey[800]
: Colors.grey[200]), : const Color.fromARGB(
255, 255, 255, 255)),
borderRadius: borderRadius:
BorderRadius.circular(22.w), BorderRadius.circular(22.w),
border: Border.all( boxShadow: [
color: isPushToTalkActive BoxShadow(
? Colors.blue color:
: (isDarkMode Colors.black.withOpacity(0.2),
? Colors.grey[800]! spreadRadius: 1,
: Colors.grey blurRadius: 2,
.withOpacity(0.1)), offset: const Offset(0, 1),
width: ),
isPushToTalkActive ? 2.w : 1.w, ],
), // border: Border.all(
// color: isPushToTalkActive
// ? Colors.blue
// : (isDarkMode
// ? Colors.grey[800]!
// : const Color.fromARGB(
// 255, 255, 255, 255)
// .withOpacity(0.1)),
// width:
// isPushToTalkActive ? 2.w : 1.w,
// ),
), ),
child: Row( child: Row(
children: [ children: [
@ -739,143 +756,149 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(width: 8.w), SizedBox(width: 8.w),
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮 // 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮
IntroStepBuilder( Builder(builder: (context) {
group: 'ai', StepWidgetParams? widgetParams;
order: 2, return IntroStepBuilder(
getOverlayPosition: group: 'ai',
IntroOverlayBuilder.overlayPosition, order: 2,
overlayBuilder: (params) { onHighlightWidgetTap: () async {
return IntroOverlayBuilder.aiBuilder( await controller
params, .noviceGuideService.aiStepsList[
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}', controller.noviceGuideService.aiStep]
alignment: false, ['method']();
onTap: () async { widgetParams!.onNext!();
await controller controller.sendMessage();
.noviceGuideService.aiStepsList[ },
controller.noviceGuideService overlayBuilder: (params) {
.aiStep]['method'](); widgetParams = params;
params.onNext!(); return IntroOverlayBuilder.aiBuilder(
controller.sendMessage(); params,
}, '点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}',
); alignment: false,
}, );
builder: (BuildContext context, GlobalKey key) => },
Obx( builder:
key: key, (BuildContext context, GlobalKey key) =>
() { Obx(
final hasText = controller.hasTextInput.value; key: key,
final hasImage = () {
controller.isImageInputActive.value; final hasText =
final shouldShowSend = hasText || hasImage; controller.hasTextInput.value;
final shouldShowExpand = final hasImage =
controller.shouldShowExpandButton.value; controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage;
final shouldShowExpand =
controller.shouldShowExpandButton.value;
if (shouldShowSend) { if (shouldShowSend) {
// 显示发送按钮(可能还有展开按钮) // 显示发送按钮(可能还有展开按钮)
return Column( return Column(
children: [ children: [
// 展开按钮(在发送按钮上方) // 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText) if (shouldShowExpand && hasText)
Padding( Padding(
padding: padding:
EdgeInsets.only(bottom: 40.h), EdgeInsets.only(bottom: 40.h),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
// 打开底部弹出编辑模式 // 打开底部弹出编辑模式
controller.openFullScreenEdit();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor:
Colors.transparent,
builder: (context) =>
const FullScreenEditor(),
).then((_) {
// 弹出页关闭时的回调
controller controller
.closeFullScreenEdit(); .openFullScreenEdit();
}); showModalBottomSheet(
}, context: context,
child: Container( isScrollControlled: true,
width: 32.w, backgroundColor:
height: 32.w, Colors.transparent,
decoration: BoxDecoration( builder: (context) =>
color: isDarkMode const FullScreenEditor(),
? Colors.grey[700] ).then((_) {
: Colors.grey[300], // 弹出页关闭时的回调
borderRadius: controller
BorderRadius.circular( .closeFullScreenEdit();
16.w), });
), },
child: Icon( child: Container(
Icons.expand_less, width: 32.w,
color: isDarkMode height: 32.w,
? Colors.grey[300] decoration: BoxDecoration(
: Colors.grey[700], color: isDarkMode
size: 20.sp, ? Colors.grey[700]
: Colors.grey[300],
borderRadius:
BorderRadius.circular(
16.w),
),
child: Icon(
Icons.expand_less,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 20.sp,
),
), ),
), ),
), ),
// 发送按钮
GestureDetector(
onTap: () =>
controller.sendMessage(),
child: Container(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: primaryColor,
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
Icons.send,
color: Colors.white,
size: 22.sp,
),
),
), ),
// 发送按钮 ],
GestureDetector( );
onTap: () => controller.sendMessage(), } else {
// 显示'+'选择按钮
return Obx(() {
final isExpanded = controller
.isPlusOptionsExpanded.value;
return GestureDetector(
onTap: () {
controller.togglePlusOptions();
},
child: Container( child: Container(
width: 44.w, width: 44.w,
height: 44.w, height: 44.w,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
vertical: 2.w), vertical: 2.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor, color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius: borderRadius:
BorderRadius.circular(22.w), BorderRadius.circular(22.w),
), ),
child: Icon( child: Icon(
Icons.send, isExpanded
color: Colors.white, ? Icons.close
: Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22.sp, size: 22.sp,
), ),
), ),
), );
], });
); }
} else { },
// 显示'+'选择按钮 ),
return Obx(() { );
final isExpanded = controller }),
.isPlusOptionsExpanded.value;
return GestureDetector(
onTap: () {
controller.togglePlusOptions();
},
child: Container(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
isExpanded
? Icons.close
: Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22.sp,
),
),
);
});
}
},
),
),
], ],
), ),

6
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -375,9 +375,9 @@ class AgentServiceImpl: NSObject {
let audioSourceType: AzureAsrHelper.AudioSourceType = useBle ? .external : .microphone let audioSourceType: AzureAsrHelper.AudioSourceType = useBle ? .external : .microphone
if useBle { if useBle {
AudioSessionHub.shared.begin(.playback) // AudioSessionHub.shared.begin(.playback)
} else { } else {
AudioSessionHub.shared.begin(.voice) //AudioSessionHub.shared.begin(.voice)
} }
print("ai启动语音=\(audioSourceType)=\(useBle)") print("ai启动语音=\(audioSourceType)=\(useBle)")
guard let success = azureAsrHelper?.startContinuousRecognition( guard let success = azureAsrHelper?.startContinuousRecognition(
@ -1487,7 +1487,7 @@ class AudioPlayer {
private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated) private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated)
// 等待音效超时时间(秒)- 改为 var 以支持动态修改 // 等待音效超时时间(秒)- 改为 var 以支持动态修改
private var awaitTimeoutSeconds: TimeInterval = 10.0 private var awaitTimeoutSeconds: TimeInterval = 30.0
private var resourcesBundle: Bundle { private var resourcesBundle: Bundle {
#if SWIFT_PACKAGE #if SWIFT_PACKAGE

330
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift

@ -28,196 +28,196 @@ public class AudioSessionHub {
_ = shared // 触发单例初始化 _ = shared // 触发单例初始化
} }
public func begin(_ intent: AudioIntent) { // public func begin(_ intent: AudioIntent) {
if intent == currentIntent && audioSession.isOtherAudioPlaying == false { // if intent == currentIntent && audioSession.isOtherAudioPlaying == false {
os_log("音频意图未变化且无其他音频播放,跳过设置: %{public}@", log: logger, type: .debug, String(describing: intent)) // os_log("音频意图未变化且无其他音频播放,跳过设置: %{public}@", log: logger, type: .debug, String(describing: intent))
return // return
} // }
os_log("开始设置音频会话,意图: %{public}@", log: logger, type: .info, String(describing: intent)) // os_log("开始设置音频会话,意图: %{public}@", log: logger, type: .info, String(describing: intent))
currentIntent = intent // currentIntent = intent
do { // do {
switch intent { // switch intent {
case .voice: // case .voice:
os_log("设置语音模式: category=playAndRecord, mode=voiceChat", log: logger, type: .info) // os_log("设置语音模式: category=playAndRecord, mode=voiceChat", log: logger, type: .info)
// 第一步:设置音频类别和模式 // // 第一步:设置音频类别和模式
try audioSession.setCategory(.playAndRecord, // try audioSession.setCategory(.playAndRecord,
mode: .voiceChat, // mode: .voiceChat,
options: [.allowBluetooth, .defaultToSpeaker, .mixWithOthers]) // options: [.allowBluetooth, .defaultToSpeaker, .mixWithOthers])
os_log("✓ 音频类别设置成功: playAndRecord + voiceChat", log: logger, type: .info) // os_log("✓ 音频类别设置成功: playAndRecord + voiceChat", log: logger, type: .info)
// 第二步:检查并设置蓝牙输入 // // 第二步:检查并设置蓝牙输入
os_log("检查可用输入设备...", log: logger, type: .info) // os_log("检查可用输入设备...", log: logger, type: .info)
if let availableInputs = audioSession.availableInputs { // if let availableInputs = audioSession.availableInputs {
os_log("可用输入设备数量: %d", log: logger, type: .info, availableInputs.count) // os_log("可用输入设备数量: %d", log: logger, type: .info, availableInputs.count)
for (index, input) in availableInputs.enumerated() { // for (index, input) in availableInputs.enumerated() {
os_log("输入设备[%d]: %{public}@ (%{public}@)", log: logger, type: .info, // os_log("输入设备[%d]: %{public}@ (%{public}@)", log: logger, type: .info,
index, input.portName, input.portType.rawValue) // index, input.portName, input.portType.rawValue)
} // }
// 查找蓝牙HFP设备 // // 查找蓝牙HFP设备
if let bluetoothInput = availableInputs.first(where: { $0.portType == .bluetoothHFP }) { // if let bluetoothInput = availableInputs.first(where: { $0.portType == .bluetoothHFP }) {
os_log("找到蓝牙HFP设备,尝试设置为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName) // os_log("找到蓝牙HFP设备,尝试设置为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName)
try audioSession.setPreferredInput(bluetoothInput) // try audioSession.setPreferredInput(bluetoothInput)
os_log("✓ 蓝牙HFP输入设置成功: %{public}@", log: logger, type: .info, bluetoothInput.portName) // os_log("✓ 蓝牙HFP输入设置成功: %{public}@", log: logger, type: .info, bluetoothInput.portName)
} else { // } else {
os_log("未找到蓝牙HFP设备", log: logger, type: .info) // os_log("未找到蓝牙HFP设备", log: logger, type: .info)
} // }
} else { // } else {
os_log("⚠️ availableInputs 为 nil", log: logger, type: .default) // os_log("⚠️ availableInputs 为 nil", log: logger, type: .default)
} // }
case .playback: // case .playback:
os_log("设置播放模式: category=playback, mode=default", log: logger, type: .info) // os_log("设置播放模式: category=playback, mode=default", log: logger, type: .info)
// 检查当前音频会话状态 // // 检查当前音频会话状态
os_log("设置前状态检查:", log: logger, type: .info) // os_log("设置前状态检查:", log: logger, type: .info)
os_log(" - 当前类别: %{public}@", log: logger, type: .info, audioSession.category.rawValue) // os_log(" - 当前类别: %{public}@", log: logger, type: .info, audioSession.category.rawValue)
os_log(" - 当前模式: %{public}@", log: logger, type: .info, audioSession.mode.rawValue) // os_log(" - 当前模式: %{public}@", log: logger, type: .info, audioSession.mode.rawValue)
os_log(" - 当前是否激活: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否") // os_log(" - 当前是否激活: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否")
os_log(" - 其他音频播放: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否") // os_log(" - 其他音频播放: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否")
// 如果当前已经激活,先尝试停用 // // 如果当前已经激活,先尝试停用
do { // do {
if audioSession.isOtherAudioPlaying { // if audioSession.isOtherAudioPlaying {
os_log("检测到其他音频正在播放,先尝试停用当前会话", log: logger, type: .info) // os_log("检测到其他音频正在播放,先尝试停用当前会话", log: logger, type: .info)
try audioSession.setActive(false, options: .notifyOthersOnDeactivation) // try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
os_log("✓ 成功停用当前音频会话", log: logger, type: .info) // os_log("✓ 成功停用当前音频会话", log: logger, type: .info)
} // }
} catch let deactivateError as NSError { // } catch let deactivateError as NSError {
os_log("⚠️ 停用当前音频会话失败: %{public}@ (继续尝试设置)", log: logger, type: .default, deactivateError.localizedDescription) // os_log("⚠️ 停用当前音频会话失败: %{public}@ (继续尝试设置)", log: logger, type: .default, deactivateError.localizedDescription)
} // }
// 尝试渐进式设置:先尝试最简单的设置 // // 尝试渐进式设置:先尝试最简单的设置
var settingSuccess = false // var settingSuccess = false
// 策略1: 只设置类别,不设置选项 // // 策略1: 只设置类别,不设置选项
if !settingSuccess { // if !settingSuccess {
do { // do {
os_log("尝试策略1: 仅设置类别 (playback)", log: logger, type: .info) // os_log("尝试策略1: 仅设置类别 (playback)", log: logger, type: .info)
try audioSession.setCategory(.playback) // try audioSession.setCategory(.playback)
os_log("✓ 策略1成功: 音频类别设置为 playback", log: logger, type: .info) // os_log("✓ 策略1成功: 音频类别设置为 playback", log: logger, type: .info)
settingSuccess = true // settingSuccess = true
} catch let error as NSError { // } catch let error as NSError {
os_log("策略1失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code) // os_log("策略1失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
} // }
} // }
// 策略2: 设置类别和模式,不设置选项 // // 策略2: 设置类别和模式,不设置选项
if !settingSuccess { // if !settingSuccess {
do { // do {
os_log("尝试策略2: 设置类别和模式 (playback + default)", log: logger, type: .info) // os_log("尝试策略2: 设置类别和模式 (playback + default)", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default) // try audioSession.setCategory(.playback, mode: .default)
os_log("✓ 策略2成功: 音频类别和模式设置完成", log: logger, type: .info) // os_log("✓ 策略2成功: 音频类别和模式设置完成", log: logger, type: .info)
settingSuccess = true // settingSuccess = true
} catch let error as NSError { // } catch let error as NSError {
os_log("策略2失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code) // os_log("策略2失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
} // }
} // }
// 策略3: 设置类别、模式和单个选项 // // 策略3: 设置类别、模式和单个选项
if !settingSuccess { // if !settingSuccess {
do { // do {
os_log("尝试策略3: 设置类别、模式和混音选项", log: logger, type: .info) // os_log("尝试策略3: 设置类别、模式和混音选项", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) // try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
os_log("✓ 策略3成功: 音频类别、模式和混音选项设置完成", log: logger, type: .info) // os_log("✓ 策略3成功: 音频类别、模式和混音选项设置完成", log: logger, type: .info)
settingSuccess = true // settingSuccess = true
} catch let error as NSError { // } catch let error as NSError {
os_log("策略3失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code) // os_log("策略3失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
} // }
} // }
// 策略4: 设置类别、模式和蓝牙选项 // // 策略4: 设置类别、模式和蓝牙选项
if !settingSuccess { // if !settingSuccess {
do { // do {
os_log("尝试策略4: 设置类别、模式和蓝牙选项", log: logger, type: .info) // os_log("尝试策略4: 设置类别、模式和蓝牙选项", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default, options: [.allowBluetoothA2DP]) // try audioSession.setCategory(.playback, mode: .default, options: [.allowBluetoothA2DP])
os_log("✓ 策略4成功: 音频类别、模式和蓝牙选项设置完成", log: logger, type: .info) // os_log("✓ 策略4成功: 音频类别、模式和蓝牙选项设置完成", log: logger, type: .info)
settingSuccess = true // settingSuccess = true
} catch let error as NSError { // } catch let error as NSError {
os_log("策略4失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code) // os_log("策略4失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
} // }
} // }
// 策略5: 完整设置(原始方案) // // 策略5: 完整设置(原始方案)
if !settingSuccess { // if !settingSuccess {
os_log("尝试策略5: 完整设置 (playback + default + 所有选项)", log: logger, type: .info) // os_log("尝试策略5: 完整设置 (playback + default + 所有选项)", log: logger, type: .info)
try audioSession.setCategory(.playback, // try audioSession.setCategory(.playback,
mode: .default, // mode: .default,
options: [.allowBluetoothA2DP, .mixWithOthers]) // options: [.allowBluetoothA2DP, .mixWithOthers])
os_log("✓ 策略5成功: 完整音频设置完成", log: logger, type: .info) // os_log("✓ 策略5成功: 完整音频设置完成", log: logger, type: .info)
settingSuccess = true // settingSuccess = true
} // }
if !settingSuccess { // if !settingSuccess {
os_log("❌ 所有设置策略都失败了", log: logger, type: .error) // os_log("❌ 所有设置策略都失败了", log: logger, type: .error)
throw NSError(domain: "AudioSessionHub", code: -1, userInfo: [NSLocalizedDescriptionKey: "所有音频类别设置策略都失败"]) // throw NSError(domain: "AudioSessionHub", code: -1, userInfo: [NSLocalizedDescriptionKey: "所有音频类别设置策略都失败"])
} // }
} // }
// 第三步:激活音频会话 // // 第三步:激活音频会话
os_log("尝试激活音频会话...", log: logger, type: .info) // os_log("尝试激活音频会话...", log: logger, type: .info)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation) // try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
os_log("✓ 音频会话激活成功", log: logger, type: .info) // os_log("✓ 音频会话激活成功", log: logger, type: .info)
os_log("✅ 音频会话完整设置成功:%{public}@", log: logger, type: .info, String(describing: intent)) // os_log("✅ 音频会话完整设置成功:%{public}@", log: logger, type: .info, String(describing: intent))
// 输出当前音频路由信息 // // 输出当前音频路由信息
let hasBluetoothDevice = hasBluetoothAudioDevice() // let hasBluetoothDevice = hasBluetoothAudioDevice()
let hasBluetoothMic = hasBluetoothMicrophone() // let hasBluetoothMic = hasBluetoothMicrophone()
let currentInput = getCurrentInputDevice() // let currentInput = getCurrentInputDevice()
os_log("📊 音频状态总览:", log: logger, type: .info) // os_log("📊 音频状态总览:", log: logger, type: .info)
os_log(" - 蓝牙音频设备: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接") // os_log(" - 蓝牙音频设备: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接")
os_log(" - 蓝牙麦克风: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接") // os_log(" - 蓝牙麦克风: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接")
os_log(" - 当前输入设备: %{public}@", log: logger, type: .info, currentInput) // os_log(" - 当前输入设备: %{public}@", log: logger, type: .info, currentInput)
if hasBluetoothDevice { // if hasBluetoothDevice {
os_log("📋 详细路由信息:\n%{public}@", log: logger, type: .debug, getCurrentAudioRoute()) // os_log("📋 详细路由信息:\n%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
} // }
} catch let error as NSError { // } catch let error as NSError {
let osStatusCode = error.code // let osStatusCode = error.code
let domain = error.domain // let domain = error.domain
let description = error.localizedDescription // let description = error.localizedDescription
os_log("❌ 设置音频会话失败", log: logger, type: .error) // os_log("❌ 设置音频会话失败", log: logger, type: .error)
os_log(" - 错误域: %{public}@", log: logger, type: .error, domain) // os_log(" - 错误域: %{public}@", log: logger, type: .error, domain)
os_log(" - OSStatus代码: %d (0x%X)", log: logger, type: .error, osStatusCode, osStatusCode) // os_log(" - OSStatus代码: %d (0x%X)", log: logger, type: .error, osStatusCode, osStatusCode)
os_log(" - 错误描述: %{public}@", log: logger, type: .error, description) // os_log(" - 错误描述: %{public}@", log: logger, type: .error, description)
os_log(" - 当前意图: %{public}@", log: logger, type: .error, String(describing: intent)) // os_log(" - 当前意图: %{public}@", log: logger, type: .error, String(describing: intent))
// 解析具体的 OSStatus 错误 // // 解析具体的 OSStatus 错误
switch osStatusCode { // switch osStatusCode {
case -50: // case -50:
os_log(" ➤ 错误分析: OSStatus -50 通常表示参数错误 (kAudioSessionInvalidPropertyError)", log: logger, type: .error) // os_log(" ➤ 错误分析: OSStatus -50 通常表示参数错误 (kAudioSessionInvalidPropertyError)", log: logger, type: .error)
case -560557673: // kAudioSessionNotInitialized // case -560557673: // kAudioSessionNotInitialized
os_log(" ➤ 错误分析: 音频会话未初始化", log: logger, type: .error) // os_log(" ➤ 错误分析: 音频会话未初始化", log: logger, type: .error)
case -560030580: // kAudioSessionBadPropertySizeError // case -560030580: // kAudioSessionBadPropertySizeError
os_log(" ➤ 错误分析: 属性大小错误", log: logger, type: .error) // os_log(" ➤ 错误分析: 属性大小错误", log: logger, type: .error)
case -560161140: // kAudioSessionIncompatibleCategory // case -560161140: // kAudioSessionIncompatibleCategory
os_log(" ➤ 错误分析: 音频类别不兼容", log: logger, type: .error) // os_log(" ➤ 错误分析: 音频类别不兼容", log: logger, type: .error)
default: // default:
os_log(" ➤ 错误分析: 未知的OSStatus错误代码", log: logger, type: .error) // os_log(" ➤ 错误分析: 未知的OSStatus错误代码", log: logger, type: .error)
} // }
// 输出当前音频会话状态用于调试 // // 输出当前音频会话状态用于调试
os_log("🔍 当前音频会话状态:", log: logger, type: .error) // os_log("🔍 当前音频会话状态:", log: logger, type: .error)
os_log(" - 类别: %{public}@", log: logger, type: .error, audioSession.category.rawValue) // os_log(" - 类别: %{public}@", log: logger, type: .error, audioSession.category.rawValue)
os_log(" - 模式: %{public}@", log: logger, type: .error, audioSession.mode.rawValue) // os_log(" - 模式: %{public}@", log: logger, type: .error, audioSession.mode.rawValue)
os_log(" - 是否激活: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否") // os_log(" - 是否激活: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否")
os_log(" - 其他音频播放: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否") // os_log(" - 其他音频播放: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否")
} // }
} // }
public func appWillEnterForeground() { public func appWillEnterForeground() {
os_log("📱 应用即将进入前台", log: logger, type: .info) os_log("📱 应用即将进入前台", log: logger, type: .info)
if let intent = currentIntent { if let intent = currentIntent {
os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent)) os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent) //begin(intent)
} else { } else {
os_log("无之前的音频意图需要恢复", log: logger, type: .info) os_log("无之前的音频意图需要恢复", log: logger, type: .info)
} }
@ -385,7 +385,7 @@ public class AudioSessionHub {
if options.contains(.shouldResume), let intent = currentIntent { if options.contains(.shouldResume), let intent = currentIntent {
os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent)) os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
// 恢复之前的音频意图 // 恢复之前的音频意图
begin(intent) //begin(intent)
os_log("✓ 音频会话中断结束,已恢复", log: logger, type: .info) os_log("✓ 音频会话中断结束,已恢复", log: logger, type: .info)
} else { } else {
os_log("不需要恢复音频会话", log: logger, type: .info) os_log("不需要恢复音频会话", log: logger, type: .info)
@ -420,7 +420,7 @@ public class AudioSessionHub {
// 重新应用当前意图以确保正确路由 // 重新应用当前意图以确保正确路由
if let intent = currentIntent { if let intent = currentIntent {
os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent)) os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent) // begin(intent)
} else { } else {
os_log("无当前音频意图需要重新应用", log: logger, type: .info) os_log("无当前音频意图需要重新应用", log: logger, type: .info)
} }
@ -444,7 +444,7 @@ public class AudioSessionHub {
// 重新应用当前意图以确保正确路由 // 重新应用当前意图以确保正确路由
if let intent = currentIntent { if let intent = currentIntent {
os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent)) os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent) // begin(intent)
} else { } else {
os_log("无当前音频意图需要重新应用", log: logger, type: .info) os_log("无当前音频意图需要重新应用", log: logger, type: .info)
} }
@ -474,13 +474,13 @@ public class AudioSessionHub {
/// 处理播放音频会话激活请求 /// 处理播放音频会话激活请求
@objc private func handleAudioSessionBeginPlayback(notification: Notification) { @objc private func handleAudioSessionBeginPlayback(notification: Notification) {
os_log("收到播放音频会话激活请求", log: logger, type: .info) os_log("收到播放音频会话激活请求", log: logger, type: .info)
begin(.playback) // begin(.playback)
} }
/// 处理语音音频会话激活请求 /// 处理语音音频会话激活请求
@objc private func handleAudioSessionBeginVoice(notification: Notification) { @objc private func handleAudioSessionBeginVoice(notification: Notification) {
os_log("收到语音音频会话激活请求", log: logger, type: .info) os_log("收到语音音频会话激活请求", log: logger, type: .info)
begin(.voice) // begin(.voice)
} }
/// 处理音量控制请求 /// 处理音量控制请求

178
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift

@ -27,6 +27,16 @@ public class AzureAsrHelper: NSObject {
private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor() private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor()
private var continuousCallback: ContinuousRecognizeCallback? private var continuousCallback: ContinuousRecognizeCallback?
private var isNetworkRecovering = false private var isNetworkRecovering = false
// 网络丢失计数器
private var networkLostCount = 0
// 最大网络丢失次数(3秒检测)
private let maxNetworkLostCount = 3
// 防抖机制相关变量
private var lastNetworkLostTime: Int64 = 0
private let networkLostDebounceInterval: Int64 = 3000 // 3秒防抖间隔
// 状态管理 // 状态管理
private var _isContinuousRecognitionActive = false private var _isContinuousRecognitionActive = false
@ -141,61 +151,52 @@ public class AzureAsrHelper: NSObject {
private func handleNetworkAvailable() { private func handleNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info) os_log("网络已恢复", log: log, type: .info)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if !self.isNetworkRecovering { if !self.isNetworkRecovering {
self.isNetworkRecovering = true self.isNetworkRecovering = true
os_log("网络恢复,准备重新启动识别", log: self.log, type: .info) os_log("网络恢复,准备重新启动识别", log: self.log, type: .info)
// 如果之前在进行连续识别,重新启动
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.restartContinuousRecognition()
}
} }
}
} }
/**
* 重新启动连续识别(网络恢复时使用) /**
* 处理网络连接丢失(带防抖机制)
*/ */
private func restartContinuousRecognition() { private func handleNetworkLost() {
os_log("重新启动连续识别", log: log, type: .info) let currentTime = Int64(Date().timeIntervalSince1970 * 1000) // 转换为毫秒
// 检查continuousCallback是否为空 // 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发
guard let callback = continuousCallback else { if currentTime - lastNetworkLostTime < networkLostDebounceInterval {
os_log("连续识别回调为空,无法重新启动", log: log, type: .info) os_log("网络丢失事件被防抖过滤,距离上次触发仅%{public}dms", log: log, type: .debug, currentTime - lastNetworkLostTime)
return return
} }
do { // 更新最后触发时间
_ = setupEventListeners(callback: callback) lastNetworkLostTime = currentTime
isNetworkRecovering = false
os_log("连续识别重新启动成功", log: log, type: .info)
} catch {
os_log("重新启动连续识别失败: %@", log: log, type: .error, error.localizedDescription)
isNetworkRecovering = false
}
}
/**
* 立即停止音频处理
*/
private func stopAudioProcessingImmediately() {
audioStream?.stopMicrophoneCapture()
if _isContinuousRecognitionActive { // 这里启动一个3秒定时器,3秒后检查网络是否恢复
do { DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
try recognizer?.stopContinuousRecognition() guard let self = self else { return }
_isContinuousRecognitionActive = false
os_log("因网络问题强制停止识别", log: log, type: .info) os_log("网络不可用", log: self.log, type: .error)
} catch { let isNetworkAvailable = self.checkNetworkStatus()
_isContinuousRecognitionActive = false
os_log("强制停止识别失败: %@", log: log, type: .error, error.localizedDescription) if isNetworkAvailable {
// 如果网络恢复,重置计数器和防抖时间
os_log("网络已恢复,重置计数器", log: self.log, type: .info)
self.lastNetworkLostTime = 0
self.isNetworkRecovering = true
} else {
self.continuousCallback?.onError(1000, "网络连接不可用,请检查网络设置")
os_log("网络仍未恢复,继续检测", log: self.log, type: .debug)
self.networkLostCount = 0
self.stopContinuousRecognition()
} }
} }
} }
/** /**
* 预初始化音频组件 * 预初始化音频组件
* 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟 * 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟
@ -245,13 +246,11 @@ public class AzureAsrHelper: NSObject {
do { do {
guard recognizer != nil else { if recognizer == nil {
if !setupRecognizer() { // 检查continuousCallback是否为空,只有在不为空时才设置事件监听器
print("重置识别器失败") if let callback = self.continuousCallback {
// callback.onError("重置识别器失败") setupEventListeners(callback: callback)
return false
} }
return false
} }
print("startContinuousRecognition:\(audioSourceType)") print("startContinuousRecognition:\(audioSourceType)")
@ -266,7 +265,7 @@ public class AzureAsrHelper: NSObject {
return true return true
} catch { } catch {
audioStream?.stopMicrophoneCapture() stopContinuousRecognition()
_isContinuousRecognitionActive = false _isContinuousRecognitionActive = false
//callback.onError("启动连续识别失败: \(error.localizedDescription)") //callback.onError("启动连续识别失败: \(error.localizedDescription)")
return false return false
@ -300,13 +299,10 @@ public class AzureAsrHelper: NSObject {
do { do {
_isContinuousRecognitionActive = false _isContinuousRecognitionActive = false
if audioSourceType == .external {
//pushAudioData(data: Data())
}
audioStream?.stopMicrophoneCapture() audioStream?.stopMicrophoneCapture()
// 停止连续识别 // 停止连续识别
try recognizer.stopContinuousRecognition() try recognizer.stopContinuousRecognition()
self.recognizer = nil
print("stopContinuousRecognition") print("stopContinuousRecognition")
return true return true
} catch { } catch {
@ -320,6 +316,7 @@ public class AzureAsrHelper: NSObject {
// 【修复】在 catch 块中安全地停止识别器,避免再次抛出异常 // 【修复】在 catch 块中安全地停止识别器,避免再次抛出异常
do { do {
try recognizer.stopContinuousRecognition() try recognizer.stopContinuousRecognition()
self.recognizer = nil
} catch { } catch {
// 如果强制停止也失败,记录错误但不再抛出异常 // 如果强制停止也失败,记录错误但不再抛出异常
os_log("强制停止识别器失败: %@", log: log, type: .error, error.localizedDescription) os_log("强制停止识别器失败: %@", log: log, type: .error, error.localizedDescription)
@ -623,18 +620,9 @@ public class AzureAsrHelper: NSObject {
os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails) os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails)
// 检查是否为网络相关错误
if self.isNetworkRelatedError(errorDetails) {
os_log("检测到网络相关错误,停止音频处理", log: self.log, type: .info)
self.stopAudioProcessingImmediately()
// 如果不是正在恢复网络,通知上层网络错误
if !self.isNetworkRecovering {
callback.onError(1000,"启动连续识别失败,网络连接已断开")
}
} else {
callback.onCanceled(reason, errorDetails) callback.onCanceled(reason, errorDetails)
}
} }
return true return true
} }
@ -845,75 +833,17 @@ extension AzureAsrHelper: NetworkStateMonitor.NetworkStateListener {
func onNetworkAvailable() { func onNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info) os_log("网络已恢复", log: log, type: .info)
DispatchQueue.main.async { [weak self] in self.handleNetworkAvailable()
self?.handleNetworkAvailable()
}
} }
/**
* 处理网络丢失事件
*/
func onNetworkLost() { func onNetworkLost() {
os_log("网络连接丢失", log: log, type: .error) os_log("网络连接丢失", log: log, type: .error)
// 立即停止音频处理 self.handleNetworkLost()
stopAudioProcessingImmediately()
DispatchQueue.main.async { [weak self] in
self?.continuousCallback?.onError(1000,"网络连接已断开")
}
} }
} }
// //ios 语言检测
// /**
// * 检测输入文本的语言,从支持的语言列表中选择最匹配的
// *
// * @param text 需要检测的文本
// * @return 从supportedLanguages中选择的最匹配语言代码
// */
// public func detectTextLanguage(_ text: String) -> String? {
// // 如果只有一种支持的语言,直接返回
// if supportedLanguages.count == 1 {
// return supportedLanguages[0]
// }
// // 获取所有可能的语言及其置信度
// let languageResults = LanguageDetector.detectLanguageWithConfidence(from: text)
// // 遍历检测结果,找到第一个在supportedLanguages中的语言
// for result in languageResults {
// let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(result.language)
// if supportedLanguages.contains(azureLanguageCode) {
// return azureLanguageCode
// }
// }
// // 如果没有找到匹配的语言,返回第一个支持的语言作为默认值
// return supportedLanguages.first
// }
// /**
// * 检测文本语言并返回详细信息
// *
// * @param text 需要检测的文本
// * @return 包含语言代码、置信度和语言名称的字典
// */
// public func detectTextLanguageWithDetails(_ text: String) -> [String: Any]? {
// let results = LanguageDetector.detectLanguageWithConfidence(from: text)
// guard let topResult = results.first else {
// return nil
// }
// let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(topResult.language)
// let languageName = LanguageDetector.getLanguageName(from: topResult.language)
// return [
// "languageCode": azureLanguageCode,
// "confidence": topResult.confidence,
// "languageName": languageName,
// "originalCode": topResult.language
// ]
// }

82
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 return await withCheckedContinuation { continuation in
// 直接调用QPlayAutoManager的搜索接口 // 调用更新后的搜索接口,包含错误处理
QPlayAutoManager.shared.search(keyword: keyword, searchType: .song) { items in QPlayAutoManager.shared.search(
var result: [String: Any] keyword: keyword,
searchType: .song,
if let items = items, !items.isEmpty { completion: { items in
let songs = items.map { item -> [String: Any] in var result: [String: Any]
return [
"mid": item.mid ?? "", if let items = items, !items.isEmpty {
"name": item.name ?? "", let songs = items.map { item -> [String: Any] in
"artist": item.singer?.name ?? "", return [
"album": item.album ?? "", "mid": item.mid ?? "",
"duration": item.duration, "name": item.name ?? "",
"pic": item.coverUri ?? "" "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, do {
"message": "Found \(items.count) songs and started playing", let jsonData = try JSONSerialization.data(withJSONObject: result)
"broadcast":false, let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
"data": songs 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) ?? "{}"
do { continuation.resume(returning: jsonString)
let jsonData = try JSONSerialization.data(withJSONObject: result) } catch {
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" continuation.resume(returning: "{\"success\": false, \"error\": \"Search failed and JSON serialization failed\"}")
continuation.resume(returning: jsonString) }
} catch {
continuation.resume(returning: "{\"success\": false, \"error\": \"JSON serialization failed\"}")
} }
} )
} }
} }
} }

39
local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt

@ -141,12 +141,15 @@ object LocationService {
callback.onLocationError(-1, "定位服务未初始化") callback.onLocationError(-1, "定位服务未初始化")
return return
} }
val p = checkPermissions(ctx)
if (!checkPermissions(ctx)) { if (p == 1) {
callback.onLocationError(-2, "没有定位权限") callback.onLocationError(-2, "定位权限没有")
return
}
if (p == 2) {
callback.onLocationError(-2, "后台定位权限没有")
return return
} }
if (!initialized) { if (!initialized) {
callback.onLocationError(-3, "定位服务未初始化") callback.onLocationError(-3, "定位服务未初始化")
return return
@ -238,7 +241,7 @@ object LocationService {
override fun run() { override fun run() {
val ctx = appcontext ?: return val ctx = appcontext ?: return
val hasPermission = checkPermissions(ctx) val hasPermission = checkPermissions(ctx)
if (hasPermission && !initialized) { if (hasPermission ==0 && !initialized) {
Log.d(TAG, "权限已授予,重新初始化定位服务") Log.d(TAG, "权限已授予,重新初始化定位服务")
initialize(ctx) initialize(ctx)
stopPermissionMonitor() stopPermissionMonitor()
@ -256,12 +259,26 @@ object LocationService {
permissionRetryRunnable = null permissionRetryRunnable = null
} }
private fun checkPermissions(context: Context): Boolean {
return if (isAppInForeground(context)) { /**
hasForegroundLocationPermission(context) * 检查位置权限状态并返回对应编码
} else { * @return 0:已获得所需全部权限;1:未获得前台权限;2:前台权限已获得但后台权限未授予(仅Android 10+)
hasBackgroundLocationPermission(context) */
private fun checkPermissions(context: Context): Int {
// 先检查前台权限是否已授予
val hasForegroundPermission = hasForegroundLocationPermission(context)
if (!hasForegroundPermission) {
return 1 // 前台权限未授予
} }
// 前台权限已授予,再根据前后台状态检查后台权限
if (!isAppInForeground(context)) {
// 应用在后台:Android 10+ 需要检查后台权限
return 2 // 后台权限未授予
}
// 所有所需权限均已授予
return 0
} }
private fun hasForegroundLocationPermission(context: Context): Boolean { private fun hasForegroundLocationPermission(context: Context): Boolean {
@ -306,7 +323,7 @@ object LocationService {
val ctx = appcontext ?: activity val ctx = appcontext ?: activity
// 如果已经有权限,直接返回成功 // 如果已经有权限,直接返回成功
if (checkPermissions(ctx)) { if (checkPermissions(ctx) == 0) {
result.success(mapOf("granted" to true)) result.success(mapOf("granted" to true))
return return
} }

45
local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift

@ -195,8 +195,15 @@ public class LocationService: NSObject {
// 检查是否已有请求在进行 // 检查是否已有请求在进行
if self.currentLocationCallback != nil { if self.currentLocationCallback != nil {
completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "位置请求正在进行中"]))) print("[LocationService] 检测到重复请求,重置之前的请求")
return self.resetLocationRequest()
}
// 实时检查权限状态
let currentStatus = CLLocationManager.authorizationStatus()
if currentStatus != self.authorizationStatus {
print("[LocationService] 权限状态已变更,更新缓存")
self.authorizationStatus = currentStatus
} }
// 检查权限 // 检查权限
@ -205,6 +212,12 @@ public class LocationService: NSObject {
return return
} }
// 检查位置服务是否启用
if !CLLocationManager.locationServicesEnabled() {
completion(.failure(NSError(domain: "LocationService", code: -6, userInfo: [NSLocalizedDescriptionKey: "位置服务未启用"])))
return
}
// 设置回调 // 设置回调
self.currentLocationCallback = { result in self.currentLocationCallback = { result in
switch result { 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() locationManager.requestLocation()
} }
@ -253,7 +274,7 @@ public class LocationService: NSObject {
) )
let callback = currentLocationCallback let callback = currentLocationCallback
currentLocationCallback = nil resetLocationRequest() // 使用重置方法
callback?(.success(locationInfo)) callback?(.success(locationInfo))
} }
@ -266,6 +287,18 @@ public class LocationService: NSObject {
"timestamp": locationData.timestamp "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 // MARK: - CLLocationManagerDelegate
@ -304,7 +337,7 @@ extension LocationService: CLLocationManagerDelegate {
print("[LocationService] 位置获取失败: \(error.localizedDescription)") print("[LocationService] 位置获取失败: \(error.localizedDescription)")
let callback = currentLocationCallback let callback = currentLocationCallback
currentLocationCallback = nil resetLocationRequest() // 使用重置方法
callback?(.failure(error)) 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

155
local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift

@ -316,7 +316,27 @@ import QPlayAutoSDK
} }
// MARK: - 登录状态管理 // 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( private func ensureQQMusicInstalledAndLoginAndExecute(
_ operation: @escaping () -> Void, _ operation: @escaping () -> Void,
onQQMusicNotInstalled: (() -> Void)? = nil, onQQMusicNotInstalled: (() -> Void)? = nil,
@ -330,15 +350,37 @@ import QPlayAutoSDK
} }
// QQ音乐已安装,继续检查登录状态 // 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行开始) // 修改现有的ensureLoginAndExecute方法
private func ensureLoginAndExecute(_ operation: @escaping () -> Void, onLoginFailed: (() -> Void)? = nil) { private func ensureLoginAndExecute(
_ operation: @escaping () -> Void,
onFailure: ((QPlayAutoError) -> Void)? = nil
) {
// 首先检查QQ音乐是否已安装
guard isQQMusicInstalled else {
print("QPlayAutoManager: QQ音乐未安装,无法执行操作")
onFailure?(.qqMusicNotInstalled)
return
}
if isLoginOK { if isLoginOK {
// 已登录,直接执行操作 // 已登录,直接执行操作
operation() operation()
} else { } else {
// 未登录,先登录 // 未登录,先登录
print("QPlayAutoManager: 未登录,正在尝试登录...") print("QPlayAutoManager: 未登录,正在尝试登录...")
performLogin { [weak self] success in performLogin { [weak self] success in
@ -347,7 +389,7 @@ import QPlayAutoSDK
operation() operation()
} else { } else {
print("QPlayAutoManager: 登录失败,无法执行操作") print("QPlayAutoManager: 登录失败,无法执行操作")
onLoginFailed?() onFailure?(.loginFailed)
} }
} }
} }
@ -533,33 +575,34 @@ import QPlayAutoSDK
} }
@objc public func pause() { @objc public func pause() {
ensureLoginAndExecute { ensureLoginAndExecute({
QPlayAutoSDK.playerPlayPause() QPlayAutoSDK.playerPlayPause()
} onLoginFailed: { }, onFailure: { error in
print("QPlayAutoManager: 暂停失败 - 登录失败") print("QPlayAutoManager: 暂停失败 - \(error)")
} })
} }
// Fix seek method (lines 571-578)
@objc public func seek(to position: TimeInterval) { @objc public func seek(to position: TimeInterval) {
ensureLoginAndExecute { ensureLoginAndExecute {
QPlayAutoSDK.playerSeek(Int(position)) QPlayAutoSDK.playerSeek(Int(position))
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 跳转播放位置失败 - 登录失败") print("QPlayAutoManager: 跳转播放位置失败 - \(error)")
} }
} }
// MARK: - 扩展功能方法(需要添加登录检查) // MARK: - 扩展功能方法(需要添加登录检查)
/// 设置播放模式 /// 设置播放模式
@objc public func setPlayMode(_ playMode: QPlayAutoPlayMode, completion: @escaping (Bool) -> Void) { @objc public func setPlayMode(_ playMode: QPlayAutoPlayMode, completion: @escaping (Bool) -> Void) {
ensureLoginAndExecute { ensureLoginAndExecute {
QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in
completion(success) completion(success)
} }
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 设置播放模式失败 - 登录失败") print("QPlayAutoManager: 设置播放模式失败 - \(error)")
completion(false) completion(false)
} }
} }
/// 收藏/取消收藏歌曲 /// 收藏/取消收藏歌曲
@ -568,11 +611,12 @@ import QPlayAutoSDK
QPlayAutoSDK.setFavoriteStateWithSong(song, isFavorite: isFavorite) { errorCode in QPlayAutoSDK.setFavoriteStateWithSong(song, isFavorite: isFavorite) { errorCode in
completion(errorCode == 0) completion(errorCode == 0)
} }
} onLoginFailed: { }onFailure: { error in
print("QPlayAutoManager: 设置收藏状态失败 - 登录失败") print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(false) completion(false)
} }
} }
/// 获取相似歌曲 /// 获取相似歌曲
@objc public func requestSimilarSongs(song: QPlayAutoListItem, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) { @objc public func requestSimilarSongs(song: QPlayAutoListItem, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) {
@ -585,12 +629,12 @@ import QPlayAutoSDK
completion(nil, error) completion(nil, error)
} }
} }
} onLoginFailed: { }onFailure: { error in
print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败") print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
completion(nil, error) completion(nil, error)
} }
} }
/// 获取歌词 /// 获取歌词
@objc public func requestLyric(song: QPlayAutoListItem, completion: @escaping (QPlayAutoLyric?, Error?) -> Void) { @objc public func requestLyric(song: QPlayAutoListItem, completion: @escaping (QPlayAutoLyric?, Error?) -> Void) {
@ -603,12 +647,11 @@ import QPlayAutoSDK
completion(nil, error) completion(nil, error)
} }
} }
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 请求歌词失败 - 登录失败") print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
completion(nil, error) completion(nil, error)
} }
} }
/// 获取歌曲列表 /// 获取歌曲列表
@objc public func requestSonglist(type: QPlayAutoSongListType, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) { @objc public func requestSonglist(type: QPlayAutoSongListType, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) {
@ -621,12 +664,11 @@ import QPlayAutoSDK
completion(nil, error) completion(nil, error)
} }
} }
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 请求歌单失败 - 登录失败") print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
completion(nil, error) completion(nil, error)
} }
} }
/// 播放指定歌曲列表 /// 播放指定歌曲列表
@objc public func playAtIndex(songList: [QPlayAutoListItem], playIndex: Int, completion: @escaping (Bool) -> Void) { @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 QPlayAutoSDK.play(atIndex: songList, play: playIndex) { errorCode in
completion(errorCode == 0) completion(errorCode == 0)
} }
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 播放指定索引歌曲失败 - QQ音乐未安装") print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(false) completion(false)
} }
} }
// MARK: - 状态查询 // MARK: - 状态查询
@objc public var isConnected: Bool { @objc public var isConnected: Bool {
@ -729,17 +771,21 @@ import QPlayAutoSDK
@objc public func search( @objc public func search(
keyword: String, keyword: String,
searchType: QPlayAutoSearchType, searchType: QPlayAutoSearchType,
completion: @escaping ([QPlayAutoListItem]?) -> Void completion: @escaping ([QPlayAutoListItem]?) -> Void,
onError: ((Error?) -> Void)? = nil
) { ) {
ensureLoginAndExecute { ensureLoginAndExecute {
QPlayAutoSDK.search(keyword, type: searchType, firstPage: true) { (error, items) in QPlayAutoSDK.search(keyword, type: searchType, firstPage: true) { (error, items) in
completion(items) completion(items)
} }
} onLoginFailed: { }onFailure: { error in
print("QPlayAutoManager: 搜索失败 - 登录失败") print("QPlayAutoManager: 搜索失败 - 登录失败 - \(error)")
completion(nil) let loginError = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [
NSLocalizedDescriptionKey: "登录失败: \(error)"
])
onError?(loginError)
}
} }
}
@objc public func getItems( @objc public func getItems(
playlistId: String, playlistId: String,
@ -756,11 +802,12 @@ import QPlayAutoSDK
completion(nil) completion(nil)
} }
} }
} onLoginFailed: { } onFailure: { error in
print("QPlayAutoManager: 获取播放列表失败 - 登录失败") print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(nil) completion(nil)
} }
} }
// 添加公开的登录方法 // 添加公开的登录方法
@objc public func login(completion: @escaping (Bool) -> Void) { @objc public func login(completion: @escaping (Bool) -> Void) {

Loading…
Cancel
Save