Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
liwei1dao 1 year ago
parent
commit
7de318cf4e
  1. 6
      lib/core/bindings/initial_binding.dart
  2. 5
      lib/core/utils/synchrodata.dart
  3. 456
      lib/modules/agent/controllers/agent_controller.dart
  4. 28
      lib/modules/agent/views/music_card.dart
  5. 15
      lib/modules/music/bindings/music_binding.dart
  6. 493
      lib/modules/music/controllers/music_controller.dart
  7. 125
      lib/modules/music/services/music_service_facade.dart
  8. 114
      lib/modules/music/views/music_playlist_view.dart
  9. 86
      lib/modules/settings/views/settings_view.dart
  10. 33
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  11. 6
      pubspec.yaml

6
lib/core/bindings/initial_binding.dart

@ -10,6 +10,8 @@ import '../../data/services/bluetooth_manager.dart';
import '../../data/services/ble_manager.dart';
import '../../data/services/account_service.dart';
import '../../data/services/version_update_service.dart';
import '../../modules/music/controllers/music_controller.dart';
import '../../modules/music/services/music_service_facade.dart';
/// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings {
@ -35,6 +37,10 @@ class InitialBinding extends Bindings {
Get.lazyPut<BleManager>(() => BleManager(), fenix: true);
// MusicManager
Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true);
// 音乐模块
Get.lazyPut<MusicController>(() => MusicController(), fenix: true);
Get.lazyPut<MusicServiceFacade>(() => MusicServiceFacade(), fenix: true);
Get.lazyPut<NavigationManager>(() => NavigationManager(), fenix: true);
Get.lazyPut<LocationManager>(() => LocationManager(), fenix: true);
// 火山AI服务

5
lib/core/utils/synchrodata.dart

@ -113,9 +113,10 @@ class Synchrodata {
// 同步数据
static Future _synchrodata() async {
final db = await DatabaseHelper().database;
var result = await Api.getOperationRecord();
List records = result['records'];
List records = result['records'] ?? [];
if (records.isEmpty) return;
final db = await DatabaseHelper().database;
await db.transaction((txn) async {
for (var element in records) {
final List existList = await txn.query(

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

@ -40,20 +40,8 @@ class AgentController extends GetxController {
final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController();
//音乐播放管理
final _musiceManager = Get.find<MusicManager>();
// 导航管理
final _navigationManager = Get.find<NavigationManager>();
var isPlaying = false.obs;
var currentId = 0.obs;
// 播放进度相关状态
var currentPosition = 0.obs; // 当前播放位置(毫秒)
var duration = 0.obs; // 总时长(毫秒)
var progress = 0.0.obs; // 播放进度(0.0-1.0)
// 当前播放列表管理
final currentPlaylist = <Map<String, dynamic>>[].obs; // 当前播放列表
final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引
// 各种状态标志
final isInitialized = false.obs;
@ -110,9 +98,6 @@ class AgentController extends GetxController {
// BLE事件订阅
StreamSubscription<dynamic>? _bleEventSubscription;
// 播放进度更新定时器
Timer? _progressTimer;
// 用于跟踪AI回复状态
bool _isNewAssistantResponse = true;
String _currentResponseId = '';
@ -125,16 +110,6 @@ class AgentController extends GetxController {
// 是否已加载聊天记录
final isHistoryLoaded = false.obs;
// 添加加载状态跟踪
final RxBool isLoadingMusic = false.obs;
final RxString loadingMusicId = ''.obs;
// 设置加载状态的方法
void setLoadingMusic(String id, bool loading) {
loadingMusicId.value = loading ? id : '';
isLoadingMusic.value = loading;
}
@override
void onInit() {
super.onInit();
@ -142,37 +117,10 @@ class AgentController extends GetxController {
_loadChatHistory(); //加载聊天历史记录
_subscribeToEvents(); //订阅代理服务事件
_subscribeToBleEvents(); //订阅BLE事件
_initMusicStatus(); //初始化音乐状态
_setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器
}
// 初始化音乐播放状态
Future<void> _initMusicStatus() async {
try {
await musicStatus();
Logger.d('AgentController', '音乐状态初始化完成');
} catch (e) {
Logger.e('AgentController', '音乐状态初始化失败: $e');
}
}
// 设置媒体会话回调
void _setupMediaSessionCallbacks() async {
try {
await _musiceManager.setMediaSessionCallbacks(
onPlay: onMediaSessionPlay,
onPause: onMediaSessionPause,
onNext: onMediaSessionNext,
onPrevious: onMediaSessionPrevious,
onSeekTo: onMediaSessionSeekTo,
onStop: onMediaSessionStop,
);
Logger.d('AgentController', '媒体会话回调设置完成');
} catch (e) {
Logger.e('AgentController', '媒体会话回调设置失败: $e');
}
}
@override
void onClose() {
@ -184,7 +132,6 @@ class AgentController extends GetxController {
_eventSubscription?.cancel();
_amapeventSubscription?.cancel();
_bleEventSubscription?.cancel();
_progressTimer?.cancel();
super.onClose();
}
@ -1321,241 +1268,12 @@ class AgentController extends GetxController {
await stopVoiceInput();
}
// 获取音乐播放状态
Future<void> musicStatus() async {
try {
var status = await _musiceManager.getStatus();
isPlaying.value = status['isPlaying'] ?? false;
currentId.value = status['id'] ?? 0;
Logger.d('AgentController',
'音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}');
} catch (e) {
Logger.e('AgentController', '获取音乐状态失败: $e');
}
}
// 播放音乐
Future<void> musicPlay(Map<String, dynamic> song) async {
try {
Logger.d('AgentController', '开始播放音乐: ${song['title'] ?? song['name']}');
// 立即更新UI状态,提供即时反馈
isPlaying.value = true;
// 修复:确保currentId接收int类型
final songId = song['id'];
currentId.value = songId is int
? songId
: (int.tryParse(songId?.toString() ?? '0') ?? 0);
// 重置进度状态
currentPosition.value = 0;
duration.value = 0;
progress.value = 0.0;
final id = song['id'] ?? "";
final timestampMs = song['ts'] ?? 0;
// 检查URL是否需要刷新(只有当有时间戳时才检查)
if (timestampMs > 0) {
final now = DateTime.now();
final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add(
const Duration(minutes: 10), // 转换为北京时间
);
// 判断是否过期
if (tsTime.isBefore(now)) {
Logger.d('AgentController', '音乐URL已过期,正在获取新URL...');
try {
var response = await Api.getMusicUrl({
"ids": [id]
});
print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据
final urldata = response['urls'][0] as Map<String, dynamic>;
song["url"] = urldata['url'] ?? '';
// 修复:确保currentId接收int类型
final newSongId = song['id'];
currentId.value = newSongId is int
? newSongId
: (int.tryParse(newSongId?.toString() ?? '0') ?? 0);
Logger.d('AgentController', '获取新URL成功');
} catch (e) {
Logger.e('AgentController', '获取新URL失败: $e');
// 如果获取新URL失败,尝试使用原URL
}
}
}
// 转换为MusicManager期望的格式
final musicData = <String, String>{
'id': song['id']?.toString() ?? '',
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl':
song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
// 调用音乐管理器播放
await _musiceManager.play(musicData);
// 更新媒体会话元数据和播放状态
await _updateMediaSessionMetadata(song);
await _updateMediaSessionPlaybackState(true);
// 启动进度定时器
_startProgressTimer();
Logger.d('AgentController', '音乐播放启动成功');
} catch (e) {
Logger.e('AgentController', '播放音乐失败: $e');
// 发生错误时重置状态
isPlaying.value = false;
currentId.value = 0;
}
}
// 暂停音乐
Future<void> musicPause() async {
try {
Logger.d('AgentController', '暂停音乐播放');
await _musiceManager.pause();
isPlaying.value = false;
_stopProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(false);
} catch (e) {
Logger.e('AgentController', '暂停音乐失败: $e');
}
}
// 恢复音乐播放
Future<void> musicResume() async {
try {
Logger.d('AgentController', '恢复播放音乐');
await _musiceManager.resume();
isPlaying.value = true;
_startProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(true);
Logger.d('AgentController', '音乐恢复播放成功');
} catch (e) {
Logger.e('AgentController', '恢复播放音乐失败: $e');
isPlaying.value = false;
}
}
// 启动进度更新定时器
void _startProgressTimer() {
_stopProgressTimer(); // 先停止现有定时器
Logger.d('AgentController', '启动播放进度定时器');
_progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
if (isPlaying.value && currentId.value != 0) {
updateMusicProgress();
} else if (!isPlaying.value) {
// 如果不在播放状态,停止定时器
timer.cancel();
_progressTimer = null;
Logger.d('AgentController', '播放已停止,取消进度定时器');
}
});
}
// 停止进度更新定时器
void _stopProgressTimer() {
if (_progressTimer != null) {
Logger.d('AgentController', '停止播放进度定时器');
_progressTimer?.cancel();
_progressTimer = null;
}
}
// 获取播放进度
Future<void> updateMusicProgress() async {
try {
if (!isPlaying.value || currentId.value == 0) {
return; // 如果没有在播放,直接返回
}
var progressInfo = await _musiceManager.getProgress();
final newPosition = progressInfo['currentPosition'] ?? 0;
final newDuration = progressInfo['duration'] ?? 0;
final newProgress = progressInfo['progress'] ?? 0.0;
bool hasUpdate = false;
// 只有当值发生变化时才更新,避免不必要的UI刷新
if (currentPosition.value != newPosition) {
currentPosition.value = newPosition;
hasUpdate = true;
}
if (duration.value != newDuration) {
duration.value = newDuration;
hasUpdate = true;
}
if ((progress.value - newProgress).abs() > 0.001) {
// 避免浮点数精度问题
progress.value = newProgress;
hasUpdate = true;
}
// 如果有进度更新,通知系统媒体会话更新进度信息
if (hasUpdate) {
await _updateMediaSessionProgress();
}
} catch (e) {
Logger.e('AgentController', '获取播放进度失败: $e');
}
}
/// 更新系统媒体会话的进度信息
Future<void> _updateMediaSessionProgress() async {
try {
// 这里将调用music_service插件的方法来更新系统媒体会话进度
// 具体实现将在music_service插件中添加
await _musiceManager.updateMediaSessionProgress(
currentPosition.value,
duration.value,
progress.value,
);
} catch (e) {
// 忽略错误,避免影响正常播放
Logger.d('AgentController', '更新媒体会话进度失败: $e');
}
}
/// 更新系统媒体会话的元数据
Future<void> _updateMediaSessionMetadata(Map<String, dynamic> song) async {
try {
await _musiceManager.updateMediaSessionMetadata(song);
} catch (e) {
Logger.d('AgentController', '更新媒体会话元数据失败: $e');
}
}
/// 更新系统媒体会话的播放状态
Future<void> _updateMediaSessionPlaybackState(bool isPlaying) async {
try {
await _musiceManager.updateMediaSessionPlaybackState(isPlaying);
} catch (e) {
Logger.d('AgentController', '更新媒体会话播放状态失败: $e');
}
}
// 跳转到指定位置
Future<void> seekToPosition(int positionMs) async {
try {
await _musiceManager.seekTo(positionMs);
// 立即更新进度状态
await updateMusicProgress();
} catch (e) {
Logger.e('AgentController', '跳转播放位置失败: $e');
}
}
// 修改自动停止处理,遵循TTS设置
void _handleAutoStop(int index) {
@ -1595,179 +1313,7 @@ class AgentController extends GetxController {
);
}
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
try {
if (playlist.isEmpty) {
Logger.e(TAG, '播放列表为空,无法播放');
return;
}
Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲');
// 保存当前播放列表
currentPlaylist.assignAll(playlist);
currentPlaylistIndex.value = 0;
// 设置加载状态
isLoadingMusic.value = true;
loadingMusicId.value = playlist[0]['id']?.toString() ?? '';
// 重置播放状态
isPlaying.value = true;
currentPosition.value = 0;
duration.value = 0;
progress.value = 0.0;
// 格式化歌曲列表数据
final formattedPlaylist = playlist.map((song) {
return {
'id': song['id']?.toString() ?? '',
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl':
song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
}).toList();
// 调用音乐管理器播放列表
await _musiceManager.playPlaylist(formattedPlaylist);
// 更新当前播放歌曲ID
if (formattedPlaylist.isNotEmpty) {
currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0;
}
// 启动进度定时器
_startProgressTimer();
Logger.d(TAG, '音乐列表播放启动成功');
} catch (e) {
Logger.e(TAG, '播放音乐列表失败: $e');
isPlaying.value = false;
currentId.value = 0;
// 显示错误提示
Get.snackbar(
'播放失败',
'无法播放音乐列表,请稍后重试',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[400],
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} finally {
// 清除加载状态
isLoadingMusic.value = false;
loadingMusicId.value = '';
}
}
/// 播放上一曲
Future<void> playPrevious() async {
if (currentPlaylist.isEmpty) {
Logger.w(TAG, '当前播放列表为空,无法播放上一曲');
return;
}
// 计算上一曲的索引
int previousIndex = currentPlaylistIndex.value - 1;
if (previousIndex < 0) {
previousIndex = currentPlaylist.length - 1; // 循环到最后一首
}
currentPlaylistIndex.value = previousIndex;
final previousSong = currentPlaylist[previousIndex];
Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}');
await musicPlay(previousSong);
}
/// 播放下一曲
Future<void> playNext() async {
if (currentPlaylist.isEmpty) {
Logger.w(TAG, '当前播放列表为空,无法播放下一曲');
return;
}
// 计算下一曲的索引
int nextIndex = currentPlaylistIndex.value + 1;
if (nextIndex >= currentPlaylist.length) {
nextIndex = 0; // 循环到第一首
}
currentPlaylistIndex.value = nextIndex;
final nextSong = currentPlaylist[nextIndex];
Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}');
await musicPlay(nextSong);
}
/// 更新当前播放列表和索引(当单独播放某首歌曲时调用)
void updateCurrentPlaylistContext(
List<Map<String, dynamic>> playlist, int songId) {
currentPlaylist.assignAll(playlist);
// 查找当前歌曲在列表中的索引
int index = -1;
for (int i = 0; i < playlist.length; i++) {
if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) {
index = i;
break;
}
}
if (index != -1) {
currentPlaylistIndex.value = index;
Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index');
}
}
// ========== 媒体会话回调处理方法 ==========
/// 处理来自系统通知栏的播放操作
Future<void> onMediaSessionPlay() async {
Logger.d(TAG, '收到媒体会话播放回调');
if (!isPlaying.value) {
await musicResume();
}
}
/// 处理来自系统通知栏的暂停操作
Future<void> onMediaSessionPause() async {
Logger.d(TAG, '收到媒体会话暂停回调');
if (isPlaying.value) {
await musicPause();
}
}
/// 处理来自系统通知栏的下一曲操作
Future<void> onMediaSessionNext() async {
Logger.d(TAG, '收到媒体会话下一曲回调');
await playNext();
}
/// 处理来自系统通知栏的上一曲操作
Future<void> onMediaSessionPrevious() async {
Logger.d(TAG, '收到媒体会话上一曲回调');
await playPrevious();
}
/// 处理来自系统通知栏的跳转操作
Future<void> onMediaSessionSeekTo(int positionMs) async {
Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms');
await seekToPosition(positionMs);
}
/// 处理来自系统通知栏的停止操作
Future<void> onMediaSessionStop() async {
Logger.d(TAG, '收到媒体会话停止回调');
await musicPause();
// 重置播放状态
currentPosition.value = 0;
progress.value = 0.0;
_stopProgressTimer();
}
}

28
lib/modules/agent/views/music_card.dart

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/agent_controller.dart';
import '../../music/services/music_service_facade.dart';
import 'circular_progress_button.dart';
/// 构建音乐项目
Widget buildMusicItem(BuildContext context, int id, String name, String url,
String singer, String image) {
final controller = Get.find<AgentController>(); // 获取控制器
final musicFacade = Get.find<MusicServiceFacade>(); // 获取音乐服务门面
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
Map<String, dynamic> song = {
@ -16,7 +16,7 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
'artist': singer,
'coverUrl': image,
};
controller.musicStatus(); //刷新状态
// 音乐状态由MusicController自动管理,无需手动刷新
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
@ -82,11 +82,11 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
),
Obx(() {
final isThisPlaying =
controller.isPlaying.value && controller.currentId.value == id;
musicFacade.isPlaying.value && musicFacade.currentId.value == id;
// 只有当前播放的歌曲才显示进度
final currentProgress =
isThisPlaying ? controller.progress.value : 0.0;
isThisPlaying ? musicFacade.progress.value : 0.0;
return CircularProgressButton(
isPlaying: isThisPlaying,
@ -97,21 +97,21 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
strokeWidth: 2.0,
onPlayPause: () async {
try {
if (controller.isPlaying.value) {
if (controller.currentId.value == id) {
if (musicFacade.isPlaying.value) {
if (musicFacade.currentId.value == id) {
// 当前歌曲正在播放,暂停它
await controller.musicPause();
await musicFacade.musicPause();
} else {
// 其他歌曲正在播放,播放这首歌
await controller.musicPlay(song);
await musicFacade.musicPlay(song);
}
} else {
if (controller.currentId.value == id) {
if (musicFacade.currentId.value == id) {
// 当前歌曲已暂停,恢复播放
await controller.musicResume();
await musicFacade.musicResume();
} else {
// 播放新歌曲
await controller.musicPlay(song);
await musicFacade.musicPlay(song);
}
}
} catch (e) {
@ -122,8 +122,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url,
? (seekProgress) {
// 只有当前播放的歌曲才允许跳转
final targetPosition =
(controller.duration.value * seekProgress).round();
controller.seekToPosition(targetPosition);
(musicFacade.duration.value * seekProgress).round();
musicFacade.seekToPosition(targetPosition);
}
: null,
);

15
lib/modules/music/bindings/music_binding.dart

@ -0,0 +1,15 @@
import 'package:get/get.dart';
import '../controllers/music_controller.dart';
/// 音乐模块绑定
/// 负责注册音乐相关的控制器和服务
class MusicBinding extends Bindings {
@override
void dependencies() {
// 注册音乐控制器为单例
Get.lazyPut<MusicController>(
() => MusicController(),
fenix: true, // 允许重新创建
);
}
}

493
lib/modules/music/controllers/music_controller.dart

@ -0,0 +1,493 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/music_manager.dart';
import '../../../data/services/network/api.dart';
/// 音乐播放控制器
/// 负责管理音乐播放状态、播放列表和播放控制
class MusicController extends GetxController {
static const String TAG = 'MusicController';
// 音乐播放管理器
final _musicManager = Get.find<MusicManager>();
// 播放状态
final isPlaying = false.obs;
final currentId = 0.obs;
// 播放进度相关状态
final currentPosition = 0.obs; // 当前播放位置(毫秒)
final duration = 0.obs; // 总时长(毫秒)
final progress = 0.0.obs; // 播放进度(0.0-1.0)
// 当前播放列表管理
final currentPlaylist = <Map<String, dynamic>>[].obs; // 当前播放列表
final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引
// 加载状态跟踪
final isLoadingMusic = false.obs;
final loadingMusicId = ''.obs;
// 进度更新定时器
Timer? _progressTimer;
@override
void onInit() {
super.onInit();
_initMusicStatus();
_setupMediaSessionCallbacks();
}
@override
void onClose() {
_stopProgressTimer();
super.onClose();
}
// 初始化音乐播放状态
Future<void> _initMusicStatus() async {
try {
await musicStatus();
Logger.d(TAG, '音乐状态初始化完成');
} catch (e) {
Logger.e(TAG, '音乐状态初始化失败: $e');
}
}
// 设置媒体会话回调
void _setupMediaSessionCallbacks() async {
try {
await _musicManager.setMediaSessionCallbacks(
onPlay: onMediaSessionPlay,
onPause: onMediaSessionPause,
onNext: onMediaSessionNext,
onPrevious: onMediaSessionPrevious,
onSeekTo: onMediaSessionSeekTo,
onStop: onMediaSessionStop,
);
Logger.d(TAG, '媒体会话回调设置完成');
} catch (e) {
Logger.e(TAG, '媒体会话回调设置失败: $e');
}
}
// 设置加载状态的方法
void setLoadingMusic(String id, bool loading) {
loadingMusicId.value = loading ? id : '';
isLoadingMusic.value = loading;
}
// 获取音乐播放状态
Future<void> musicStatus() async {
try {
var status = await _musicManager.getStatus();
isPlaying.value = status['isPlaying'] ?? false;
currentId.value = status['id'] ?? 0;
Logger.d(TAG, '音乐状态更新: playing=${isPlaying.value}, id=${currentId.value}');
} catch (e) {
Logger.e(TAG, '获取音乐状态失败: $e');
}
}
// 播放音乐
Future<void> musicPlay(Map<String, dynamic> song) async {
try {
Logger.d(TAG, '开始播放音乐: ${song['title'] ?? song['name']}');
// 立即更新UI状态,提供即时反馈
isPlaying.value = true;
// 修复:确保currentId接收int类型
final songId = song['id'];
currentId.value = songId is int
? songId
: (int.tryParse(songId?.toString() ?? '0') ?? 0);
// 重置进度状态
currentPosition.value = 0;
duration.value = 0;
progress.value = 0.0;
final id = song['id'] ?? "";
final timestampMs = song['ts'] ?? 0;
// 检查URL是否需要刷新(只有当有时间戳时才检查)
if (timestampMs > 0) {
final now = DateTime.now();
final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add(
const Duration(minutes: 10), // 转换为北京时间
);
// 判断是否过期
if (tsTime.isBefore(now)) {
Logger.d(TAG, '音乐URL已过期,正在获取新URL...');
try {
var response = await Api.getMusicUrl({
"ids": [id]
});
print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据
final urldata = response['urls'][0] as Map<String, dynamic>;
song["url"] = urldata['url'] ?? '';
// 修复:确保currentId接收int类型
final newSongId = song['id'];
currentId.value = newSongId is int
? newSongId
: (int.tryParse(newSongId?.toString() ?? '0') ?? 0);
Logger.d(TAG, '获取新URL成功');
} catch (e) {
Logger.e(TAG, '获取新URL失败: $e');
// 如果获取新URL失败,尝试使用原URL
}
}
}
// 转换为MusicManager期望的格式
final musicData = <String, String>{
'id': song['id']?.toString() ?? '',
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl':
song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
// 调用音乐管理器播放
await _musicManager.play(musicData);
// 更新媒体会话元数据和播放状态
await _updateMediaSessionMetadata(song);
await _updateMediaSessionPlaybackState(true);
// 启动进度定时器
_startProgressTimer();
Logger.d(TAG, '音乐播放启动成功');
} catch (e) {
Logger.e(TAG, '播放音乐失败: $e');
// 发生错误时重置状态
isPlaying.value = false;
currentId.value = 0;
}
}
// 暂停音乐
Future<void> musicPause() async {
try {
Logger.d(TAG, '暂停音乐播放');
await _musicManager.pause();
isPlaying.value = false;
_stopProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(false);
} catch (e) {
Logger.e(TAG, '暂停音乐失败: $e');
}
}
// 恢复音乐播放
Future<void> musicResume() async {
try {
Logger.d(TAG, '恢复播放音乐');
await _musicManager.resume();
isPlaying.value = true;
_startProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(true);
Logger.d(TAG, '音乐恢复播放成功');
} catch (e) {
Logger.e(TAG, '恢复播放音乐失败: $e');
isPlaying.value = false;
}
}
// 启动进度更新定时器
void _startProgressTimer() {
_stopProgressTimer(); // 先停止现有定时器
Logger.d(TAG, '启动播放进度定时器');
_progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
if (isPlaying.value && currentId.value != 0) {
updateMusicProgress();
} else if (!isPlaying.value) {
// 如果不在播放状态,停止定时器
timer.cancel();
_progressTimer = null;
Logger.d(TAG, '播放已停止,取消进度定时器');
}
});
}
// 停止进度更新定时器
void _stopProgressTimer() {
if (_progressTimer != null) {
Logger.d(TAG, '停止播放进度定时器');
_progressTimer?.cancel();
_progressTimer = null;
}
}
// 获取播放进度
Future<void> updateMusicProgress() async {
try {
if (!isPlaying.value || currentId.value == 0) {
return; // 如果没有在播放,直接返回
}
var progressInfo = await _musicManager.getProgress();
final newPosition = progressInfo['currentPosition'] ?? 0;
final newDuration = progressInfo['duration'] ?? 0;
final newProgress = progressInfo['progress'] ?? 0.0;
bool hasUpdate = false;
// 只有当值发生变化时才更新,避免不必要的UI刷新
if (currentPosition.value != newPosition) {
currentPosition.value = newPosition;
hasUpdate = true;
}
if (duration.value != newDuration) {
duration.value = newDuration;
hasUpdate = true;
}
if ((progress.value - newProgress).abs() > 0.001) {
// 避免浮点数精度问题
progress.value = newProgress;
hasUpdate = true;
}
// 如果有进度更新,通知系统媒体会话更新进度信息
if (hasUpdate) {
await _updateMediaSessionProgress();
}
} catch (e) {
Logger.e(TAG, '获取播放进度失败: $e');
}
}
// 跳转到指定位置
Future<void> seekToPosition(int positionMs) async {
try {
await _musicManager.seekTo(positionMs);
// 立即更新进度状态
await updateMusicProgress();
} catch (e) {
Logger.e(TAG, '跳转播放位置失败: $e');
}
}
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
try {
if (playlist.isEmpty) {
Logger.e(TAG, '播放列表为空,无法播放');
return;
}
Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲');
// 保存当前播放列表
currentPlaylist.assignAll(playlist);
currentPlaylistIndex.value = 0;
// 设置加载状态
isLoadingMusic.value = true;
loadingMusicId.value = playlist[0]['id']?.toString() ?? '';
// 重置播放状态
isPlaying.value = true;
currentPosition.value = 0;
duration.value = 0;
progress.value = 0.0;
// 格式化歌曲列表数据
final formattedPlaylist = playlist.map((song) {
return {
'id': song['id']?.toString() ?? '',
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist':
song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl':
song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
}).toList();
// 调用音乐管理器播放列表
await _musicManager.playPlaylist(formattedPlaylist);
// 更新当前播放歌曲ID
if (formattedPlaylist.isNotEmpty) {
currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0;
}
// 启动进度定时器
_startProgressTimer();
Logger.d(TAG, '音乐列表播放启动成功');
} catch (e) {
Logger.e(TAG, '播放音乐列表失败: $e');
isPlaying.value = false;
currentId.value = 0;
// 显示错误提示
Get.snackbar(
'播放失败',
'无法播放音乐列表,请稍后重试',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[400],
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} finally {
// 清除加载状态
isLoadingMusic.value = false;
loadingMusicId.value = '';
}
}
/// 播放上一曲
Future<void> playPrevious() async {
if (currentPlaylist.isEmpty) {
Logger.w(TAG, '当前播放列表为空,无法播放上一曲');
return;
}
// 计算上一曲的索引
int previousIndex = currentPlaylistIndex.value - 1;
if (previousIndex < 0) {
previousIndex = currentPlaylist.length - 1; // 循环到最后一首
}
currentPlaylistIndex.value = previousIndex;
final previousSong = currentPlaylist[previousIndex];
Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}');
await musicPlay(previousSong);
}
/// 播放下一曲
Future<void> playNext() async {
if (currentPlaylist.isEmpty) {
Logger.w(TAG, '当前播放列表为空,无法播放下一曲');
return;
}
// 计算下一曲的索引
int nextIndex = currentPlaylistIndex.value + 1;
if (nextIndex >= currentPlaylist.length) {
nextIndex = 0; // 循环到第一首
}
currentPlaylistIndex.value = nextIndex;
final nextSong = currentPlaylist[nextIndex];
Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}');
await musicPlay(nextSong);
}
/// 更新当前播放列表和索引(当单独播放某首歌曲时调用)
void updateCurrentPlaylistContext(
List<Map<String, dynamic>> playlist, int songId) {
currentPlaylist.assignAll(playlist);
// 查找当前歌曲在列表中的索引
int index = -1;
for (int i = 0; i < playlist.length; i++) {
if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) {
index = i;
break;
}
}
if (index != -1) {
currentPlaylistIndex.value = index;
Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index');
}
}
// ========== 媒体会话回调处理方法 ==========
/// 处理来自系统通知栏的播放操作
Future<void> onMediaSessionPlay() async {
Logger.d(TAG, '收到媒体会话播放回调');
if (!isPlaying.value) {
await musicResume();
}
}
/// 处理来自系统通知栏的暂停操作
Future<void> onMediaSessionPause() async {
Logger.d(TAG, '收到媒体会话暂停回调');
if (isPlaying.value) {
await musicPause();
}
}
/// 处理来自系统通知栏的下一曲操作
Future<void> onMediaSessionNext() async {
Logger.d(TAG, '收到媒体会话下一曲回调');
await playNext();
}
/// 处理来自系统通知栏的上一曲操作
Future<void> onMediaSessionPrevious() async {
Logger.d(TAG, '收到媒体会话上一曲回调');
await playPrevious();
}
/// 处理来自系统通知栏的跳转操作
Future<void> onMediaSessionSeekTo(int positionMs) async {
Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms');
await seekToPosition(positionMs);
}
/// 处理来自系统通知栏的停止操作
Future<void> onMediaSessionStop() async {
Logger.d(TAG, '收到媒体会话停止回调');
await musicPause();
// 重置播放状态
currentPosition.value = 0;
progress.value = 0.0;
_stopProgressTimer();
}
// ========== 私有辅助方法 ==========
/// 更新系统媒体会话的进度信息
Future<void> _updateMediaSessionProgress() async {
try {
// 具体实现将在music_service插件中添加
await _musicManager.updateMediaSessionProgress(
currentPosition.value,
duration.value,
progress.value,
);
} catch (e) {
// 忽略错误,避免影响正常播放
Logger.d(TAG, '更新媒体会话进度失败: $e');
}
}
/// 更新系统媒体会话的元数据
Future<void> _updateMediaSessionMetadata(Map<String, dynamic> song) async {
try {
await _musicManager.updateMediaSessionMetadata(song);
} catch (e) {
Logger.d(TAG, '更新媒体会话元数据失败: $e');
}
}
/// 更新系统媒体会话的播放状态
Future<void> _updateMediaSessionPlaybackState(bool isPlaying) async {
try {
await _musicManager.updateMediaSessionPlaybackState(isPlaying);
} catch (e) {
Logger.d(TAG, '更新媒体会话播放状态失败: $e');
}
}
}

125
lib/modules/music/services/music_service_facade.dart

@ -0,0 +1,125 @@
import 'package:get/get.dart';
import '../controllers/music_controller.dart';
import '../../../data/services/music_manager.dart';
/// 音乐服务门面
/// 作为音乐功能的统一入口,简化模块间的交互
class MusicServiceFacade {
// 单例实现
static final MusicServiceFacade _instance = MusicServiceFacade._internal();
factory MusicServiceFacade() => _instance;
MusicServiceFacade._internal();
// 获取音乐控制器实例
MusicController get musicController => Get.find<MusicController>();
// 获取音乐管理器实例
MusicManager get musicManager => Get.find<MusicManager>();
/// 播放音乐
Future<void> musicPlay(Map<String, dynamic> song) async {
await musicController.musicPlay(song);
}
/// 暂停音乐
Future<void> musicPause() async {
await musicController.musicPause();
}
/// 恢复播放
Future<void> musicResume() async {
await musicController.musicResume();
}
/// 播放音乐(兼容旧接口)
Future<void> playMusic(Map<String, dynamic> song) async {
await musicPlay(song);
}
/// 暂停音乐(兼容旧接口)
Future<void> pauseMusic() async {
await musicPause();
}
/// 恢复播放(兼容旧接口)
Future<void> resumeMusic() async {
await musicResume();
}
/// 播放/暂停切换
Future<void> togglePlayPause() async {
if (musicController.isPlaying.value) {
await musicPause();
} else {
await musicResume();
}
}
/// 播放上一曲
Future<void> playPrevious() async {
await musicController.playPrevious();
}
/// 播放下一曲
Future<void> playNext() async {
await musicController.playNext();
}
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
await musicController.playPlaylist(playlist);
}
/// 跳转到指定位置
Future<void> seekToPosition(int positionMs) async {
await musicController.seekToPosition(positionMs);
}
/// 获取当前播放状态
bool get isPlayingValue => musicController.isPlaying.value;
/// 获取当前播放歌曲ID
int get currentIdValue => musicController.currentId.value;
/// 获取当前播放位置(毫秒)
int get currentPositionValue => musicController.currentPosition.value;
/// 获取当前歌曲总时长(毫秒)
int get durationValue => musicController.duration.value;
/// 获取当前播放进度(0.0-1.0)
double get progressValue => musicController.progress.value;
/// 获取响应式播放状态(用于Obx)
get isPlaying => musicController.isPlaying;
/// 获取响应式当前播放歌曲ID(用于Obx)
get currentId => musicController.currentId;
/// 获取响应式当前播放位置(用于Obx)
get currentPosition => musicController.currentPosition;
/// 获取响应式当前歌曲总时长(用于Obx)
get duration => musicController.duration;
/// 获取响应式当前播放进度(用于Obx)
get progress => musicController.progress;
/// 获取当前播放列表
List<Map<String, dynamic>> get currentPlaylist => musicController.currentPlaylist;
/// 获取当前播放索引
int get currentPlaylistIndex => musicController.currentPlaylistIndex.value;
/// 设置加载状态
void setLoadingMusic(String id, bool loading) {
musicController.setLoadingMusic(id, loading);
}
/// 更新当前播放列表上下文
void updateCurrentPlaylistContext(List<Map<String, dynamic>> playlist, int songId) {
musicController.updateCurrentPlaylistContext(playlist, songId);
}
}

114
lib/modules/music/views/music_playlist_view.dart

@ -1,9 +1,11 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../agent/controllers/agent_controller.dart';
import '../../agent/views/circular_progress_button.dart';
import 'package:flutter/services.dart';
import '../services/music_service_facade.dart';
import '../../../core/utils/logger.dart';
import '../../agent/views/circular_progress_button.dart';
class MusicPlaylistView extends StatelessWidget {
const MusicPlaylistView({Key? key}) : super(key: key);
@ -11,7 +13,7 @@ class MusicPlaylistView extends StatelessWidget {
@override
Widget build(BuildContext context) {
ScreenUtil.init(context, designSize: const Size(375, 812)); // 添加设计尺寸参考
final controller = Get.find<AgentController>();
final musicFacade = Get.find<MusicServiceFacade>();
final List<Map<String, dynamic>> playlist = Get.arguments ?? [];
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
@ -53,14 +55,14 @@ class MusicPlaylistView extends StatelessWidget {
Container(
height: MediaQuery.of(context).size.height * 0.38, // 增大高度比例
child: _buildCurrentPlayingSection(
controller, playlist, isDarkMode),
musicFacade, playlist, isDarkMode),
),
// 中部:播放全部按钮
_buildPlayAllSection(controller, playlist, isDarkMode),
_buildPlayAllSection(musicFacade, playlist, isDarkMode),
// 下部:歌单列表 - 使用剩余空间
Expanded(
child:
_buildPlaylistSection(controller, playlist, isDarkMode),
_buildPlaylistSection(musicFacade, playlist, isDarkMode),
),
],
),
@ -68,19 +70,19 @@ class MusicPlaylistView extends StatelessWidget {
}
// 构建当前播放音乐详情部分
Widget _buildCurrentPlayingSection(AgentController controller,
Widget _buildCurrentPlayingSection(MusicServiceFacade musicFacade,
List<Map<String, dynamic>> playlist, bool isDarkMode) {
return SingleChildScrollView(
// 添加滚动支持
child: Obx(() {
// 查找当前播放的歌曲
Map<String, dynamic>? currentSong;
if (controller.currentId.value != 0) {
if (musicFacade.currentId.value != 0) {
try {
currentSong = playlist.firstWhere(
(song) =>
int.tryParse(song['id']?.toString() ?? '0') ==
controller.currentId.value,
musicFacade.currentId.value,
);
} catch (e) {
// 如果没找到,使用第一首歌曲
@ -135,10 +137,10 @@ class MusicPlaylistView extends StatelessWidget {
_buildSongInfo(title, artist, isDarkMode),
SizedBox(height: 16.h),
// 进度条
_buildProgressBar(controller, isDarkMode),
_buildProgressBar(musicFacade, isDarkMode),
SizedBox(height: 15.h),
// 播放控制按钮
_buildPlayControls(controller, playlist, isDarkMode),
_buildPlayControls(musicFacade, playlist, isDarkMode),
],
),
);
@ -147,7 +149,7 @@ class MusicPlaylistView extends StatelessWidget {
}
// 构建播放全部按钮部分
Widget _buildPlayAllSection(AgentController controller,
Widget _buildPlayAllSection(MusicServiceFacade musicFacade,
List<Map<String, dynamic>> playlist, bool isDarkMode) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
@ -157,7 +159,7 @@ class MusicPlaylistView extends StatelessWidget {
child: ElevatedButton.icon(
onPressed: () async {
if (playlist.isNotEmpty) {
await controller.playPlaylist(playlist);
await musicFacade.playPlaylist(playlist);
}
},
icon: Icon(Icons.play_arrow, color: Colors.white, size: 20.w),
@ -183,7 +185,7 @@ class MusicPlaylistView extends StatelessWidget {
}
// 构建歌单列表部分
Widget _buildPlaylistSection(AgentController controller,
Widget _buildPlaylistSection(MusicServiceFacade musicFacade,
List<Map<String, dynamic>> playlist, bool isDarkMode) {
return ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h),
@ -198,7 +200,7 @@ class MusicPlaylistView extends StatelessWidget {
return _buildMusicItem(
context,
controller,
musicFacade,
id,
name,
url,
@ -212,7 +214,7 @@ class MusicPlaylistView extends StatelessWidget {
Widget _buildMusicItem(
BuildContext context,
AgentController controller,
MusicServiceFacade musicFacade,
String id,
String name,
String url,
@ -230,7 +232,7 @@ class MusicPlaylistView extends StatelessWidget {
return GestureDetector(
onTap: () {
_playSelectedSong(controller, song, Get.arguments ?? []);
_playSelectedSong(musicFacade, song, Get.arguments ?? []);
},
child: Container(
margin: EdgeInsets.only(bottom: 10.h),
@ -322,10 +324,10 @@ class MusicPlaylistView extends StatelessWidget {
// 播放按钮
Obx(() {
final currentIdInt = int.tryParse(id) ?? 0;
final isThisPlaying = controller.isPlaying.value &&
controller.currentId.value == currentIdInt;
final isThisPlaying = musicFacade.isPlaying.value &&
musicFacade.currentId.value == currentIdInt;
final currentProgress =
isThisPlaying ? controller.progress.value : 0.0;
isThisPlaying ? musicFacade.progress.value : 0.0;
return CircularProgressButton(
isPlaying: isThisPlaying,
@ -337,20 +339,20 @@ class MusicPlaylistView extends StatelessWidget {
onPlayPause: () async {
try {
Logger.d('MusicPlaylistView',
'播放按钮被点击,当前播放状态: ${controller.isPlaying.value}');
'播放按钮被点击,当前播放状态: ${musicFacade.isPlaying.value}');
Logger.d('MusicPlaylistView',
'当前歌曲ID: ${controller.currentId.value}, 点击歌曲ID: $currentIdInt');
if (controller.isPlaying.value) {
if (controller.currentId.value == currentIdInt) {
'当前歌曲ID: ${musicFacade.currentId.value}, 点击歌曲ID: $currentIdInt');
if (musicFacade.isPlaying.value) {
if (musicFacade.currentId.value == currentIdInt) {
Logger.d('MusicPlaylistView', '暂停当前播放的歌曲');
await controller.musicPause();
await musicFacade.musicPause();
} else {
Logger.d('MusicPlaylistView', '播放新歌曲');
await controller.musicPlay(song);
await musicFacade.musicPlay(song);
}
} else {
Logger.d('MusicPlaylistView', '开始播放歌曲');
await controller.musicPlay(song);
await musicFacade.musicPlay(song);
}
} catch (e) {
Logger.e('MusicPlaylistView', '播放音乐时发生错误: $e');
@ -360,8 +362,8 @@ class MusicPlaylistView extends StatelessWidget {
? (seekProgress) {
// 只有当前播放的歌曲才允许跳转
final targetPosition =
(controller.duration.value * seekProgress).round();
controller.seekToPosition(targetPosition);
(musicFacade.duration.value * seekProgress).round();
musicFacade.seekToPosition(targetPosition);
}
: null,
);
@ -447,14 +449,14 @@ class MusicPlaylistView extends StatelessWidget {
}
// 构建进度条
Widget _buildProgressBar(AgentController controller, bool isDarkMode) {
Widget _buildProgressBar(MusicServiceFacade musicFacade, bool isDarkMode) {
return Obx(() {
return Row(
children: [
SizedBox(
width: 40.w,
child: Text(
_formatDuration(controller.currentPosition.value),
_formatDuration(musicFacade.currentPosition.value),
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
@ -478,11 +480,11 @@ class MusicPlaylistView extends StatelessWidget {
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14),
),
child: Slider(
value: controller.progress.value.clamp(0.0, 1.0),
value: musicFacade.progress.value.clamp(0.0, 1.0),
onChanged: (value) {
final targetPosition =
(controller.duration.value * value).round();
controller.seekToPosition(targetPosition);
(musicFacade.duration.value * value).round();
musicFacade.seekToPosition(targetPosition);
},
),
),
@ -491,7 +493,7 @@ class MusicPlaylistView extends StatelessWidget {
SizedBox(
width: 40.w,
child: Text(
_formatDuration(controller.duration.value),
_formatDuration(musicFacade.duration.value),
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
@ -506,14 +508,14 @@ class MusicPlaylistView extends StatelessWidget {
}
// 构建播放控制按钮
Widget _buildPlayControls(AgentController controller,
Widget _buildPlayControls(MusicServiceFacade musicFacade,
List<Map<String, dynamic>> playlist, bool isDarkMode) {
return Obx(() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () => _playPrevious(controller, playlist),
onPressed: () => _playPrevious(musicFacade, playlist),
iconSize: 32.w,
padding: EdgeInsets.all(8.w), // 增大触摸区域
icon: Icon(
@ -538,10 +540,10 @@ class MusicPlaylistView extends StatelessWidget {
],
),
child: IconButton(
onPressed: () => _togglePlayPause(controller),
onPressed: () => _togglePlayPause(musicFacade),
padding: EdgeInsets.zero,
icon: Icon(
controller.isPlaying.value ? Icons.pause : Icons.play_arrow,
musicFacade.isPlaying.value ? Icons.pause : Icons.play_arrow,
size: 28.w,
color: Colors.white,
),
@ -549,7 +551,7 @@ class MusicPlaylistView extends StatelessWidget {
),
SizedBox(width: 16.w),
IconButton(
onPressed: () => _playNext(controller, playlist),
onPressed: () => _playNext(musicFacade, playlist),
iconSize: 32.w,
padding: EdgeInsets.all(8.w), // 增大触摸区域
icon: Icon(
@ -572,35 +574,35 @@ class MusicPlaylistView extends StatelessWidget {
}
// 播放/暂停切换
Future<void> _togglePlayPause(AgentController controller) async {
if (controller.isPlaying.value) {
await controller.musicPause();
Future<void> _togglePlayPause(MusicServiceFacade musicFacade) async {
if (musicFacade.isPlaying.value) {
await musicFacade.musicPause();
} else {
await controller.musicResume();
await musicFacade.musicResume();
}
}
// 播放上一曲
void _playPrevious(
AgentController controller, List<Map<String, dynamic>> playlist) {
controller.updateCurrentPlaylistContext(
playlist, controller.currentId.value);
controller.playPrevious();
MusicServiceFacade musicFacade, List<Map<String, dynamic>> playlist) {
musicFacade.updateCurrentPlaylistContext(
playlist, musicFacade.currentId.value);
musicFacade.playPrevious();
}
// 播放下一曲
void _playNext(
AgentController controller, List<Map<String, dynamic>> playlist) {
controller.updateCurrentPlaylistContext(
playlist, controller.currentId.value);
controller.playNext();
MusicServiceFacade musicFacade, List<Map<String, dynamic>> playlist) {
musicFacade.updateCurrentPlaylistContext(
playlist, musicFacade.currentId.value);
musicFacade.playNext();
}
// 播放选中的歌曲
void _playSelectedSong(AgentController controller, Map<String, dynamic> song,
void _playSelectedSong(MusicServiceFacade musicFacade, Map<String, dynamic> song,
List<Map<String, dynamic>> playlist) {
final songId = int.tryParse(song['id']?.toString() ?? '0') ?? 0;
controller.updateCurrentPlaylistContext(playlist, songId);
controller.musicPlay(song);
musicFacade.updateCurrentPlaylistContext(playlist, songId);
musicFacade.musicPlay(song);
}
}

86
lib/modules/settings/views/settings_view.dart

@ -548,49 +548,49 @@ class SettingsView extends GetView<SettingsController> {
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// 面对面翻译测试
_buildSimpleNavigationSetting(
title: '面对面翻译测试',
subtitle: '测试面对面翻译',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.ftftranslation);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// BLE测试
_buildSimpleNavigationSetting(
title: 'opus解码测试',
subtitle: '测试opus解码',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.opusTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// _buildSimpleNavigationSetting(
// title: '面对面翻译测试',
// subtitle: '测试面对面翻译',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.ftftranslation);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// // BLE测试
// _buildSimpleNavigationSetting(
// title: 'opus解码测试',
// subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.opusTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// BLE测试
// _buildSimpleNavigationSetting(

33
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -136,8 +136,6 @@ class AzureAsrHelper(private val context: Context) {
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL
// 5. 初始化麦克风数据
initAudioRecord()
// 保存配置
this.subscriptionKey = subscriptionKey
@ -381,6 +379,8 @@ class AzureAsrHelper(private val context: Context) {
}
try {
// 5. 初始化麦克风
initAudioRecord()
// 设置各种事件监听
setupEventListeners(callback)
@ -508,7 +508,7 @@ class AzureAsrHelper(private val context: Context) {
// 停止音频处理
stopAudioProcessing()
// 停止录音
// 停止录音
stopMicrophoneCapture()
// 尝试强制关闭识别器
try {
@ -734,18 +734,18 @@ class AzureAsrHelper(private val context: Context) {
// 获取已确定的采样率
val format = getOptimalAudioFormat()
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
val channelConfig = AudioFormat.CHANNEL_IN_MONO // 单声道输入
val audioFormat = AudioFormat.ENCODING_PCM_16BIT // 16位PCM格式
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
channelConfig,
audioFormat
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
val channelConfig = AudioFormat.CHANNEL_IN_MONO // 单声道输入
val audioFormat = AudioFormat.ENCODING_PCM_16BIT // 16位PCM格式
// 计算最小缓冲区大小(避免出现音频欠载)
val minBufferSize = AudioRecord.getMinBufferSize(
@ -808,16 +808,17 @@ class AzureAsrHelper(private val context: Context) {
while (!Thread.interrupted() && audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
// 读取音频
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0
if (!isPaused && bytesRead > 0) {
// 写入Azure流
audioStream?.write(buffer)
if (!isPaused) {
// 读取音频
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0
if (bytesRead > 0) {
// 写入Azure流
audioStream?.write(buffer)
// 保存录音
recordfile?.saveAudioDataToWav(buffer)
// 保存录音
recordfile?.saveAudioDataToWav(buffer)
}
}
}
}.apply { start() }

6
pubspec.yaml

@ -23,6 +23,10 @@ dependencies:
audio_session: ^0.1.18
flutter_markdown: ^0.7.7+1
markdown: ^7.1.1
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: main
permission_handler: ^10.4.5
web_socket_channel: ^2.4.0
flutter_dotenv: ^5.1.0
@ -47,7 +51,7 @@ dependencies:
path: plugins/window_size
ref: 6c66ad23ee79749f30a8eece542cf54eaf157ed8
flutter_local_notifications: ^18.0.1
device_info_plus: ^10.1.2
device_info_plus: ^11.5.0
uuid: ^4.3.3
record: ^5.2.1
share_plus: ^11.0.0

Loading…
Cancel
Save