mymuseum-visitapp/lib/Services/Glasses/engines/impl/elevenlabs_tts_engine.dart

82 lines
2.4 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:mymuseum_visitapp/PlatformChannels/audio_routing_channel.dart';
import 'package:mymuseum_visitapp/Services/Glasses/engines/tts_engine.dart';
/// TTS via ElevenLabs API — voix naturelle, payant.
/// Upgrade par rapport à FlutterTtsEngine quand la qualité vocale prime.
class ElevenLabsTtsEngine implements TtsEngine {
static const String _baseUrl = 'https://api.elevenlabs.io/v1';
final String apiKey;
final String voiceId;
final AudioPlayer _player = AudioPlayer();
bool _speaking = false;
String? _lastTempPath;
ElevenLabsTtsEngine({required this.apiKey, required this.voiceId});
@override
bool get isSpeaking => _speaking;
@override
Future<void> speak(String text, {String languageCode = 'fr-FR'}) async {
if (text.isEmpty) return;
try {
_speaking = true;
if (!Platform.isWindows) await AudioRoutingChannel.enableBluetoothOutput();
await _speakElevenLabs(text);
} catch (e) {
debugPrint('[ElevenLabsTtsEngine] speak error: $e');
} finally {
_speaking = false;
}
}
@override
Future<void> stop() async {
await _player.stop();
_speaking = false;
}
@override
Future<void> replay() async {
if (_lastTempPath == null) return;
await _player.seek(Duration.zero);
await _player.play();
}
Future<void> _speakElevenLabs(String text) async {
final response = await http.post(
Uri.parse('$_baseUrl/text-to-speech/$voiceId'),
headers: {
'xi-api-key': apiKey,
'Content-Type': 'application/json',
'Accept': 'audio/mpeg',
},
body: jsonEncode({
'text': text,
'model_id': 'eleven_multilingual_v2',
'voice_settings': {'stability': 0.5, 'similarity_boost': 0.75},
}),
);
if (response.statusCode != 200) {
throw Exception('ElevenLabs ${response.statusCode}');
}
final dir = await getTemporaryDirectory();
final file = File('${dir.path}/glasses_tts.mp3');
await file.writeAsBytes(response.bodyBytes, flush: true);
_lastTempPath = file.path;
await _player.setFilePath(file.path);
await _player.play();
}
void dispose() => _player.dispose();
}