From d46a66675f919ac43d6ee22ae52d16e13771df63 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 10 Jun 2026 10:39:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=B0=83=E8=AF=95=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/ble_manager.dart | 27 + .../call_translation_debug_binding.dart | 10 + .../call_translation_debug_controller.dart | 148 ++++++ .../views/call_translation_debug_view.dart | 300 +++++++++++ lib/modules/settings/views/settings_view.dart | 20 + lib/routes/app_pages.dart | 9 + lib/routes/app_routes.dart | 1 + .../yunqiinnovation/agent_service/BleAgent.kt | 3 + .../azure_speech/AzureSpeechPlugin.kt | 3 + .../ble_service/BleCommandSender.kt | 18 +- .../yunqiinnovation/ble_service/BleConst.kt | 8 +- .../yunqiinnovation/ble_service/BleService.kt | 254 +++++++-- .../ble_service/BleServicePlugin.kt | 23 + .../ble_service/OpusAudioManager.kt | 42 +- .../ble_service/lib/ble_service.dart | 37 ++ .../main/kotlin/com/example/ota/OtaPlugin.kt | 3 + pubspec.lock | 486 +++++++++--------- 17 files changed, 1094 insertions(+), 298 deletions(-) create mode 100644 lib/modules/call_translation_debug/bindings/call_translation_debug_binding.dart create mode 100644 lib/modules/call_translation_debug/controllers/call_translation_debug_controller.dart create mode 100644 lib/modules/call_translation_debug/views/call_translation_debug_view.dart diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 2471a5758..790bd82a6 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -1247,6 +1247,33 @@ class BleManager extends GetxService { } } + /// 设置通话翻译调试参数(运行时生效) + /// [sendIntervalMs] 音频下行发送间隔(ms);[bundleFrameCount] 下行合包帧数 + Future setCallTranslationDebugParams({ + int? sendIntervalMs, + int? bundleFrameCount, + }) async { + try { + return await _bleService.setCallTranslationDebugParams( + sendIntervalMs: sendIntervalMs, + bundleFrameCount: bundleFrameCount, + ); + } catch (e) { + Logger.e('CALL_TRANS_DEBUG', '设置通话翻译调试参数失败: ${e.toString()}'); + return false; + } + } + + /// 获取当前通话翻译调试参数 {'sendIntervalMs': int, 'bundleFrameCount': int} + Future> getCallTranslationDebugParams() async { + try { + return await _bleService.getCallTranslationDebugParams(); + } catch (e) { + Logger.e('CALL_TRANS_DEBUG', '获取通话翻译调试参数失败: ${e.toString()}'); + return {}; + } + } + /// 打开解码器 Future openCallRecordDecoder() async { try { diff --git a/lib/modules/call_translation_debug/bindings/call_translation_debug_binding.dart b/lib/modules/call_translation_debug/bindings/call_translation_debug_binding.dart new file mode 100644 index 000000000..e901c0a41 --- /dev/null +++ b/lib/modules/call_translation_debug/bindings/call_translation_debug_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../controllers/call_translation_debug_controller.dart'; + +class CallTranslationDebugBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => CallTranslationDebugController()); + } +} diff --git a/lib/modules/call_translation_debug/controllers/call_translation_debug_controller.dart b/lib/modules/call_translation_debug/controllers/call_translation_debug_controller.dart new file mode 100644 index 000000000..703fd5af3 --- /dev/null +++ b/lib/modules/call_translation_debug/controllers/call_translation_debug_controller.dart @@ -0,0 +1,148 @@ +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; + +import '../../../data/services/ble_manager.dart'; +import '../../../core/utils/logger.dart'; + +/// 通话翻译调试控制器 +/// 负责调试参数(音频下行发送间隔、下行合包帧数)的读取、保存、下发, +/// 以及通话翻译编解码的启动/停止。 +class CallTranslationDebugController extends GetxController { + final BleManager _bleManager = Get.find(); + final GetStorage _storage = GetStorage(); + + // 持久化键 + static const String _kSendInterval = 'call_trans_debug_send_interval'; + static const String _kBundleFrameCount = 'call_trans_debug_bundle_frames'; + + // 默认值(与原生默认保持一致) + static const int defaultSendIntervalMs = 100; + static const int defaultBundleFrameCount = 4; + + // 取值范围 + static const int minSendIntervalMs = 20; + static const int maxSendIntervalMs = 1000; + static const int sendIntervalStepMs = 20; // 音频下行间隔步长 20ms + static const int minBundleFrameCount = 1; + static const int maxBundleFrameCount = 20; + + /// 音频下行发送间隔(ms) + final RxInt sendIntervalMs = defaultSendIntervalMs.obs; + + /// 下行合包帧数 + final RxInt bundleFrameCount = defaultBundleFrameCount.obs; + + /// 通话翻译编解码是否已开启 + RxBool get isCodecActive => _bleManager.isCodecActiveRx; + + /// 蓝牙是否已连接 + RxBool get isConnected => _bleManager.isConnectedRx; + + @override + void onInit() { + super.onInit(); + _loadFromStorage(); + // 进入界面时尝试用原生当前值同步(若原生已被修改) + _syncFromNative(); + } + + /// 从本地存储加载参数 + void _loadFromStorage() { + final savedInterval = _storage.read(_kSendInterval); + final savedFrames = _storage.read(_kBundleFrameCount); + if (savedInterval != null) { + sendIntervalMs.value = + savedInterval.clamp(minSendIntervalMs, maxSendIntervalMs); + } + if (savedFrames != null) { + bundleFrameCount.value = + savedFrames.clamp(minBundleFrameCount, maxBundleFrameCount); + } + } + + /// 用原生当前实际值同步显示(不覆盖用户本地保存的偏好,仅在无本地保存时参考) + Future _syncFromNative() async { + try { + final params = await _bleManager.getCallTranslationDebugParams(); + if (params.isEmpty) return; + // 仅当本地没有保存过时,采用原生返回值 + if (_storage.read(_kSendInterval) == null) { + final ni = params['sendIntervalMs']; + if (ni != null && ni > 0) { + sendIntervalMs.value = ni.clamp(minSendIntervalMs, maxSendIntervalMs); + } + } + if (_storage.read(_kBundleFrameCount) == null) { + final nf = params['bundleFrameCount']; + if (nf != null && nf > 0) { + bundleFrameCount.value = + nf.clamp(minBundleFrameCount, maxBundleFrameCount); + } + } + } catch (e) { + Logger.e('CALL_TRANS_DEBUG', '同步原生参数失败: $e'); + } + } + + void setSendInterval(int value) { + // 按 20ms 步长对齐 + final snapped = + (value / sendIntervalStepMs).round() * sendIntervalStepMs; + sendIntervalMs.value = snapped.clamp(minSendIntervalMs, maxSendIntervalMs); + } + + void setBundleFrameCount(int value) { + bundleFrameCount.value = + value.clamp(minBundleFrameCount, maxBundleFrameCount); + } + + /// 恢复默认值 + void resetDefaults() { + sendIntervalMs.value = defaultSendIntervalMs; + bundleFrameCount.value = defaultBundleFrameCount; + } + + /// 保存并下发参数到原生(运行时立即生效) + Future applyParams() async { + _storage.write(_kSendInterval, sendIntervalMs.value); + _storage.write(_kBundleFrameCount, bundleFrameCount.value); + final ok = await _bleManager.setCallTranslationDebugParams( + sendIntervalMs: sendIntervalMs.value, + bundleFrameCount: bundleFrameCount.value, + ); + Logger.i('CALL_TRANS_DEBUG', + '下发调试参数 interval=${sendIntervalMs.value}ms frames=${bundleFrameCount.value} result=$ok'); + Get.snackbar( + '通话翻译调试', + ok ? '参数已应用' : '参数下发失败(请确认设备已连接)', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2), + ); + } + + /// 应用参数并启动通话翻译编解码 + Future applyAndStart() async { + if (!isConnected.value) { + Get.snackbar('通话翻译调试', '设备未连接,无法启动', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2)); + return; + } + // 先下发参数再启动,确保编解码以新参数运行 + await applyParams(); + final ok = await _bleManager.openA2DPDecoder(); + Logger.i('CALL_TRANS_DEBUG', '启动通话翻译编解码 result=$ok'); + Get.snackbar('通话翻译调试', ok ? '已启动通话翻译' : '启动失败', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2)); + } + + /// 停止通话翻译编解码 + Future stop() async { + final ok = await _bleManager.closeCodec(); + Logger.i('CALL_TRANS_DEBUG', '停止通话翻译编解码 result=$ok'); + Get.snackbar('通话翻译调试', ok ? '已停止通话翻译' : '停止失败', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2)); + } +} diff --git a/lib/modules/call_translation_debug/views/call_translation_debug_view.dart b/lib/modules/call_translation_debug/views/call_translation_debug_view.dart new file mode 100644 index 000000000..7221bf98b --- /dev/null +++ b/lib/modules/call_translation_debug/views/call_translation_debug_view.dart @@ -0,0 +1,300 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../controllers/call_translation_debug_controller.dart'; + +/// 通话翻译调试界面 +/// 可调整音频下行发送间隔与下行合包帧数,应用后启动/停止通话翻译。 +class CallTranslationDebugView + extends GetView { + const CallTranslationDebugView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return Scaffold( + backgroundColor: isDark ? Colors.black : Colors.grey[50], + appBar: AppBar( + backgroundColor: isDark ? Colors.black : Colors.white, + elevation: 0.5, + centerTitle: true, + title: Text( + '通话翻译调试', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black, + ), + ), + ), + body: ListView( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + children: [ + _buildStatusCard(isDark), + SizedBox(height: 16.h), + _buildSendIntervalCard(isDark), + SizedBox(height: 16.h), + _buildBundleFrameCard(isDark), + SizedBox(height: 24.h), + _buildActionButtons(isDark), + ], + ), + ); + } + + Widget _card(bool isDark, {required Widget child}) { + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1E1E1E) : Colors.white, + borderRadius: BorderRadius.circular(16.r), + border: isDark + ? Border.all(color: const Color(0xFF2C2C2C), width: 1) + : null, + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withValues(alpha: 0.4) + : Colors.black.withValues(alpha: 0.06), + blurRadius: 12, + offset: const Offset(0, 2), + ), + ], + ), + child: child, + ); + } + + // 连接 / 编解码状态 + Widget _buildStatusCard(bool isDark) { + return _card( + isDark, + child: Obx(() { + final connected = controller.isConnected.value; + final active = controller.isCodecActive.value; + return Row( + children: [ + _statusDot(connected ? Colors.green : Colors.red), + SizedBox(width: 8.w), + Text( + connected ? '设备已连接' : '设备未连接', + style: TextStyle( + fontSize: 14.sp, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const Spacer(), + _statusDot(active ? Colors.green : Colors.grey), + SizedBox(width: 8.w), + Text( + active ? '翻译运行中' : '翻译未启动', + style: TextStyle( + fontSize: 14.sp, + color: isDark ? Colors.white70 : Colors.black54, + ), + ), + ], + ); + }), + ); + } + + Widget _statusDot(Color color) { + return Container( + width: 10.w, + height: 10.w, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ); + } + + // 音频下行发送间隔 + Widget _buildSendIntervalCard(bool isDark) { + return _card( + isDark, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _title(isDark, '音频下行发送间隔', + '每包音频之间的发送间隔,值越小发送越快、占用带宽越高(${CallTranslationDebugController.minSendIntervalMs}~${CallTranslationDebugController.maxSendIntervalMs} ms,步长 ${CallTranslationDebugController.sendIntervalStepMs} ms)'), + SizedBox(height: 8.h), + Obx( + () => Row( + children: [ + Expanded( + child: Slider( + value: controller.sendIntervalMs.value + .clamp( + CallTranslationDebugController.minSendIntervalMs, + CallTranslationDebugController.maxSendIntervalMs, + ) + .toDouble(), + min: CallTranslationDebugController.minSendIntervalMs + .toDouble(), + max: CallTranslationDebugController.maxSendIntervalMs + .toDouble(), + divisions: (CallTranslationDebugController + .maxSendIntervalMs - + CallTranslationDebugController.minSendIntervalMs) ~/ + CallTranslationDebugController.sendIntervalStepMs, + label: '${controller.sendIntervalMs.value} ms', + onChanged: (v) => + controller.setSendInterval(v.round()), + ), + ), + _valueChip(isDark, '${controller.sendIntervalMs.value} ms'), + ], + ), + ), + ], + ), + ); + } + + // 下行合包帧数 + Widget _buildBundleFrameCard(bool isDark) { + return _card( + isDark, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _title(isDark, '下行合包帧数', + '左右声道各自累积多少帧 opus 合并为一包后下发,值越大单包越大、发送频率越低(${CallTranslationDebugController.minBundleFrameCount}~${CallTranslationDebugController.maxBundleFrameCount} 帧)'), + SizedBox(height: 8.h), + Obx( + () => Row( + children: [ + Expanded( + child: Slider( + value: controller.bundleFrameCount.value + .clamp( + CallTranslationDebugController.minBundleFrameCount, + CallTranslationDebugController.maxBundleFrameCount, + ) + .toDouble(), + min: CallTranslationDebugController.minBundleFrameCount + .toDouble(), + max: CallTranslationDebugController.maxBundleFrameCount + .toDouble(), + divisions: + CallTranslationDebugController.maxBundleFrameCount - + CallTranslationDebugController.minBundleFrameCount, + label: '${controller.bundleFrameCount.value} 帧', + onChanged: (v) => + controller.setBundleFrameCount(v.round()), + ), + ), + _valueChip(isDark, '${controller.bundleFrameCount.value} 帧'), + ], + ), + ), + ], + ), + ); + } + + Widget _title(bool isDark, String title, String subtitle) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + SizedBox(height: 4.h), + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: isDark ? Colors.white60 : Colors.black54, + height: 1.3, + ), + ), + ], + ); + } + + Widget _valueChip(bool isDark, String text) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: isDark + ? Colors.blue[900]!.withValues(alpha: 0.3) + : Colors.blue[50], + borderRadius: BorderRadius.circular(10.r), + ), + child: Text( + text, + style: TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.w600, + color: isDark ? Colors.blue[200] : Colors.blue[700], + ), + ), + ); + } + + Widget _buildActionButtons(bool isDark) { + return Column( + children: [ + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: controller.resetDefaults, + style: OutlinedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + child: const Text('恢复默认'), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: ElevatedButton( + onPressed: controller.applyParams, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue[600], + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + child: const Text('保存并应用'), + ), + ), + ], + ), + SizedBox(height: 12.h), + Obx( + () { + final active = controller.isCodecActive.value; + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: active ? controller.stop : controller.applyAndStart, + icon: Icon(active ? Icons.stop : Icons.play_arrow), + label: Text(active ? '停止通话翻译' : '应用并启动通话翻译'), + style: ElevatedButton.styleFrom( + backgroundColor: active ? Colors.red[600] : Colors.green[600], + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 14.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index e2a3cc56a..0c60796a0 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -616,6 +616,26 @@ class SettingsView extends GetView { ) : const SizedBox.shrink()), + Obx(() => controller.showDebugInfo.value + ? _buildSimpleNavigationSetting( + title: '通话翻译调试', + subtitle: '调整发送间隔/合包帧数后启动通话翻译', + icon: Icons.tune, + iconBgColor: isDarkMode + ? Colors.blue[900]!.withValues(alpha: 0.3) + : Colors.blue[100]!, + iconColor: isDarkMode + ? Colors.blue[300]! + : Colors.blue[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.callTransDebug); + }, + isDarkMode: isDarkMode, + ) + : const SizedBox.shrink()), + Obx(() => controller.showDebugInfo.value ? _buildSimpleNavigationSetting( title: '日志导出', diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index a072a94de..13bb276c5 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -26,6 +26,8 @@ import '../modules/opus_test/bindings/opus_test_binding.dart'; import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/log_export/bindings/log_export_binding.dart'; import '../modules/log_export/views/log_export_view.dart'; +import '../modules/call_translation_debug/bindings/call_translation_debug_binding.dart'; +import '../modules/call_translation_debug/views/call_translation_debug_view.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/profile/views/profile_meeting_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; @@ -293,6 +295,13 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.callTransDebug, + page: () => const CallTranslationDebugView(), + binding: CallTranslationDebugBinding(), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 250), + ), GetPage( name: Routes.agent, page: () => const AgentView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index e9bcfdc48..c81788bda 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -47,6 +47,7 @@ abstract class Routes { static const jieliTest = '/jieli_test'; // 杰理芯片测试 static const opusTest = '/opus_test'; // Opus编码测试 static const logExport = '/log_export'; // 日志导出 + static const callTransDebug = '/call_trans_debug'; // 通话翻译调试 static const bleTest = '/ble_test'; // 蓝牙测试 static const speechTest = '/speech_test'; // 综合语音测试 diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt index 182dc7f9f..f02b44d77 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt @@ -209,6 +209,9 @@ object BleAgent : BleService.Callback, AgentServiceListener { override fun onCallSleepReceived(channel: Int) { } + override fun onCallResumeReceived(channel: Int) { + } + //============================================================================================ diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index 001e9feb8..295f95e21 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt @@ -2070,6 +2070,9 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin, ActivityAware, override fun onCallSleepReceived(channel: Int) { } + override fun onCallResumeReceived(channel: Int) { + } + // TODO: 双端翻译时启用副芯片回调 // override fun onSecondaryChipConnectionStateChangedDetail(state: Int, deviceAddress: String?, reason: String?) { } // override fun onSecondaryChipConnectionsCountChanged(count: Int, addresses: List) { } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt index c1320f26d..11f6a7c10 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt @@ -91,6 +91,12 @@ class BleCommandSender { */ fun notifyCallSleepReceived(channel: Int) + /** + * 通知声道通话恢复指令接收 + * @param channel 恢复声道:BleConst.AUDIO_CHANNEL_LEFT(左) / BleConst.AUDIO_CHANNEL_RIGHT(右) + */ + fun notifyCallResumeReceived(channel: Int) + /** * 通知开始 AI 单次对话 */ @@ -385,6 +391,8 @@ class BleCommandSender { BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码" BleConst.CODEC_CONTROL_CALL_SLEEP_LEFT -> "左声道通话休眠" BleConst.CODEC_CONTROL_CALL_SLEEP_RIGHT -> "右声道通话休眠" + BleConst.CODEC_CONTROL_CALL_RESUME_LEFT -> "左声道通话恢复" + BleConst.CODEC_CONTROL_CALL_RESUME_RIGHT -> "右声道通话恢复" else -> "未知状态($codecStatus)" } @@ -591,7 +599,7 @@ class BleCommandSender { } BleConst.CMD_CONTROL_CODEC -> { - // 设备主动上报编解码控制状态,关心左/右声道通话休眠 (0xF0 / 0xF1) + // 设备主动上报编解码控制状态,关心左/右声道通话休眠 (0xF0 / 0xF1) 与恢复 (0xF2 / 0xF3) // 帧格式: 0xCC 0x05 [len] [codecStatus, channelMode, ...] [crc] val codecStatus = if (notifyData.isNotEmpty()) notifyData[0].toInt() and 0xFF else -1 @@ -604,6 +612,14 @@ class BleCommandSender { Log.i(TAG, "收到主动上报: 右声道通话休眠 (codecStatus=0xF1)") callback?.notifyCallSleepReceived(BleConst.AUDIO_CHANNEL_RIGHT) } + BleConst.CODEC_CONTROL_CALL_RESUME_LEFT -> { + Log.i(TAG, "收到主动上报: 左声道通话恢复 (codecStatus=0xF2)") + callback?.notifyCallResumeReceived(BleConst.AUDIO_CHANNEL_LEFT) + } + BleConst.CODEC_CONTROL_CALL_RESUME_RIGHT -> { + Log.i(TAG, "收到主动上报: 右声道通话恢复 (codecStatus=0xF3)") + callback?.notifyCallResumeReceived(BleConst.AUDIO_CHANNEL_RIGHT) + } else -> { Log.d(TAG, "收到主动上报编解码控制: codecStatus=0x${codecStatus.toString(16)}") } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt index ec9bd979e..0106599e5 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt @@ -121,10 +121,14 @@ object BleConst { const val CODEC_CONTROL_A2DP_PLAY = 0xA2 /** mic和dac(音乐或者通话远端)声音 */ const val CODEC_CONTROL_CALL_RECORD_PLAY = 0xA3 - /** 左声道通话休眠(休眠期间暂停下发左声道,仅传输右声道,固定 1s 后自动恢复) */ + /** 左声道通话休眠(休眠期间暂停下发左声道,仅传输右声道,直到收到 F2 恢复指令才恢复) */ const val CODEC_CONTROL_CALL_SLEEP_LEFT = 0xF0 - /** 右声道通话休眠(休眠期间暂停下发右声道,仅传输左声道,固定 1s 后自动恢复) */ + /** 右声道通话休眠(休眠期间暂停下发右声道,仅传输左声道,直到收到 F3 恢复指令才恢复) */ const val CODEC_CONTROL_CALL_SLEEP_RIGHT = 0xF1 + /** 左声道通话恢复(收到后立即恢复左声道下发,按序补发休眠期间积压的左声道包,与 F0 配对) */ + const val CODEC_CONTROL_CALL_RESUME_LEFT = 0xF2 + /** 右声道通话恢复(收到后立即恢复右声道下发,按序补发休眠期间积压的右声道包,与 F1 配对) */ + const val CODEC_CONTROL_CALL_RESUME_RIGHT = 0xF3 /** 打开编码指令 */ const val CODEC_CONTROL_ENCODE_ON = 0xB1 /** 左声道 */ diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index fc65bb00f..c34d01daf 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -74,6 +74,9 @@ object BleService { // 声道通话休眠相关回调 fun onCallSleepReceived(channel: Int) + + // 声道通话恢复相关回调 + fun onCallResumeReceived(channel: Int) } // ====================================================================================================== @@ -140,25 +143,37 @@ object BleService { private var audioSendThread: Thread? = null private val audioSendHandler = Handler(Looper.getMainLooper()) private val isAudioSending = AtomicBoolean(false) + // 上一包音频实际发送的时间戳(ms),仅用于发送节拍日志统计间隔;-1 表示尚未发送过 + private var lastAudioSendTime = -1L + // 左/右声道各自上一包已发送的序号,用于在日志里核对每个声道的包序是否连续(+1);-1 表示尚未发送过 + private var lastLeftSeq = -1L + private var lastRightSeq = -1L + // 下行写入连续失败(底层发送缓冲拥塞)的计数,用于节流告警与恢复提示;写成功即清零 + private var writeFailRetryCount = 0 + // 写失败(拥塞)后的退避时长(ms):给底层发送缓冲腾空的时间,再重试同一包,期间绝不丢包 + private const val WRITE_RETRY_BACKOFF_MS = 5L // 音频数据分块发送的常量 private val AUDIO_CHUNK_SIZE = 120 // 每次发送120字节 - private const val AUDIO_SEND_INTERVAL_NORMAL = 60L // 正常发送间隔60ms - private const val CHANNEL_SLEEP_DURATION = 400L // 收到 F0/F1 后该声道固定休眠 1s,到期自动恢复 - // 左/右声道休眠到期时间戳(SystemClock.elapsedRealtime 基准,0 表示未休眠)。 - // 收到 F0(左)/F1(右) 时置为 now + CHANNEL_SLEEP_DURATION, - // 休眠窗口内该声道的包暂不发送(转入积压缓存),仅传输另一声道,到期后按序补发,不丢包。 + private const val DEFAULT_AUDIO_SEND_INTERVAL_NORMAL = 40L // 默认音频下行发送间隔(ms) + // 音频下行发送间隔(ms),调试界面可在运行时调整;通过 setCallTranslationDebugParams() 修改 + @Volatile + private var audioSendIntervalNormal = DEFAULT_AUDIO_SEND_INTERVAL_NORMAL + // 左/右声道休眠标记(true 表示该声道处于休眠,暂停下发)。 + // 收到 F0(左)/F1(右) 时置 true,收到 F2(左)/F3(右) 恢复指令时置 false。 + // 休眠期间该声道的包暂不发送(转入积压缓存),仅传输另一声道,恢复后按序补发,不丢包。 @Volatile - private var leftChannelSleepUntil: Long = 0L + private var leftChannelSleeping: Boolean = false @Volatile - private var rightChannelSleepUntil: Long = 0L + private var rightChannelSleeping: Boolean = false // 声道休眠期间该声道的包不丢弃,转入对应积压缓存,待声道唤醒后按 FIFO 顺序补发。 // 仅在音频发送线程内访问,无需额外加锁。 private val leftHoldBuffer = ArrayDeque() private val rightHoldBuffer = ArrayDeque() // 积压缓存安全上限(防止设备持续上报休眠导致内存无限增长)。 // 1s 休眠约积压 16 包,留足余量;超限时丢弃最旧包并告警(仅在异常场景触发)。 - private const val CHANNEL_HOLD_BUFFER_MAX = 200 + // 要求绝对不丢包,故大幅抬高上限作为纯防 OOM 的兜底;单包约 165B,10000 包≈1.6MB。 + private const val CHANNEL_HOLD_BUFFER_MAX = 10000 // 左右积压同时可补发时的轮转标记,保证两声道公平补发、避免一方饿死。 private var holdDrainPreferLeft = true // 音频数据缓冲区,用于累积数据到80字节再发送 @@ -371,6 +386,14 @@ object BleService { // 使用外部类的方法来处理声道通话休眠指令,避免无限递归 this@BleService.notifyCallSleepReceived(channel) } + + /** + * 通知声道通话恢复指令接收 + */ + override fun notifyCallResumeReceived(channel: Int) { + // 使用外部类的方法来处理声道通话恢复指令,避免无限递归 + this@BleService.notifyCallResumeReceived(channel) + } /** * 通知开始 AI 单次对话 @@ -1363,7 +1386,12 @@ object BleService { isAudioSending.set(true) audioSendThread = Thread { Log.i(TAG, "音频发送线程已启动") - + + // 固定节拍的下一拍绝对时刻(ms)。每发完一包就 += 间隔,按这个时刻补偿性 sleep, + // 把 poll 等待和发送耗时一起抵消掉,保证实际发送周期稳定贴近 audioSendIntervalNormal。 + // 0 表示尚未起拍,由首包发送时初始化。 + var nextSendAt = 0L + while (isAudioSending.get() && !Thread.currentThread().isInterrupted) { try { // 1) 从主队列取新包,按声道分流到积压缓存。 @@ -1377,11 +1405,10 @@ object BleService { } } - // 2) 选出一包可发送的数据:声道已唤醒(不在休眠窗口)且缓存非空。 + // 2) 选出一包可发送的数据:声道未休眠且缓存非空。 // 左右都可发时按 holdDrainPreferLeft 轮转,保证两声道公平补发、避免一方饿死。 - val now = SystemClock.elapsedRealtime() - val leftReady = leftHoldBuffer.isNotEmpty() && now >= leftChannelSleepUntil - val rightReady = rightHoldBuffer.isNotEmpty() && now >= rightChannelSleepUntil + val leftReady = leftHoldBuffer.isNotEmpty() && !leftChannelSleeping + val rightReady = rightHoldBuffer.isNotEmpty() && !rightChannelSleeping val audioChunk: ByteArray? = when { leftReady && rightReady -> { holdDrainPreferLeft = !holdDrainPreferLeft @@ -1394,27 +1421,83 @@ object BleService { } if (audioChunk != null) { - // 调试录音文件只保留拼接后的纯 opus 数据,剥离 5 字节包头再写入 - if (audioChunk.size > 5) { - val payload = audioChunk.copyOfRange(5, audioChunk.size) - recordfile1?.saveAudioDataToWav(payload) + // 解析包头:声道(byte[4]) 与 序号(byte[0..3] 小端),供日志与重试归位使用 + val channelByte = if (audioChunk.size >= 5) audioChunk[4] else null + val seq = if (audioChunk.size >= 5) + ((audioChunk[3].toInt() and 0xFF) shl 24) or + ((audioChunk[2].toInt() and 0xFF) shl 16) or + ((audioChunk[1].toInt() and 0xFF) shl 8) or + (audioChunk[0].toInt() and 0xFF) + else -1 + val chTag = when (channelByte) { + OpusAudioManager.CHANNEL_LEFT -> "L" + OpusAudioManager.CHANNEL_RIGHT -> "R" + else -> "?($channelByte)" } - // 解析包头供发送节拍日志使用 - if (audioChunk.size >= 5) { - val seq = ((audioChunk[0].toInt() and 0xFF) shl 24) or - ((audioChunk[1].toInt() and 0xFF) shl 16) or - ((audioChunk[2].toInt() and 0xFF) shl 8) or - (audioChunk[3].toInt() and 0xFF) - val chTag = when (audioChunk[4]) { - OpusAudioManager.CHANNEL_LEFT -> "L" - OpusAudioManager.CHANNEL_RIGHT -> "R" - else -> "?(${audioChunk[4]})" + + // 同步写入,拿到真实的成功/失败:false=底层发送缓冲已满(拥塞),作为背压信号 + val sent = sendAudioChunkBlocking(audioChunk) + + if (!sent) { + // 写失败:把这一包放回原声道队头,下轮重试,绝不丢弃(满足“绝对不丢包”)。 + // 包仍在队头,声道内顺序不变。 + if (channelByte == OpusAudioManager.CHANNEL_RIGHT) rightHoldBuffer.addFirst(audioChunk) + else leftHoldBuffer.addFirst(audioChunk) + writeFailRetryCount++ + // 节流告警,避免拥塞期刷屏 + if (writeFailRetryCount == 1 || writeFailRetryCount % 50 == 0) { + Log.w(TAG, "下行写入拥塞,重试中 ch=$chTag seq=$seq 连续失败=$writeFailRetryCount " + + "hold(L=${leftHoldBuffer.size},R=${rightHoldBuffer.size})") + } + // 退避,给底层缓冲腾空时间;重置节拍,待恢复后重新起拍 + nextSendAt = 0L + Thread.sleep(WRITE_RETRY_BACKOFF_MS) + } else { + if (writeFailRetryCount > 0) { + Log.i(TAG, "下行写入已恢复,之前连续失败=$writeFailRetryCount") + writeFailRetryCount = 0 + } + // 调试录音文件只保留拼接后的纯 opus 数据,剥离 5 字节包头再写入(仅成功时写,避免重试重复) + if (audioChunk.size > 5) { + recordfile1?.saveAudioDataToWav(audioChunk.copyOfRange(5, audioChunk.size)) + } + // 发送成功后才推进发送节拍统计与声道包序 + if (audioChunk.size >= 5) { + val now = System.currentTimeMillis() + val deltaMs = if (lastAudioSendTime < 0) 0 else now - lastAudioSendTime + lastAudioSendTime = now + // 按声道统计各自的包序增量:正常应为 +1,出现非 +1 即说明该声道丢包或乱序 + val seqGap = when (channelByte) { + OpusAudioManager.CHANNEL_LEFT -> { + val gap = if (lastLeftSeq < 0) 1L else seq.toLong() - lastLeftSeq + lastLeftSeq = seq.toLong() + gap + } + OpusAudioManager.CHANNEL_RIGHT -> { + val gap = if (lastRightSeq < 0) 1L else seq.toLong() - lastRightSeq + lastRightSeq = seq.toLong() + gap + } + else -> 0L + } + val gapTag = if (seqGap == 1L) "" else " !gap=$seqGap" + Log.i(TAG, "音频下行发送 ch=$chTag seq=$seq Δ=${deltaMs}ms size=${audioChunk.size}B " + + "lastSeq(L=$lastLeftSeq,R=$lastRightSeq)$gapTag " + + "hold(L=${leftHoldBuffer.size},R=${rightHoldBuffer.size}) queue=${audioSendQueue.size}") + } + + // 固定节拍:睡到 nextSendAt 这个绝对时刻,把 poll/发送耗时算进同一周期,间隔不随它们抖动。 + val nowMs = System.currentTimeMillis() + if (nextSendAt == 0L) nextSendAt = nowMs + nextSendAt += audioSendIntervalNormal + val sleepMs = nextSendAt - nowMs + if (sleepMs > 0) { + Thread.sleep(sleepMs) + } else { + // 落后于节拍(发送/积压处理超时),重新对齐,避免之后疯狂追发 + nextSendAt = System.currentTimeMillis() } - Log.i(TAG, "音频下行发送 ch=$chTag seq=$seq size=${audioChunk.size}B " + - "hold(L=${leftHoldBuffer.size},R=${rightHoldBuffer.size}) queue=${audioSendQueue.size}") } - sendAudioChunkWithCallback(audioChunk) - Thread.sleep(AUDIO_SEND_INTERVAL_NORMAL) } } catch (e: InterruptedException) { Log.d(TAG, "音频发送线程被中断") @@ -1459,11 +1542,43 @@ object BleService { audioBuffer.clear() // 清空音频缓冲区 leftHoldBuffer.clear() // 清空左声道积压缓存 rightHoldBuffer.clear() // 清空右声道积压缓存 - leftChannelSleepUntil = 0L - rightChannelSleepUntil = 0L + leftChannelSleeping = false + rightChannelSleeping = false + lastAudioSendTime = -1L // 重置发送节拍计时,下次首包 Δ 从 0 开始 + lastLeftSeq = -1L // 重置左右声道包序追踪 + lastRightSeq = -1L + writeFailRetryCount = 0 // 重置写入拥塞计数 Log.i(TAG, "音频发送线程已停止,队列和缓冲区已清空") } + /** + * 同步发送一包音频数据,返回 writeCharacteristic 的真实结果。 + * 直接在发送线程调用 writeCharacteristic(GATT 写允许在工作线程执行), + * 这样发送线程能拿到 true/false 作为背压信号: + * - true : 已成功塞入底层发送缓冲 + * - false : 底层发送缓冲已满(拥塞) 或 连接异常 → 调用方应放回队头重试,不丢包 + * 写类型为 WRITE_TYPE_NO_RESPONSE(不等对端 ACK),保证吞吐。 + * @return 是否成功写入 + */ + private fun sendAudioChunkBlocking(chunk: ByteArray): Boolean { + return try { + val gatt = bluetoothGatt + val ch = callWriteChar + if (ch == null || gatt == null) { + Log.e(TAG, "蓝牙连接或特征值未准备就绪") + false + } else { + ch.value = chunk + val ok = gatt.writeCharacteristic(ch) == true + if (ok) bytesSentInCurrentSecond += chunk.size + ok + } + } catch (e: Exception) { + Log.e(TAG, "发送音频数据块异常: ${e.message}", e) + false + } + } + /** * 发送音频数据块并等待写入回调(新方法) * @param chunk 要发送的音频数据块 @@ -1592,6 +1707,37 @@ object BleService { Log.e(TAG, failureReason.toString()) } + // ====================================================================================================== + // 通话翻译调试参数 - 运行时可调 + // ====================================================================================================== + + /** + * 设置通话翻译调试参数(运行时生效,无需重连)。 + * @param sendIntervalMs 音频下行发送间隔(ms),null 或 <=0 表示不修改 + * @param bundleFrameCount 下行合包帧数,null 或 <=0 表示不修改 + */ + fun setCallTranslationDebugParams(sendIntervalMs: Long?, bundleFrameCount: Int?) { + if (sendIntervalMs != null && sendIntervalMs > 0) { + audioSendIntervalNormal = sendIntervalMs.coerceIn(1L, 1000L) + Log.i(TAG, "[CALL_TRANS_DEBUG] 设置音频下行发送间隔: ${audioSendIntervalNormal}ms") + } + if (bundleFrameCount != null && bundleFrameCount > 0) { + opusAudioManager.setBundleFrameCount(bundleFrameCount) + Log.i(TAG, "[CALL_TRANS_DEBUG] 设置下行合包帧数: ${opusAudioManager.getBundleFrameCount()}") + } + } + + /** + * 获取当前通话翻译调试参数。 + * @return Map: sendIntervalMs(Long) / bundleFrameCount(Int) + */ + fun getCallTranslationDebugParams(): Map { + return mapOf( + "sendIntervalMs" to audioSendIntervalNormal, + "bundleFrameCount" to opusAudioManager.getBundleFrameCount() + ) + } + // ====================================================================================================== // 设备控制方法 - 使用新的指令发送器 // ====================================================================================================== @@ -1997,21 +2143,20 @@ object BleService { /** * 收到设备主动上报的左/右声道通话休眠 (F0/F1) 时调用。 - * 将对应声道的休眠到期时间置为 now + CHANNEL_SLEEP_DURATION(1s), - * 休眠窗口内该声道的包转入积压缓存暂不发送、仅传输另一声道,到期后按序补发,不丢包。 - * 重复触发只是刷新到期时间,不会叠加。 + * 将对应声道置为休眠:该声道的包转入积压缓存暂不发送、仅传输另一声道, + * 直到收到对应的恢复指令 (F2/F3) 才恢复并按序补发,不丢包。 + * 重复触发幂等,不会叠加。 * @param channel BleConst.AUDIO_CHANNEL_LEFT(左) / BleConst.AUDIO_CHANNEL_RIGHT(右) */ private fun notifyCallSleepReceived(channel: Int) { - val until = SystemClock.elapsedRealtime() + CHANNEL_SLEEP_DURATION when (channel) { BleConst.AUDIO_CHANNEL_LEFT -> { - leftChannelSleepUntil = until - Log.i(TAG, "收到左声道休眠指令,暂停左声道下发 ${CHANNEL_SLEEP_DURATION}ms") + leftChannelSleeping = true + Log.i(TAG, "收到左声道休眠指令(F0),暂停左声道下发,等待恢复指令(F2)") } BleConst.AUDIO_CHANNEL_RIGHT -> { - rightChannelSleepUntil = until - Log.i(TAG, "收到右声道休眠指令,暂停右声道下发 ${CHANNEL_SLEEP_DURATION}ms") + rightChannelSleeping = true + Log.i(TAG, "收到右声道休眠指令(F1),暂停右声道下发,等待恢复指令(F3)") } else -> Log.w(TAG, "收到未知声道休眠指令: channel=$channel") } @@ -2023,6 +2168,33 @@ object BleService { } } } + + /** + * 收到设备主动上报的左/右声道通话恢复 (F2/F3) 时调用。 + * 解除对应声道的休眠标记,发送线程随即按 FIFO 顺序补发休眠期间积压的该声道包,不丢包。 + * 未处于休眠时收到恢复指令为幂等操作。 + * @param channel BleConst.AUDIO_CHANNEL_LEFT(左) / BleConst.AUDIO_CHANNEL_RIGHT(右) + */ + private fun notifyCallResumeReceived(channel: Int) { + when (channel) { + BleConst.AUDIO_CHANNEL_LEFT -> { + leftChannelSleeping = false + Log.i(TAG, "收到左声道恢复指令(F2),恢复左声道下发,补发积压 ${leftHoldBuffer.size} 包") + } + BleConst.AUDIO_CHANNEL_RIGHT -> { + rightChannelSleeping = false + Log.i(TAG, "收到右声道恢复指令(F3),恢复右声道下发,补发积压 ${rightHoldBuffer.size} 包") + } + else -> Log.w(TAG, "收到未知声道恢复指令: channel=$channel") + } + for (callback in callbacks) { + try { + callback.onCallResumeReceived(channel) + } catch (e: Exception) { + Log.e(TAG, "分发声道通话恢复回调异常", e) + } + } + } /** * 向所有回调监听器分发开始 AI 单次对话 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index ee015e109..7bb7d2563 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -230,6 +230,15 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, val success = BleService.playA2DP() result.success(success) } + "setCallTranslationDebugParams" -> { + val sendIntervalMs = (call.argument("sendIntervalMs"))?.toLong() + val bundleFrameCount = (call.argument("bundleFrameCount"))?.toInt() + BleService.setCallTranslationDebugParams(sendIntervalMs, bundleFrameCount) + result.success(true) + } + "getCallTranslationDebugParams" -> { + result.success(BleService.getCallTranslationDebugParams()) + } // 新增Companion Device相关方法 "isCompanionSupported" -> { val isSupported = bleCompanionManager.isCompanionDeviceManagerSupported() @@ -464,4 +473,18 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, "发送声道通话休眠事件异常" ) } + + override fun onCallResumeReceived(channel: Int) { + val channelName = when (channel) { + BleConst.AUDIO_CHANNEL_LEFT -> "左声道" + BleConst.AUDIO_CHANNEL_RIGHT -> "右声道" + else -> "未知声道($channel)" + } + Log.i(TAG, "[Flutter推送] 声道通话恢复: $channelName (channel=$channel)") + sendEvent( + statusEventSink, + mapOf("type" to "callResume", "channel" to channel), + "发送声道通话恢复事件异常" + ) + } } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OpusAudioManager.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OpusAudioManager.kt index 520f5f5ac..fa0c639bc 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OpusAudioManager.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/OpusAudioManager.kt @@ -42,8 +42,11 @@ class OpusAudioManager { const val CHANNEL_LEFT: Byte = 0 const val CHANNEL_RIGHT: Byte = 1 - // 下行合包:左右各自累积 BUNDLE_FRAME_COUNT 帧 opus 合并为 1 包后才下发,降低 BLE 发送频率 - private const val BUNDLE_FRAME_COUNT = 4 + // 下行合包默认帧数:左右各自累积 bundleFrameCount 帧 opus 合并为 1 包后才下发,降低 BLE 发送频率 + // 调试界面可在运行时调整;通过 setBundleFrameCount() 修改 + const val DEFAULT_BUNDLE_FRAME_COUNT = 4 + @Volatile + private var bundleFrameCount = DEFAULT_BUNDLE_FRAME_COUNT // 合包头部:bytes 0-3 序号(小端 uint32),byte 4 声道标识(0=左/1=右) private const val BUNDLE_HEADER_SIZE = 5 } @@ -108,10 +111,10 @@ class OpusAudioManager { private var rightEncodeManager: OpusManager? = null private var isDualEncoding = false - // 左右各自的 opus 帧缓存与发送序号;累满 BUNDLE_FRAME_COUNT 帧后打包下发 + // 左右各自的 opus 帧缓存与发送序号;累满 bundleFrameCount 帧后打包下发 private val bundleLock = Any() - private val leftBundleFrames = ArrayList(BUNDLE_FRAME_COUNT) - private val rightBundleFrames = ArrayList(BUNDLE_FRAME_COUNT) + private val leftBundleFrames = ArrayList(DEFAULT_BUNDLE_FRAME_COUNT) + private val rightBundleFrames = ArrayList(DEFAULT_BUNDLE_FRAME_COUNT) private var leftBundleSeq = 0 private var rightBundleSeq = 0 @@ -435,7 +438,7 @@ class OpusAudioManager { // ====================================================================================================== // 双声道独立编码方法(左右声道各用独立 OpusManager,互不污染预测状态) // 输出格式:每包 = [4B 序号(小端 uint32)] + [1B 声道(0=左/1=右)] + [N × opus 帧] - // 左右各自累积 BUNDLE_FRAME_COUNT 帧后打包一次,通过 onAudioDataEncoded 下发 + // 左右各自累积 bundleFrameCount 帧后打包一次,通过 onAudioDataEncoded 下发 // ====================================================================================================== /** @@ -535,7 +538,7 @@ class OpusAudioManager { /** * 写入左声道 mono PCM(本端翻译音频) - * 累满 BUNDLE_FRAME_COUNT 帧 opus 后通过 onAudioDataEncoded 下发:[4B 序号] + [1B 声道=0] + [N × opus] + * 累满 bundleFrameCount 帧 opus 后通过 onAudioDataEncoded 下发:[4B 序号] + [1B 声道=0] + [N × opus] */ fun writeLeftPcm(pcm: ByteArray) { if (!isDualEncoding) { @@ -548,7 +551,7 @@ class OpusAudioManager { /** * 写入右声道 mono PCM(对端翻译音频) - * 累满 BUNDLE_FRAME_COUNT 帧 opus 后通过 onAudioDataEncoded 下发:[4B 序号] + [1B 声道=1] + [N × opus] + * 累满 bundleFrameCount 帧 opus 后通过 onAudioDataEncoded 下发:[4B 序号] + [1B 声道=1] + [N × opus] */ fun writeRightPcm(pcm: ByteArray) { if (!isDualEncoding) { @@ -560,15 +563,32 @@ class OpusAudioManager { } /** - * 单帧入合包缓冲;左右各自累满 BUNDLE_FRAME_COUNT 帧后整包通过回调下发 + * 设置下行合包帧数(调试用)。取值范围 1..20,超出自动钳制。 + * 立即生效:后续累满新帧数即打包下发,已缓存的不足帧待补满后随新值打包。 + * @param count 每包累积的 opus 帧数 + */ + fun setBundleFrameCount(count: Int) { + val clamped = count.coerceIn(1, 20) + bundleFrameCount = clamped + Log.i(TAG, "设置下行合包帧数: $clamped") + } + + /** + * 获取当前下行合包帧数 + */ + fun getBundleFrameCount(): Int = bundleFrameCount + + /** + * 单帧入合包缓冲;左右各自累满 bundleFrameCount 帧后整包通过回调下发 * 输出包布局:[seq(4B 小端 uint32)] + [channel(1B)] + [N 帧 opus 顺次拼接] */ private fun emitChannelFrame(channelPrefix: Byte, opusFrame: ByteArray) { + val frameCount = bundleFrameCount val packet: ByteArray? = synchronized(bundleLock) { when (channelPrefix) { CHANNEL_LEFT -> { leftBundleFrames.add(opusFrame) - if (leftBundleFrames.size >= BUNDLE_FRAME_COUNT) { + if (leftBundleFrames.size >= frameCount) { val p = buildBundlePacket(leftBundleSeq, CHANNEL_LEFT, leftBundleFrames) leftBundleSeq++ leftBundleFrames.clear() @@ -577,7 +597,7 @@ class OpusAudioManager { } CHANNEL_RIGHT -> { rightBundleFrames.add(opusFrame) - if (rightBundleFrames.size >= BUNDLE_FRAME_COUNT) { + if (rightBundleFrames.size >= frameCount) { val p = buildBundlePacket(rightBundleSeq, CHANNEL_RIGHT, rightBundleFrames) rightBundleSeq++ rightBundleFrames.clear() diff --git a/local_plugins/ble_service/lib/ble_service.dart b/local_plugins/ble_service/lib/ble_service.dart index 52c1ada13..2fbe25f60 100644 --- a/local_plugins/ble_service/lib/ble_service.dart +++ b/local_plugins/ble_service/lib/ble_service.dart @@ -172,6 +172,43 @@ class BleService { } } + /// 设置通话翻译调试参数(运行时生效) + /// [sendIntervalMs] 音频下行发送间隔(ms) + /// [bundleFrameCount] 下行合包帧数 + Future setCallTranslationDebugParams({ + int? sendIntervalMs, + int? bundleFrameCount, + }) async { + try { + final result = await _methodChannel + .invokeMethod('setCallTranslationDebugParams', { + if (sendIntervalMs != null) 'sendIntervalMs': sendIntervalMs, + if (bundleFrameCount != null) 'bundleFrameCount': bundleFrameCount, + }); + return result ?? false; + } catch (e) { + print('设置通话翻译调试参数失败: $e'); + return false; + } + } + + /// 获取当前通话翻译调试参数 + /// 返回 {'sendIntervalMs': int, 'bundleFrameCount': int},失败返回空 Map + Future> getCallTranslationDebugParams() async { + try { + final result = await _methodChannel + .invokeMethod('getCallTranslationDebugParams'); + if (result == null) return {}; + return { + 'sendIntervalMs': (result['sendIntervalMs'] as num?)?.toInt() ?? 0, + 'bundleFrameCount': (result['bundleFrameCount'] as num?)?.toInt() ?? 0, + }; + } catch (e) { + print('获取通话翻译调试参数失败: $e'); + return {}; + } + } + /// 启动BLE后台服务 Future startBleService(Map config) async { diff --git a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt b/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt index 27e990208..b1dc9179a 100644 --- a/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt +++ b/local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt @@ -415,4 +415,7 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler { override fun onCallSleepReceived(channel: Int) { } + override fun onCallResumeReceived(channel: Int) { + } + } diff --git a/pubspec.lock b/pubspec.lock index 1d28bdc0b..87afceb40 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: _fe_analyzer_shared sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "93.0.0" _flutterfire_internals: @@ -14,7 +14,7 @@ packages: description: name: _flutterfire_internals sha256: "0d1f0adfabbab9f46a1a80ce84a4d8b852b6e4dbf53ce413b30e0cf7d3631b71" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.72" agent_service: @@ -36,7 +36,7 @@ packages: description: name: analyzer sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "10.0.1" app_links: @@ -44,7 +44,7 @@ packages: description: name: app_links sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.0" app_links_linux: @@ -52,7 +52,7 @@ packages: description: name: app_links_linux sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" app_links_platform_interface: @@ -60,7 +60,7 @@ packages: description: name: app_links_platform_interface sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" app_links_web: @@ -68,7 +68,7 @@ packages: description: name: app_links_web sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" app_settings: @@ -76,7 +76,7 @@ packages: description: name: app_settings sha256: "476df1d85cec143c3d27dd1c7451629a59c0c5ccf70a0adcbfa92a0a2d928705" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.2.0" app_tracking_transparency: @@ -84,7 +84,7 @@ packages: description: name: app_tracking_transparency sha256: "3a52669c0645ffd7efe9a460047616de3e3f2128338f9ec7cdb681628d36238e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.7" archive: @@ -92,7 +92,7 @@ packages: description: name: archive sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.9" args: @@ -100,7 +100,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: @@ -108,7 +108,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" audio_session: @@ -116,7 +116,7 @@ packages: description: name: audio_session sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.25" audio_waveforms: @@ -124,7 +124,7 @@ packages: description: name: audio_waveforms sha256: "658fef41bbab299184b65ba2fd749e8ec658c1f7d54a21f7cf97fa96b173b4ce" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" azure_speech: @@ -139,7 +139,7 @@ packages: description: name: barcode sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.9" bidi: @@ -147,7 +147,7 @@ packages: description: name: bidi sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.13" ble_service: @@ -162,7 +162,7 @@ packages: description: name: bluez sha256: "61a7204381925896a374301498f2f5399e59827c6498ae1e924aaa598751b545" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.3" boolean_selector: @@ -170,7 +170,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" build: @@ -178,7 +178,7 @@ packages: description: name: build sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.6" build_config: @@ -186,7 +186,7 @@ packages: description: name: build_config sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" build_daemon: @@ -194,7 +194,7 @@ packages: description: name: build_daemon sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.1" build_runner: @@ -202,7 +202,7 @@ packages: description: name: build_runner sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.15.0" built_collection: @@ -210,7 +210,7 @@ packages: description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" built_value: @@ -218,7 +218,7 @@ packages: description: name: built_value sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.12.6" characters: @@ -226,7 +226,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" chat_api: @@ -248,7 +248,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" classic_bluetooth: @@ -263,7 +263,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" code_assets: @@ -271,7 +271,7 @@ packages: description: name: code_assets sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" collection: @@ -279,7 +279,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" common_utils: @@ -287,7 +287,7 @@ packages: description: name: common_utils sha256: c26884339b13ff99b0739e56f4b02090c84054ed9dd3a045435cd24e7b99c2c1 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" connectivity_plus: @@ -295,7 +295,7 @@ packages: description: name: connectivity_plus sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.5" connectivity_plus_platform_interface: @@ -303,7 +303,7 @@ packages: description: name: connectivity_plus_platform_interface sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" convert: @@ -311,7 +311,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" cross_file: @@ -319,7 +319,7 @@ packages: description: name: cross_file sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.5+2" crypto: @@ -327,7 +327,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" cryptography: @@ -335,7 +335,7 @@ packages: description: name: cryptography sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.9.0" csslib: @@ -343,7 +343,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" cupertino_icons: @@ -351,7 +351,7 @@ packages: description: name: cupertino_icons sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.9" dart_style: @@ -359,7 +359,7 @@ packages: description: name: dart_style sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.7" dbus: @@ -367,7 +367,7 @@ packages: description: name: dbus sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.14" decimal: @@ -375,7 +375,7 @@ packages: description: name: decimal sha256: fc706a5618b81e5b367b01dd62621def37abc096f2b46a9bd9068b64c1fa36d0 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.4" device_info_plus: @@ -383,7 +383,7 @@ packages: description: name: device_info_plus sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.5.0" device_info_plus_platform_interface: @@ -391,7 +391,7 @@ packages: description: name: device_info_plus_platform_interface sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.3" device_marketing_names: @@ -399,7 +399,7 @@ packages: description: name: device_marketing_names sha256: c70685986d820170105de0cff61461caf31d2085de2339890910cb242489a1c7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.7" diff_match_patch: @@ -407,7 +407,7 @@ packages: description: name: diff_match_patch sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.1" dio: @@ -415,7 +415,7 @@ packages: description: name: dio sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.9.2" dio_web_adapter: @@ -423,7 +423,7 @@ packages: description: name: dio_web_adapter sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" fake_async: @@ -431,7 +431,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -439,7 +439,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" file: @@ -447,7 +447,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" file_picker: @@ -455,7 +455,7 @@ packages: description: name: file_picker sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "10.3.10" file_selector_linux: @@ -463,7 +463,7 @@ packages: description: name: file_selector_linux sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.4" file_selector_macos: @@ -471,7 +471,7 @@ packages: description: name: file_selector_macos sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.5" file_selector_platform_interface: @@ -479,7 +479,7 @@ packages: description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" file_selector_windows: @@ -487,7 +487,7 @@ packages: description: name: file_selector_windows sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.3+5" firebase_auth: @@ -495,7 +495,7 @@ packages: description: name: firebase_auth sha256: "9905a315f1235a649e29df19b246adde7350a11234837cd605254b8e25144711" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.5.2" firebase_auth_platform_interface: @@ -503,7 +503,7 @@ packages: description: name: firebase_auth_platform_interface sha256: f757dc5636ce2b204a82383f7246ef0d9c925f9e96c8c9a4a6c315d673d662bb - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.0.2" firebase_auth_web: @@ -511,7 +511,7 @@ packages: description: name: firebase_auth_web sha256: "1c44330eef3c82068e0c2919b6b015897d49211dcccdf64f90a2ed73ce216ecb" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.2.2" firebase_core: @@ -519,7 +519,7 @@ packages: description: name: firebase_core sha256: ec46a100a560d3bd5f97f2d89ba7492cb09b6dd0a4a28753d1258f360d6bd9f9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.10.0" firebase_core_platform_interface: @@ -527,7 +527,7 @@ packages: description: name: firebase_core_platform_interface sha256: "4a120366dbf7d5a8ee9438978530b664b855728fb8dcc3a201017660817e555b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" firebase_core_web: @@ -535,7 +535,7 @@ packages: description: name: firebase_core_web sha256: "5ad1be848692ec148f2d6a8ad2a3838cb852ea5f3c9e6479a7afce479e1854f8" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.8.0" fixnum: @@ -543,7 +543,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" fleather: @@ -551,7 +551,7 @@ packages: description: name: fleather sha256: baa1cbba5de755adf8114eac3b8adf352f06a18e55d1d58636e9c10a0f86aed5 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.26.0" floating_ui_plugin: @@ -571,7 +571,7 @@ packages: description: name: flutter_blue_plus sha256: "69a8c87c11fc792e8cf0f997d275484fbdb5143ac9f0ac4d424429700cb4e0ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.36.8" flutter_blue_plus_android: @@ -579,7 +579,7 @@ packages: description: name: flutter_blue_plus_android sha256: "6f7fe7e69659c30af164a53730707edc16aa4d959e4c208f547b893d940f853d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.4" flutter_blue_plus_darwin: @@ -587,7 +587,7 @@ packages: description: name: flutter_blue_plus_darwin sha256: "682982862c1d964f4d54a3fb5fccc9e59a066422b93b7e22079aeecd9c0d38f8" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.3" flutter_blue_plus_linux: @@ -595,7 +595,7 @@ packages: description: name: flutter_blue_plus_linux sha256: "56b0c45edd0a2eec8f85bd97a26ac3cd09447e10d0094fed55587bf0592e3347" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.3" flutter_blue_plus_platform_interface: @@ -603,7 +603,7 @@ packages: description: name: flutter_blue_plus_platform_interface sha256: "84fbd180c50a40c92482f273a92069960805ce324e3673ad29c41d2faaa7c5c2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.0" flutter_blue_plus_web: @@ -611,7 +611,7 @@ packages: description: name: flutter_blue_plus_web sha256: a1aceee753d171d24c0e0cdadb37895b5e9124862721f25f60bb758e20b72c99 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.2" flutter_dotenv: @@ -619,7 +619,7 @@ packages: description: name: flutter_dotenv sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.2.1" flutter_easyloading: @@ -627,7 +627,7 @@ packages: description: name: flutter_easyloading sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.5" flutter_image_compress: @@ -635,7 +635,7 @@ packages: description: name: flutter_image_compress sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" flutter_image_compress_common: @@ -643,7 +643,7 @@ packages: description: name: flutter_image_compress_common sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.6" flutter_image_compress_macos: @@ -651,7 +651,7 @@ packages: description: name: flutter_image_compress_macos sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" flutter_image_compress_ohos: @@ -659,7 +659,7 @@ packages: description: name: flutter_image_compress_ohos sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.3" flutter_image_compress_platform_interface: @@ -667,7 +667,7 @@ packages: description: name: flutter_image_compress_platform_interface sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" flutter_image_compress_web: @@ -675,7 +675,7 @@ packages: description: name: flutter_image_compress_web sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.5" flutter_intro: @@ -683,7 +683,7 @@ packages: description: name: flutter_intro sha256: f8cee0d0d03d60f076c342073228a8a1f43df5e99eade30bcf895af6e045008c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.4.0" flutter_lints: @@ -691,7 +691,7 @@ packages: description: name: flutter_lints sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" flutter_local_notifications: @@ -699,7 +699,7 @@ packages: description: name: flutter_local_notifications sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "18.0.1" flutter_local_notifications_linux: @@ -707,7 +707,7 @@ packages: description: name: flutter_local_notifications_linux sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" flutter_local_notifications_platform_interface: @@ -715,7 +715,7 @@ packages: description: name: flutter_local_notifications_platform_interface sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.0.0" flutter_localizations: @@ -728,7 +728,7 @@ packages: description: name: flutter_markdown sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.7+1" flutter_plugin_android_lifecycle: @@ -736,7 +736,7 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.35" flutter_screenutil: @@ -744,7 +744,7 @@ packages: description: name: flutter_screenutil sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.9.3" flutter_shaders: @@ -752,7 +752,7 @@ packages: description: name: flutter_shaders sha256: "1cf98f699701b520ed029c144f22c478c1f37f5784a4789b61d4fdbe95350a56" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.6" flutter_spinkit: @@ -760,7 +760,7 @@ packages: description: name: flutter_spinkit sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.2.2" flutter_staggered_grid_view: @@ -768,7 +768,7 @@ packages: description: name: flutter_staggered_grid_view sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.0" flutter_svg: @@ -776,7 +776,7 @@ packages: description: name: flutter_svg sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" flutter_test: @@ -789,7 +789,7 @@ packages: description: name: flutter_tts sha256: ce5eb209b40e95f2f4a1397116c87ab2fcdff32257d04ed7a764e75894c03775 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.5" flutter_web_plugins: @@ -802,7 +802,7 @@ packages: description: name: fluwx sha256: "7e92d2000ee49c5262a88c51ea2d22b91a753d5b29df27cc264bb0a115d65373" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.7.5" font_awesome_flutter: @@ -810,7 +810,7 @@ packages: description: name: font_awesome_flutter sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "10.12.0" get: @@ -818,7 +818,7 @@ packages: description: name: get sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.7.3" get_storage: @@ -826,7 +826,7 @@ packages: description: name: get_storage sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" glob: @@ -834,7 +834,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" google_identity_services_web: @@ -842,7 +842,7 @@ packages: description: name: google_identity_services_web sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.3+1" google_sign_in: @@ -850,7 +850,7 @@ packages: description: name: google_sign_in sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.0" google_sign_in_android: @@ -858,7 +858,7 @@ packages: description: name: google_sign_in_android sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.2.1" google_sign_in_ios: @@ -866,7 +866,7 @@ packages: description: name: google_sign_in_ios sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.9.0" google_sign_in_platform_interface: @@ -874,7 +874,7 @@ packages: description: name: google_sign_in_platform_interface sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.0" google_sign_in_web: @@ -882,7 +882,7 @@ packages: description: name: google_sign_in_web sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.4+4" graphs: @@ -890,7 +890,7 @@ packages: description: name: graphs sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" gtk: @@ -898,7 +898,7 @@ packages: description: name: gtk sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" haptic_feedback: @@ -906,7 +906,7 @@ packages: description: name: haptic_feedback sha256: "3422bf2a55c541c9e3f029197b9a94d262d2dda19191a022eb310e5dd922e9e0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.1+2" hooks: @@ -914,7 +914,7 @@ packages: description: name: hooks sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" html: @@ -922,7 +922,7 @@ packages: description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.6" htmltopdfwidgets: @@ -930,7 +930,7 @@ packages: description: name: htmltopdfwidgets sha256: e2c42dc784fa884eee0f29bbced6555a1a4cb0a3171c7d334cb5ca64a3854d77 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.3" http: @@ -938,7 +938,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" http_multi_server: @@ -946,7 +946,7 @@ packages: description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" http_parser: @@ -954,7 +954,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" image: @@ -962,7 +962,7 @@ packages: description: name: image sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.8.0" image_gallery_saver_plus: @@ -970,7 +970,7 @@ packages: description: name: image_gallery_saver_plus sha256: "199b9e24f8d85e98f11e3d35571ab68ae50626ad40e2bb85c84383f69a6950ad" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.1" image_picker: @@ -978,7 +978,7 @@ packages: description: name: image_picker sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" image_picker_android: @@ -986,7 +986,7 @@ packages: description: name: image_picker_android sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.13+17" image_picker_for_web: @@ -994,7 +994,7 @@ packages: description: name: image_picker_for_web sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" image_picker_ios: @@ -1002,7 +1002,7 @@ packages: description: name: image_picker_ios sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.13+6" image_picker_linux: @@ -1010,7 +1010,7 @@ packages: description: name: image_picker_linux sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" image_picker_macos: @@ -1018,7 +1018,7 @@ packages: description: name: image_picker_macos sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2+1" image_picker_platform_interface: @@ -1026,7 +1026,7 @@ packages: description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.11.1" image_picker_windows: @@ -1034,7 +1034,7 @@ packages: description: name: image_picker_windows sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" in_app_purchase: @@ -1042,7 +1042,7 @@ packages: description: name: in_app_purchase sha256: ad59b45d998868b8ace0f606e09b348ffd888952a3cf8ff9f7956aaf4844e6e2 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.4" in_app_purchase_android: @@ -1050,7 +1050,7 @@ packages: description: name: in_app_purchase_android sha256: "518b48a385dfc1aa278080472969c6cee0c3d41ef8b9511b0071dd015473ecf1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0+11" in_app_purchase_platform_interface: @@ -1058,7 +1058,7 @@ packages: description: name: in_app_purchase_platform_interface sha256: "0b0076cac8ce4fa7048f01e76af8b123aeb6a7c4e0dea2a5206d6664454f3e36" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" in_app_purchase_storekit: @@ -1066,7 +1066,7 @@ packages: description: name: in_app_purchase_storekit sha256: "5f9d59c86c15f56429a4fdf09097c99d5b412510e1fcf80cf874fc9638fab369" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.10" intl: @@ -1074,7 +1074,7 @@ packages: description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.20.2" io: @@ -1082,7 +1082,7 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" jl_opus: @@ -1097,7 +1097,7 @@ packages: description: name: jni sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" jni_flutter: @@ -1105,7 +1105,7 @@ packages: description: name: jni_flutter sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" json_annotation: @@ -1113,7 +1113,7 @@ packages: description: name: json_annotation sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.12.0" json_serializable: @@ -1121,7 +1121,7 @@ packages: description: name: json_serializable sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.14.0" just_audio: @@ -1129,7 +1129,7 @@ packages: description: name: just_audio sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.46" just_audio_platform_interface: @@ -1137,7 +1137,7 @@ packages: description: name: just_audio_platform_interface sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.6.0" just_audio_web: @@ -1145,7 +1145,7 @@ packages: description: name: just_audio_web sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.16" leak_tracker: @@ -1153,7 +1153,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -1161,7 +1161,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -1169,7 +1169,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -1177,7 +1177,7 @@ packages: description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" location_service: @@ -1192,7 +1192,7 @@ packages: description: name: logger sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" logging: @@ -1200,7 +1200,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" lottie: @@ -1208,7 +1208,7 @@ packages: description: name: lottie sha256: "8b6359a7422167014aa73ce763fa133fb832065dcc0ac4d1dec1f603a5cef7d0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.3.3" markdown: @@ -1216,7 +1216,7 @@ packages: description: name: markdown sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.3.1" matcher: @@ -1224,7 +1224,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -1232,7 +1232,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -1240,7 +1240,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mime: @@ -1248,7 +1248,7 @@ packages: description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" music_service: @@ -1263,7 +1263,7 @@ packages: description: name: nm sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.0" objective_c: @@ -1271,7 +1271,7 @@ packages: description: name: objective_c sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.4.1" open_ai_service: @@ -1293,7 +1293,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" package_info_plus: @@ -1301,7 +1301,7 @@ packages: description: name: package_info_plus sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.3.1" package_info_plus_platform_interface: @@ -1309,7 +1309,7 @@ packages: description: name: package_info_plus_platform_interface sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.1" parchment: @@ -1317,7 +1317,7 @@ packages: description: name: parchment sha256: "1af4dd4a39034452aecabac2baf4cb2a5248fc4467b6ba48cd5e11e533e00875" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.27.0" parchment_delta: @@ -1325,7 +1325,7 @@ packages: description: name: parchment_delta sha256: "8b360d0138eb591c7fe193670e2d05466e482792af9c5b4d3f568c5c61530f08" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" path: @@ -1333,7 +1333,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_parsing: @@ -1341,7 +1341,7 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" path_provider: @@ -1349,7 +1349,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.5" path_provider_android: @@ -1357,7 +1357,7 @@ packages: description: name: path_provider_android sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" path_provider_foundation: @@ -1365,7 +1365,7 @@ packages: description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" path_provider_linux: @@ -1373,7 +1373,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -1381,7 +1381,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" path_provider_windows: @@ -1389,7 +1389,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" pdf: @@ -1397,7 +1397,7 @@ packages: description: name: pdf sha256: e47a275b267873d5944ad5f5ff0dcc7ac2e36c02b3046a0ffac9b72fd362c44b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.12.0" permission_handler: @@ -1405,7 +1405,7 @@ packages: description: name: permission_handler sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "12.0.3" permission_handler_android: @@ -1413,7 +1413,7 @@ packages: description: name: permission_handler_android sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "13.0.1" permission_handler_apple: @@ -1421,7 +1421,7 @@ packages: description: name: permission_handler_apple sha256: e20daf680eef1ca62ffe8c8c526b778cc386d50137c77ac71c8ec9c88c13fb9d - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.4.9" permission_handler_html: @@ -1429,7 +1429,7 @@ packages: description: name: permission_handler_html sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3+5" permission_handler_platform_interface: @@ -1437,7 +1437,7 @@ packages: description: name: permission_handler_platform_interface sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.3.0" permission_handler_windows: @@ -1445,7 +1445,7 @@ packages: description: name: permission_handler_windows sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" petitparser: @@ -1453,7 +1453,7 @@ packages: description: name: petitparser sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.2" photo_view: @@ -1461,7 +1461,7 @@ packages: description: name: photo_view sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.0" pip: @@ -1469,7 +1469,7 @@ packages: description: name: pip sha256: f3c0890458a1e7ea3597fd2432841fb82565d66e4aa5b4931326a2ec052b3505 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.3" platform: @@ -1477,7 +1477,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -1485,7 +1485,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" pool: @@ -1493,7 +1493,7 @@ packages: description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.2" posix: @@ -1501,7 +1501,7 @@ packages: description: name: posix sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.5.0" pub_semver: @@ -1509,7 +1509,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" pubspec_parse: @@ -1517,7 +1517,7 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" qq_music: @@ -1532,7 +1532,7 @@ packages: description: name: qr sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" quiver: @@ -1540,7 +1540,7 @@ packages: description: name: quiver sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" rational: @@ -1548,7 +1548,7 @@ packages: description: name: rational sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.3" realtime: @@ -1563,7 +1563,7 @@ packages: description: name: record sha256: "10911465138fafacef459a780564e883e01bd48eabf87ab20543684884492870" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.2.1" record_android: @@ -1571,7 +1571,7 @@ packages: description: name: record_android sha256: eb1732e42d0d2a1895b8db86e4fc917287e6d8491b6ed59918aea8bed6c69de4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.2" record_ios: @@ -1579,7 +1579,7 @@ packages: description: name: record_ios sha256: c051fb48edd7a0e265daafb9108730dc827c27b551728a3fdfb3ef69efd89c73 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" record_linux: @@ -1587,7 +1587,7 @@ packages: description: name: record_linux sha256: "31181787bf7eccb0e298835836b69b3cd0a903863b75d70e937de3dec71cd8f3" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" record_macos: @@ -1595,7 +1595,7 @@ packages: description: name: record_macos sha256: cfe1b61435e27db418bf513dc36820d10c9f7eb1843786c2c9a52e07e2f4f627 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" record_platform_interface: @@ -1603,7 +1603,7 @@ packages: description: name: record_platform_interface sha256: "8e56cbe06c6984137fb86132ff03459f29938d927496d9b2d0962e2d6345d488" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" record_use: @@ -1611,7 +1611,7 @@ packages: description: name: record_use sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.0" record_web: @@ -1619,7 +1619,7 @@ packages: description: name: record_web sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" record_windows: @@ -1627,7 +1627,7 @@ packages: description: name: record_windows sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.7" rxdart: @@ -1635,7 +1635,7 @@ packages: description: name: rxdart sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.28.0" share_plus: @@ -1643,7 +1643,7 @@ packages: description: name: share_plus sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "12.0.2" share_plus_platform_interface: @@ -1651,7 +1651,7 @@ packages: description: name: share_plus_platform_interface sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.0" shelf: @@ -1659,7 +1659,7 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.2" shelf_web_socket: @@ -1667,7 +1667,7 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" sign_in_with_apple: @@ -1675,7 +1675,7 @@ packages: description: name: sign_in_with_apple sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" sign_in_with_apple_platform_interface: @@ -1683,7 +1683,7 @@ packages: description: name: sign_in_with_apple_platform_interface sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" sign_in_with_apple_web: @@ -1691,7 +1691,7 @@ packages: description: name: sign_in_with_apple_web sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" sky_engine: @@ -1704,7 +1704,7 @@ packages: description: name: source_gen sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.3" source_helper: @@ -1712,7 +1712,7 @@ packages: description: name: source_helper sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.12" source_span: @@ -1720,7 +1720,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" speech: @@ -1742,7 +1742,7 @@ packages: description: name: sqflite sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2+1" sqflite_android: @@ -1750,7 +1750,7 @@ packages: description: name: sqflite_android sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2+3" sqflite_common: @@ -1758,7 +1758,7 @@ packages: description: name: sqflite_common sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.8" sqflite_darwin: @@ -1766,7 +1766,7 @@ packages: description: name: sqflite_darwin sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2" sqflite_platform_interface: @@ -1774,7 +1774,7 @@ packages: description: name: sqflite_platform_interface sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" stack_trace: @@ -1782,7 +1782,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -1790,7 +1790,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" stream_transform: @@ -1798,7 +1798,7 @@ packages: description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" string_scanner: @@ -1806,7 +1806,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" synchronized: @@ -1814,7 +1814,7 @@ packages: description: name: synchronized sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.4.0+1" tencentcloud_cos_sdk_plugin: @@ -1822,7 +1822,7 @@ packages: description: name: tencentcloud_cos_sdk_plugin sha256: "5f7f30f75c31eefd0be14287a07197d155f1a18c634b5d3d1d0fed3e01e8ea69" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" term_glyph: @@ -1830,7 +1830,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test: @@ -1845,7 +1845,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" timezone: @@ -1853,7 +1853,7 @@ packages: description: name: timezone sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.10.1" tobias: @@ -1861,7 +1861,7 @@ packages: description: name: tobias sha256: "1cfd203bf57f8d4daa3e734d24ef97568eb5265af62d1498c32da4e69905c70f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.3.4" typed_data: @@ -1869,7 +1869,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" universal_platform: @@ -1877,7 +1877,7 @@ packages: description: name: universal_platform sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" url_launcher: @@ -1885,7 +1885,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.2" url_launcher_android: @@ -1893,7 +1893,7 @@ packages: description: name: url_launcher_android sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.30" url_launcher_ios: @@ -1901,7 +1901,7 @@ packages: description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.4.1" url_launcher_linux: @@ -1909,7 +1909,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" url_launcher_macos: @@ -1917,7 +1917,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -1925,7 +1925,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" url_launcher_web: @@ -1933,7 +1933,7 @@ packages: description: name: url_launcher_web sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" url_launcher_windows: @@ -1941,7 +1941,7 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.5" user_portrait: @@ -1956,7 +1956,7 @@ packages: description: name: uuid sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.5.3" vector_graphics: @@ -1964,7 +1964,7 @@ packages: description: name: vector_graphics sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" vector_graphics_codec: @@ -1972,7 +1972,7 @@ packages: description: name: vector_graphics_codec sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.13" vector_graphics_compiler: @@ -1980,7 +1980,7 @@ packages: description: name: vector_graphics_compiler sha256: "7ee12e6dffe0fc8e755179d6d91b3b34f5924223fc104d85572ef9180d73d172" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.5" vector_math: @@ -1988,7 +1988,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -1996,7 +1996,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" watcher: @@ -2004,7 +2004,7 @@ packages: description: name: watcher sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" web: @@ -2012,7 +2012,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" web_socket_channel: @@ -2020,7 +2020,7 @@ packages: description: name: web_socket_channel sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" webview_flutter: @@ -2028,7 +2028,7 @@ packages: description: name: webview_flutter sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.13.1" webview_flutter_android: @@ -2036,7 +2036,7 @@ packages: description: name: webview_flutter_android sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.12.0" webview_flutter_platform_interface: @@ -2044,7 +2044,7 @@ packages: description: name: webview_flutter_platform_interface sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.15.1" webview_flutter_wkwebview: @@ -2052,7 +2052,7 @@ packages: description: name: webview_flutter_wkwebview sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.25.1" wifi_iot: @@ -2060,7 +2060,7 @@ packages: description: name: wifi_iot sha256: "0861aed0c0afd6031b4337811d31cdd181c594a8a2c73e94826ea21d2cb4707b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.19+2" win32: @@ -2068,7 +2068,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" win32_registry: @@ -2076,7 +2076,7 @@ packages: description: name: win32_registry sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" window_size: @@ -2093,7 +2093,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" xml: @@ -2101,7 +2101,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.6.1" yaml: @@ -2109,7 +2109,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: