82 lines
2.4 KiB
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();
|
|
}
|