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 {
// 只有在非按住说话模式且非打电话模式下才自动切换到文字模式
// 按住说话模式下应该保持在语音模式,等待下次按住
if (!isPhoneCallMode.value) {
isTextInputMode.value = true;
}
// if (!isPhoneCallMode.value && !isPushToTalkActive.value) {
// Logger.i(TAG, '切换文本输入模式');
// isTextInputMode.value = false;
// }
}
Logger.i(TAG, '识别停止');

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

@ -196,7 +196,17 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
IntroStepBuilder(
group: 'ai',
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) {
return Obx(
() => IntroOverlayBuilder.homeBuilder(
@ -242,8 +252,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
? IntroStepBuilder(
group: 'ai',
order: 4,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
@ -503,8 +511,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return IntroStepBuilder(
group: 'ai',
order: 1,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
@ -667,19 +673,30 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
: Colors.blue[100])
: (isDarkMode
? Colors.grey[800]
: Colors.grey[200]),
: const Color.fromARGB(
255, 255, 255, 255)),
borderRadius:
BorderRadius.circular(22.w),
border: Border.all(
color: isPushToTalkActive
? Colors.blue
: (isDarkMode
? Colors.grey[800]!
: Colors.grey
.withOpacity(0.1)),
width:
isPushToTalkActive ? 2.w : 1.w,
),
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 2,
offset: const Offset(0, 1),
),
],
// 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(
children: [
@ -739,143 +756,149 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(width: 8.w),
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮
IntroStepBuilder(
group: 'ai',
order: 2,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}',
alignment: false,
onTap: () async {
await controller
.noviceGuideService.aiStepsList[
controller.noviceGuideService
.aiStep]['method']();
params.onNext!();
controller.sendMessage();
},
);
},
builder: (BuildContext context, GlobalKey key) =>
Obx(
key: key,
() {
final hasText = controller.hasTextInput.value;
final hasImage =
controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage;
final shouldShowExpand =
controller.shouldShowExpandButton.value;
Builder(builder: (context) {
StepWidgetParams? widgetParams;
return IntroStepBuilder(
group: 'ai',
order: 2,
onHighlightWidgetTap: () async {
await controller
.noviceGuideService.aiStepsList[
controller.noviceGuideService.aiStep]
['method']();
widgetParams!.onNext!();
controller.sendMessage();
},
overlayBuilder: (params) {
widgetParams = params;
return IntroOverlayBuilder.aiBuilder(
params,
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}',
alignment: false,
);
},
builder:
(BuildContext context, GlobalKey key) =>
Obx(
key: key,
() {
final hasText =
controller.hasTextInput.value;
final hasImage =
controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage;
final shouldShowExpand =
controller.shouldShowExpandButton.value;
if (shouldShowSend) {
// 显示发送按钮(可能还有展开按钮)
return Column(
children: [
// 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText)
Padding(
padding:
EdgeInsets.only(bottom: 40.h),
child: GestureDetector(
onTap: () {
// 打开底部弹出编辑模式
controller.openFullScreenEdit();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor:
Colors.transparent,
builder: (context) =>
const FullScreenEditor(),
).then((_) {
// 弹出页关闭时的回调
if (shouldShowSend) {
// 显示发送按钮(可能还有展开按钮)
return Column(
children: [
// 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText)
Padding(
padding:
EdgeInsets.only(bottom: 40.h),
child: GestureDetector(
onTap: () {
// 打开底部弹出编辑模式
controller
.closeFullScreenEdit();
});
},
child: Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? 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,
.openFullScreenEdit();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor:
Colors.transparent,
builder: (context) =>
const FullScreenEditor(),
).then((_) {
// 弹出页关闭时的回调
controller
.closeFullScreenEdit();
});
},
child: Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? 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(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: primaryColor,
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
Icons.send,
color: Colors.white,
isExpanded
? Icons.close
: Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
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
if useBle {
AudioSessionHub.shared.begin(.playback)
// AudioSessionHub.shared.begin(.playback)
} else {
AudioSessionHub.shared.begin(.voice)
//AudioSessionHub.shared.begin(.voice)
}
print("ai启动语音=\(audioSourceType)=\(useBle)")
guard let success = azureAsrHelper?.startContinuousRecognition(
@ -1487,7 +1487,7 @@ class AudioPlayer {
private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated)
// 等待音效超时时间(秒)- 改为 var 以支持动态修改
private var awaitTimeoutSeconds: TimeInterval = 10.0
private var awaitTimeoutSeconds: TimeInterval = 30.0
private var resourcesBundle: Bundle {
#if SWIFT_PACKAGE

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

@ -28,196 +28,196 @@ public class AudioSessionHub {
_ = shared // 触发单例初始化
}
public func begin(_ intent: AudioIntent) {
if intent == currentIntent && audioSession.isOtherAudioPlaying == false {
os_log("音频意图未变化且无其他音频播放,跳过设置: %{public}@", log: logger, type: .debug, String(describing: intent))
return
}
// public func begin(_ intent: AudioIntent) {
// if intent == currentIntent && audioSession.isOtherAudioPlaying == false {
// os_log("音频意图未变化且无其他音频播放,跳过设置: %{public}@", log: logger, type: .debug, String(describing: intent))
// return
// }
os_log("开始设置音频会话,意图: %{public}@", log: logger, type: .info, String(describing: intent))
currentIntent = intent
// os_log("开始设置音频会话,意图: %{public}@", log: logger, type: .info, String(describing: intent))
// currentIntent = intent
do {
switch intent {
// do {
// switch intent {
case .voice:
os_log("设置语音模式: category=playAndRecord, mode=voiceChat", log: logger, type: .info)
// case .voice:
// os_log("设置语音模式: category=playAndRecord, mode=voiceChat", log: logger, type: .info)
// 第一步:设置音频类别和模式
try audioSession.setCategory(.playAndRecord,
mode: .voiceChat,
options: [.allowBluetooth, .defaultToSpeaker, .mixWithOthers])
os_log("✓ 音频类别设置成功: playAndRecord + voiceChat", log: logger, type: .info)
// // 第一步:设置音频类别和模式
// try audioSession.setCategory(.playAndRecord,
// mode: .voiceChat,
// options: [.allowBluetooth, .defaultToSpeaker, .mixWithOthers])
// os_log("✓ 音频类别设置成功: playAndRecord + voiceChat", log: logger, type: .info)
// 第二步:检查并设置蓝牙输入
os_log("检查可用输入设备...", log: logger, type: .info)
if let availableInputs = audioSession.availableInputs {
os_log("可用输入设备数量: %d", log: logger, type: .info, availableInputs.count)
for (index, input) in availableInputs.enumerated() {
os_log("输入设备[%d]: %{public}@ (%{public}@)", log: logger, type: .info,
index, input.portName, input.portType.rawValue)
}
// // 第二步:检查并设置蓝牙输入
// os_log("检查可用输入设备...", log: logger, type: .info)
// if let availableInputs = audioSession.availableInputs {
// os_log("可用输入设备数量: %d", log: logger, type: .info, availableInputs.count)
// for (index, input) in availableInputs.enumerated() {
// os_log("输入设备[%d]: %{public}@ (%{public}@)", log: logger, type: .info,
// index, input.portName, input.portType.rawValue)
// }
// 查找蓝牙HFP设备
if let bluetoothInput = availableInputs.first(where: { $0.portType == .bluetoothHFP }) {
os_log("找到蓝牙HFP设备,尝试设置为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName)
try audioSession.setPreferredInput(bluetoothInput)
os_log("✓ 蓝牙HFP输入设置成功: %{public}@", log: logger, type: .info, bluetoothInput.portName)
} else {
os_log("未找到蓝牙HFP设备", log: logger, type: .info)
}
} else {
os_log("⚠️ availableInputs 为 nil", log: logger, type: .default)
}
// // 查找蓝牙HFP设备
// if let bluetoothInput = availableInputs.first(where: { $0.portType == .bluetoothHFP }) {
// os_log("找到蓝牙HFP设备,尝试设置为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName)
// try audioSession.setPreferredInput(bluetoothInput)
// os_log("✓ 蓝牙HFP输入设置成功: %{public}@", log: logger, type: .info, bluetoothInput.portName)
// } else {
// os_log("未找到蓝牙HFP设备", log: logger, type: .info)
// }
// } else {
// os_log("⚠️ availableInputs 为 nil", log: logger, type: .default)
// }
case .playback:
os_log("设置播放模式: category=playback, mode=default", log: logger, type: .info)
// case .playback:
// os_log("设置播放模式: category=playback, mode=default", 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.mode.rawValue)
os_log(" - 当前是否激活: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否")
os_log(" - 其他音频播放: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否")
// // 检查当前音频会话状态
// os_log("设置前状态检查:", log: logger, type: .info)
// 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.isOtherAudioPlaying ? "是" : "否")
// os_log(" - 其他音频播放: %{public}@", log: logger, type: .info, audioSession.isOtherAudioPlaying ? "是" : "否")
// 如果当前已经激活,先尝试停用
do {
if audioSession.isOtherAudioPlaying {
os_log("检测到其他音频正在播放,先尝试停用当前会话", log: logger, type: .info)
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
os_log("✓ 成功停用当前音频会话", log: logger, type: .info)
}
} catch let deactivateError as NSError {
os_log("⚠️ 停用当前音频会话失败: %{public}@ (继续尝试设置)", log: logger, type: .default, deactivateError.localizedDescription)
}
// // 如果当前已经激活,先尝试停用
// do {
// if audioSession.isOtherAudioPlaying {
// os_log("检测到其他音频正在播放,先尝试停用当前会话", log: logger, type: .info)
// try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
// os_log("✓ 成功停用当前音频会话", log: logger, type: .info)
// }
// } catch let deactivateError as NSError {
// os_log("⚠️ 停用当前音频会话失败: %{public}@ (继续尝试设置)", log: logger, type: .default, deactivateError.localizedDescription)
// }
// 尝试渐进式设置:先尝试最简单的设置
var settingSuccess = false
// // 尝试渐进式设置:先尝试最简单的设置
// var settingSuccess = false
// 策略1: 只设置类别,不设置选项
if !settingSuccess {
do {
os_log("尝试策略1: 仅设置类别 (playback)", log: logger, type: .info)
try audioSession.setCategory(.playback)
os_log("✓ 策略1成功: 音频类别设置为 playback", log: logger, type: .info)
settingSuccess = true
} catch let error as NSError {
os_log("策略1失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
}
}
// // 策略1: 只设置类别,不设置选项
// if !settingSuccess {
// do {
// os_log("尝试策略1: 仅设置类别 (playback)", log: logger, type: .info)
// try audioSession.setCategory(.playback)
// os_log("✓ 策略1成功: 音频类别设置为 playback", log: logger, type: .info)
// settingSuccess = true
// } catch let error as NSError {
// os_log("策略1失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
// }
// }
// 策略2: 设置类别和模式,不设置选项
if !settingSuccess {
do {
os_log("尝试策略2: 设置类别和模式 (playback + default)", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default)
os_log("✓ 策略2成功: 音频类别和模式设置完成", log: logger, type: .info)
settingSuccess = true
} catch let error as NSError {
os_log("策略2失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
}
}
// // 策略2: 设置类别和模式,不设置选项
// if !settingSuccess {
// do {
// os_log("尝试策略2: 设置类别和模式 (playback + default)", log: logger, type: .info)
// try audioSession.setCategory(.playback, mode: .default)
// os_log("✓ 策略2成功: 音频类别和模式设置完成", log: logger, type: .info)
// settingSuccess = true
// } catch let error as NSError {
// os_log("策略2失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
// }
// }
// 策略3: 设置类别、模式和单个选项
if !settingSuccess {
do {
os_log("尝试策略3: 设置类别、模式和混音选项", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
os_log("✓ 策略3成功: 音频类别、模式和混音选项设置完成", log: logger, type: .info)
settingSuccess = true
} catch let error as NSError {
os_log("策略3失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
}
}
// // 策略3: 设置类别、模式和单个选项
// if !settingSuccess {
// do {
// os_log("尝试策略3: 设置类别、模式和混音选项", log: logger, type: .info)
// try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
// os_log("✓ 策略3成功: 音频类别、模式和混音选项设置完成", log: logger, type: .info)
// settingSuccess = true
// } catch let error as NSError {
// os_log("策略3失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
// }
// }
// 策略4: 设置类别、模式和蓝牙选项
if !settingSuccess {
do {
os_log("尝试策略4: 设置类别、模式和蓝牙选项", log: logger, type: .info)
try audioSession.setCategory(.playback, mode: .default, options: [.allowBluetoothA2DP])
os_log("✓ 策略4成功: 音频类别、模式和蓝牙选项设置完成", log: logger, type: .info)
settingSuccess = true
} catch let error as NSError {
os_log("策略4失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
}
}
// // 策略4: 设置类别、模式和蓝牙选项
// if !settingSuccess {
// do {
// os_log("尝试策略4: 设置类别、模式和蓝牙选项", log: logger, type: .info)
// try audioSession.setCategory(.playback, mode: .default, options: [.allowBluetoothA2DP])
// os_log("✓ 策略4成功: 音频类别、模式和蓝牙选项设置完成", log: logger, type: .info)
// settingSuccess = true
// } catch let error as NSError {
// os_log("策略4失败: %{public}@ (OSStatus: %d)", log: logger, type: .info, error.localizedDescription, error.code)
// }
// }
// 策略5: 完整设置(原始方案)
if !settingSuccess {
os_log("尝试策略5: 完整设置 (playback + default + 所有选项)", log: logger, type: .info)
try audioSession.setCategory(.playback,
mode: .default,
options: [.allowBluetoothA2DP, .mixWithOthers])
os_log("✓ 策略5成功: 完整音频设置完成", log: logger, type: .info)
settingSuccess = true
}
// // 策略5: 完整设置(原始方案)
// if !settingSuccess {
// os_log("尝试策略5: 完整设置 (playback + default + 所有选项)", log: logger, type: .info)
// try audioSession.setCategory(.playback,
// mode: .default,
// options: [.allowBluetoothA2DP, .mixWithOthers])
// os_log("✓ 策略5成功: 完整音频设置完成", log: logger, type: .info)
// settingSuccess = true
// }
if !settingSuccess {
os_log("❌ 所有设置策略都失败了", log: logger, type: .error)
throw NSError(domain: "AudioSessionHub", code: -1, userInfo: [NSLocalizedDescriptionKey: "所有音频类别设置策略都失败"])
}
// if !settingSuccess {
// os_log("❌ 所有设置策略都失败了", log: logger, type: .error)
// throw NSError(domain: "AudioSessionHub", code: -1, userInfo: [NSLocalizedDescriptionKey: "所有音频类别设置策略都失败"])
// }
}
// }
// 第三步:激活音频会话
os_log("尝试激活音频会话...", log: logger, type: .info)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
os_log("✓ 音频会话激活成功", log: logger, type: .info)
// // 第三步:激活音频会话
// os_log("尝试激活音频会话...", log: logger, type: .info)
// try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
// 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 hasBluetoothMic = hasBluetoothMicrophone()
let currentInput = getCurrentInputDevice()
// // 输出当前音频路由信息
// let hasBluetoothDevice = hasBluetoothAudioDevice()
// let hasBluetoothMic = hasBluetoothMicrophone()
// let currentInput = getCurrentInputDevice()
os_log("📊 音频状态总览:", log: logger, type: .info)
os_log(" - 蓝牙音频设备: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接")
os_log(" - 蓝牙麦克风: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接")
os_log(" - 当前输入设备: %{public}@", log: logger, type: .info, currentInput)
// os_log("📊 音频状态总览:", log: logger, type: .info)
// os_log(" - 蓝牙音频设备: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接")
// os_log(" - 蓝牙麦克风: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接")
// os_log(" - 当前输入设备: %{public}@", log: logger, type: .info, currentInput)
if hasBluetoothDevice {
os_log("📋 详细路由信息:\n%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
}
} catch let error as NSError {
let osStatusCode = error.code
let domain = error.domain
let description = error.localizedDescription
// if hasBluetoothDevice {
// os_log("📋 详细路由信息:\n%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
// }
// } catch let error as NSError {
// let osStatusCode = error.code
// let domain = error.domain
// let description = error.localizedDescription
os_log("❌ 设置音频会话失败", log: logger, type: .error)
os_log(" - 错误域: %{public}@", log: logger, type: .error, domain)
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, String(describing: intent))
// os_log("❌ 设置音频会话失败", log: logger, type: .error)
// os_log(" - 错误域: %{public}@", log: logger, type: .error, domain)
// 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, String(describing: intent))
// 解析具体的 OSStatus 错误
switch osStatusCode {
case -50:
os_log(" ➤ 错误分析: OSStatus -50 通常表示参数错误 (kAudioSessionInvalidPropertyError)", log: logger, type: .error)
case -560557673: // kAudioSessionNotInitialized
os_log(" ➤ 错误分析: 音频会话未初始化", log: logger, type: .error)
case -560030580: // kAudioSessionBadPropertySizeError
os_log(" ➤ 错误分析: 属性大小错误", log: logger, type: .error)
case -560161140: // kAudioSessionIncompatibleCategory
os_log(" ➤ 错误分析: 音频类别不兼容", log: logger, type: .error)
default:
os_log(" ➤ 错误分析: 未知的OSStatus错误代码", log: logger, type: .error)
}
// // 解析具体的 OSStatus 错误
// switch osStatusCode {
// case -50:
// os_log(" ➤ 错误分析: OSStatus -50 通常表示参数错误 (kAudioSessionInvalidPropertyError)", log: logger, type: .error)
// case -560557673: // kAudioSessionNotInitialized
// os_log(" ➤ 错误分析: 音频会话未初始化", log: logger, type: .error)
// case -560030580: // kAudioSessionBadPropertySizeError
// os_log(" ➤ 错误分析: 属性大小错误", log: logger, type: .error)
// case -560161140: // kAudioSessionIncompatibleCategory
// os_log(" ➤ 错误分析: 音频类别不兼容", log: logger, type: .error)
// default:
// os_log(" ➤ 错误分析: 未知的OSStatus错误代码", 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.mode.rawValue)
os_log(" - 是否激活: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否")
os_log(" - 其他音频播放: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否")
}
}
// // 输出当前音频会话状态用于调试
// os_log("🔍 当前音频会话状态:", log: logger, type: .error)
// 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.isOtherAudioPlaying ? "是" : "否")
// os_log(" - 其他音频播放: %{public}@", log: logger, type: .error, audioSession.isOtherAudioPlaying ? "是" : "否")
// }
// }
public func appWillEnterForeground() {
os_log("📱 应用即将进入前台", log: logger, type: .info)
if let intent = currentIntent {
os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent)
//begin(intent)
} else {
os_log("无之前的音频意图需要恢复", log: logger, type: .info)
}
@ -385,7 +385,7 @@ public class AudioSessionHub {
if options.contains(.shouldResume), let intent = currentIntent {
os_log("恢复之前的音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
// 恢复之前的音频意图
begin(intent)
//begin(intent)
os_log("✓ 音频会话中断结束,已恢复", log: logger, type: .info)
} else {
os_log("不需要恢复音频会话", log: logger, type: .info)
@ -420,7 +420,7 @@ public class AudioSessionHub {
// 重新应用当前意图以确保正确路由
if let intent = currentIntent {
os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent)
// begin(intent)
} else {
os_log("无当前音频意图需要重新应用", log: logger, type: .info)
}
@ -444,7 +444,7 @@ public class AudioSessionHub {
// 重新应用当前意图以确保正确路由
if let intent = currentIntent {
os_log("重新应用当前音频意图: %{public}@", log: logger, type: .info, String(describing: intent))
begin(intent)
// begin(intent)
} else {
os_log("无当前音频意图需要重新应用", log: logger, type: .info)
}
@ -474,13 +474,13 @@ public class AudioSessionHub {
/// 处理播放音频会话激活请求
@objc private func handleAudioSessionBeginPlayback(notification: Notification) {
os_log("收到播放音频会话激活请求", log: logger, type: .info)
begin(.playback)
// begin(.playback)
}
/// 处理语音音频会话激活请求
@objc private func handleAudioSessionBeginVoice(notification: Notification) {
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 continuousCallback: ContinuousRecognizeCallback?
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
@ -141,61 +151,52 @@ public class AzureAsrHelper: NSObject {
private func handleNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if !self.isNetworkRecovering {
self.isNetworkRecovering = true
os_log("网络恢复,准备重新启动识别", log: self.log, type: .info)
// 如果之前在进行连续识别,重新启动
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.restartContinuousRecognition()
}
}
}
}
/**
* 重新启动连续识别(网络恢复时使用)
/**
* 处理网络连接丢失(带防抖机制)
*/
private func restartContinuousRecognition() {
os_log("重新启动连续识别", log: log, type: .info)
private func handleNetworkLost() {
let currentTime = Int64(Date().timeIntervalSince1970 * 1000) // 转换为毫秒
// 检查continuousCallback是否为空
guard let callback = continuousCallback else {
os_log("连续识别回调为空,无法重新启动", log: log, type: .info)
// 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发
if currentTime - lastNetworkLostTime < networkLostDebounceInterval {
os_log("网络丢失事件被防抖过滤,距离上次触发仅%{public}dms", log: log, type: .debug, currentTime - lastNetworkLostTime)
return
}
do {
_ = setupEventListeners(callback: callback)
isNetworkRecovering = false
os_log("连续识别重新启动成功", log: log, type: .info)
} catch {
os_log("重新启动连续识别失败: %@", log: log, type: .error, error.localizedDescription)
isNetworkRecovering = false
}
}
/**
* 立即停止音频处理
*/
private func stopAudioProcessingImmediately() {
audioStream?.stopMicrophoneCapture()
// 更新最后触发时间
lastNetworkLostTime = currentTime
if _isContinuousRecognitionActive {
do {
try recognizer?.stopContinuousRecognition()
_isContinuousRecognitionActive = false
os_log("因网络问题强制停止识别", log: log, type: .info)
} catch {
_isContinuousRecognitionActive = false
os_log("强制停止识别失败: %@", log: log, type: .error, error.localizedDescription)
// 这里启动一个3秒定时器,3秒后检查网络是否恢复
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
guard let self = self else { return }
os_log("网络不可用", log: self.log, type: .error)
let isNetworkAvailable = self.checkNetworkStatus()
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 {
guard recognizer != nil else {
if !setupRecognizer() {
print("重置识别器失败")
// callback.onError("重置识别器失败")
return false
if recognizer == nil {
// 检查continuousCallback是否为空,只有在不为空时才设置事件监听器
if let callback = self.continuousCallback {
setupEventListeners(callback: callback)
}
return false
}
print("startContinuousRecognition:\(audioSourceType)")
@ -266,7 +265,7 @@ public class AzureAsrHelper: NSObject {
return true
} catch {
audioStream?.stopMicrophoneCapture()
stopContinuousRecognition()
_isContinuousRecognitionActive = false
//callback.onError("启动连续识别失败: \(error.localizedDescription)")
return false
@ -300,13 +299,10 @@ public class AzureAsrHelper: NSObject {
do {
_isContinuousRecognitionActive = false
if audioSourceType == .external {
//pushAudioData(data: Data())
}
audioStream?.stopMicrophoneCapture()
// 停止连续识别
try recognizer.stopContinuousRecognition()
self.recognizer = nil
print("stopContinuousRecognition")
return true
} catch {
@ -320,6 +316,7 @@ public class AzureAsrHelper: NSObject {
// 【修复】在 catch 块中安全地停止识别器,避免再次抛出异常
do {
try recognizer.stopContinuousRecognition()
self.recognizer = nil
} catch {
// 如果强制停止也失败,记录错误但不再抛出异常
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)
// 检查是否为网络相关错误
if self.isNetworkRelatedError(errorDetails) {
os_log("检测到网络相关错误,停止音频处理", log: self.log, type: .info)
self.stopAudioProcessingImmediately()
// 如果不是正在恢复网络,通知上层网络错误
if !self.isNetworkRecovering {
callback.onError(1000,"启动连续识别失败,网络连接已断开")
}
} else {
callback.onCanceled(reason, errorDetails)
}
}
return true
}
@ -845,75 +833,17 @@ extension AzureAsrHelper: NetworkStateMonitor.NetworkStateListener {
func onNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info)
DispatchQueue.main.async { [weak self] in
self?.handleNetworkAvailable()
}
self.handleNetworkAvailable()
}
/**
* 处理网络丢失事件
*/
func onNetworkLost() {
os_log("网络连接丢失", log: log, type: .error)
// 立即停止音频处理
stopAudioProcessingImmediately()
DispatchQueue.main.async { [weak self] in
self?.continuousCallback?.onError(1000,"网络连接已断开")
}
self.handleNetworkLost()
}
}
// //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
// 直接调用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\"}")
}
}
}
)
}
}
}

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

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

155
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) {

Loading…
Cancel
Save