import 'dart:async'; import 'dart:typed_data'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; import 'package:flutter_azure_tts/flutter_azure_tts.dart'; import 'package:audio_session/audio_session.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; /// A custom AudioSource that reads audio data from a byte array class BytesAudioSource extends StreamAudioSource { final Uint8List _bytes; static const int _recommendedBufferSize = 8192; // 使用推荐的缓冲区大小 BytesAudioSource(this._bytes); @override Future request([int? start, int? end]) async { start = start ?? 0; end = end ?? _bytes.length; try { // 确保缓冲区大小至少为推荐值 final bufferSize = (end - start) < _recommendedBufferSize ? _recommendedBufferSize : end - start; final subData = _bytes.sublist(start, end); return StreamAudioResponse( sourceLength: _bytes.length, contentLength: subData.length, offset: start, stream: Stream.value(subData), contentType: 'audio/mpeg', ); } catch (e) { print('BytesAudioSource request error: $e'); rethrow; } } @override Future get length => Future.value(_bytes.length); } /// Azure TTS Service,支持将文本转换为语音并以流式方式边下载边播放 class AzureTtsService extends GetxService { static bool _isInitialized = false; final _audioPlayer = AudioPlayer(); final ConcatenatingAudioSource _playlist = ConcatenatingAudioSource(children: []); final isEnabled = false.obs; final isPlaying = false.obs; StreamSubscription? _playbackEventSubscription; StreamSubscription? _playerStateSubscription; AudioSession? _audioSession; // Azure 配置 final String _azureKey; final String _azureRegion; // 句子管理 String _pendingText = ''; final List _sentenceQueue = []; static final _sentenceBreaks = RegExp(r'[。!?.!?]'); // 状态 bool _isFetching = false; late Voice _selectedVoice; bool _isDisposed = false; AzureTtsService() : _azureKey = dotenv.env['AZURE_TTS_KEY'] ?? '', _azureRegion = dotenv.env['AZURE_TTS_REGION'] ?? '' { if (_azureKey.isEmpty || _azureRegion.isEmpty) { throw Exception('Azure TTS配置信息不完整,请检查环境变量'); } } @override Future onInit() async { super.onInit(); try { // 初始化音频会话 _audioSession = await AudioSession.instance; await _audioSession?.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playback, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, usage: AndroidAudioUsage.media, flags: AndroidAudioFlags.audibilityEnforced, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, )); // 设置音频播放器 if (!_isDisposed) { await _audioPlayer.setVolume(1.0); await _audioPlayer.setLoopMode(LoopMode.off); await _audioPlayer.setAudioSource( _playlist, initialPosition: Duration.zero, preload: false, ); _playbackEventSubscription = _audioPlayer.playbackEventStream.listen( (event) { if (_isDisposed) return; print('播放事件: $event'); if (event.processingState == ProcessingState.completed) { _tryPlayNext(); } }, onError: (error) { if (_isDisposed) return; print('播放错误: $error'); _handlePlaybackError(error); }, ); _playerStateSubscription = _audioPlayer.playerStateStream.listen((state) { if (_isDisposed) return; print('播放器状态变化: playing=${state.playing},processingState=${state.processingState}'); isPlaying.value = state.playing; if (state.processingState == ProcessingState.completed) { _tryPlayNext(); } }); } // 初始化 Azure TTS (只初始化一次) if (!_isInitialized && !_isDisposed) { FlutterAzureTts.init( subscriptionKey: _azureKey, region: _azureRegion, withLogs: true, ); _isInitialized = true; } // 获取可用语音列表并设置默认语音 if (!_isDisposed) { final voicesResponse = await FlutterAzureTts.getAvailableVoices(); _selectedVoice = voicesResponse.voices.firstWhere( (v) => v.shortName == 'zh-CN-YunxiNeural', orElse: () => voicesResponse.voices.first, ); print('已选择语音: ${_selectedVoice.shortName}'); } } catch (e, stackTrace) { print('初始化TTS服务失败: $e'); print('Stack trace: $stackTrace'); } } Future _cleanupResources() async { _isDisposed = true; await _playbackEventSubscription?.cancel(); await _playerStateSubscription?.cancel(); await _audioSession?.setActive(false); await stop(); await _audioPlayer.dispose(); } @override void onClose() async { print('关闭TTS服务'); await _cleanupResources(); super.onClose(); } void _handlePlaybackError(dynamic error) { print('处理播放错误: $error'); // 尝试重新初始化播放器 _reinitializePlayer(); } Future _reinitializePlayer() async { try { await _audioPlayer.stop(); await _playlist.clear(); await _audioPlayer.setAudioSource(_playlist); print('播放器重新初始化成功'); } catch (e) { print('播放器重新初始化失败: $e'); } } void _tryPlayNext() async { if (_playlist.length > 0 && !_audioPlayer.playing) { try { await _audioPlayer.seek(Duration.zero, index: 0); await _audioPlayer.play(); } catch (e) { print('尝试播放下一段失败: $e'); } } } Future speak(String text) async { if (!isEnabled.value) { print('TTS 未启用,跳过播放。'); return; } try { print('准备播放文本: $text'); _pendingText += text; _extractFullSentences(); await _startPreloadIfNeeded(); if (!_audioPlayer.playing && _playlist.length > 0) { print('开始播放音频'); await _audioPlayer.play(); } } catch (e) { print('播放文本失败: $e'); } } void _extractFullSentences() { if (_pendingText.isEmpty) return; final matches = _sentenceBreaks.allMatches(_pendingText).toList(); if (matches.isEmpty) return; int lastPos = 0; for (final match in matches) { final end = match.end; final sentence = _pendingText.substring(lastPos, end).trim(); if (sentence.isNotEmpty) { _sentenceQueue.add(sentence); } lastPos = end; } if (lastPos > 0) { _pendingText = _pendingText.substring(lastPos); } } Future _startPreloadIfNeeded() async { if (_isFetching) return; if (_sentenceQueue.isEmpty) return; _isFetching = true; await _fetchNextSegment(); } Future _fetchNextSegment() async { if (_sentenceQueue.isEmpty) { _isFetching = false; return; } final sentence = _sentenceQueue.removeAt(0); print('获取音频片段: $sentence'); try { final params = TtsParams( voice: _selectedVoice, audioFormat: AudioOutputFormat.audio16khz32kBitrateMonoMp3, rate: 1.0, text: sentence, ); final ttsResponse = await FlutterAzureTts.getTts(params); if (ttsResponse.audio != null && ttsResponse.audio.isNotEmpty) { print('成功获取音频数据: ${ttsResponse.audio.length} bytes'); try { final audioSource = BytesAudioSource(ttsResponse.audio); await _playlist.add(audioSource); print('音频片段已添加到播放列表'); if (!_audioPlayer.playing && _playlist.length == 1) { await _audioPlayer.setVolume(1.0); await _audioPlayer.seek(Duration.zero, index: 0); await Future.delayed(const Duration(milliseconds: 100)); await _audioPlayer.play(); } } catch (e) { print('添加音频源失败: $e'); } } else { print('获取音频数据失败: 返回为空或长度为0'); } } catch (e, stackTrace) { print('获取音频失败: $e'); print('Stack trace: $stackTrace'); } await _fetchNextSegment(); } Future stop() async { try { print('停止播放'); await _audioPlayer.stop(); await _playlist.clear(); _pendingText = ''; _sentenceQueue.clear(); _isFetching = false; isPlaying.value = false; } catch (e) { print('停止播放失败: $e'); } } void toggleEnabled() { isEnabled.toggle(); if (!isEnabled.value) { stop(); } } }