move text to speech controller out of pangea controller
This commit is contained in:
parent
9cb155fcf1
commit
e184e9a76f
18 changed files with 308 additions and 290 deletions
|
|
@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/video_player.dart';
|
|||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'choreographer_error_controller.dart';
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ import 'package:fluffychat/pangea/learning_settings/utils/locale_provider.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/spaces/controllers/space_code_controller.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -38,7 +37,6 @@ class PangeaController {
|
|||
late GetAnalyticsController getAnalytics;
|
||||
late PutAnalyticsController putAnalytics;
|
||||
late SubscriptionController subscriptionController;
|
||||
late TextToSpeechController textToSpeech;
|
||||
|
||||
///store Services
|
||||
final pLanguageStore = PLanguageStore();
|
||||
|
|
@ -82,7 +80,6 @@ class PangeaController {
|
|||
getAnalytics = GetAnalyticsController(this);
|
||||
putAnalytics = PutAnalyticsController(this);
|
||||
subscriptionController = SubscriptionController(this);
|
||||
textToSpeech = TextToSpeechController(this);
|
||||
PAuthGaurd.pController = this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/choice_animation.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../choreographer/it/it_shimmer.dart';
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import 'package:fluffychat/pangea/speech_to_text/audio_encoding_enum.dart';
|
|||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_repo.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_request_model.dart';
|
||||
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
|
|
@ -360,7 +362,7 @@ class PangeaMessageEvent {
|
|||
|
||||
final rep = representationByLanguage(langCode);
|
||||
final tokensResp = await rep?.requestTokens();
|
||||
final request = TextToSpeechRequest(
|
||||
final request = TextToSpeechRequestModel(
|
||||
text: rep?.content.text ?? body,
|
||||
tokens: tokensResp?.result?.map((t) => t.text).toList() ?? [],
|
||||
langCode: langCode,
|
||||
|
|
@ -368,10 +370,18 @@ class PangeaMessageEvent {
|
|||
userL2: _l2Code ?? LanguageKeys.unknownLanguage,
|
||||
);
|
||||
|
||||
final response = await MatrixState.pangeaController.textToSpeech.get(
|
||||
final result = await TextToSpeechRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request,
|
||||
);
|
||||
|
||||
if (result.error != null) {
|
||||
throw Exception(
|
||||
"Error getting text to speech: ${result.error}",
|
||||
);
|
||||
}
|
||||
|
||||
final response = result.result!;
|
||||
final audioBytes = base64.decode(response.audioContent);
|
||||
final fileName =
|
||||
"audio_for_${_event.eventId}_$langCode.${response.fileExtension}";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
|||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
|
||||
extension PangeaEvent on Event {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning_view.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/models/user_model.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
|
|||
105
lib/pangea/text_to_speech/text_to_speech_repo.dart
Normal file
105
lib/pangea/text_to_speech/text_to_speech_repo.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
|
||||
class _TextToSpeechCacheItem {
|
||||
final Future<TextToSpeechResponseModel> data;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _TextToSpeechCacheItem({
|
||||
required this.data,
|
||||
required this.timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
class TextToSpeechRepo {
|
||||
static final Map<String, _TextToSpeechCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<TextToSpeechResponseModel>> get(
|
||||
String accessToken,
|
||||
TextToSpeechRequestModel request,
|
||||
) {
|
||||
final cached = _getCached(request);
|
||||
if (cached != null) {
|
||||
return _getResult(request, cached);
|
||||
}
|
||||
|
||||
final future = _fetch(accessToken, request);
|
||||
_setCached(request, future);
|
||||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<TextToSpeechResponseModel> _fetch(
|
||||
String accessToken,
|
||||
TextToSpeechRequestModel request,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.textToSpeech,
|
||||
body: request.toJson(),
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to convert text to speech: ${res.statusCode} ${res.reasonPhrase}',
|
||||
);
|
||||
}
|
||||
|
||||
return TextToSpeechResponseModel.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes)),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Result<TextToSpeechResponseModel>> _getResult(
|
||||
TextToSpeechRequestModel request,
|
||||
Future<TextToSpeechResponseModel> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
return Result.value(res);
|
||||
} catch (e, s) {
|
||||
_cache.remove(request.hashCode.toString());
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: request.toJson(),
|
||||
);
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<TextToSpeechResponseModel>? _getCached(
|
||||
TextToSpeechRequestModel request,
|
||||
) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
for (final key in cacheKeys) {
|
||||
if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) {
|
||||
_cache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
return _cache[request.hashCode.toString()]?.data;
|
||||
}
|
||||
|
||||
static void _setCached(
|
||||
TextToSpeechRequestModel request,
|
||||
Future<TextToSpeechResponseModel> response,
|
||||
) =>
|
||||
_cache[request.hashCode.toString()] = _TextToSpeechCacheItem(
|
||||
data: response,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
38
lib/pangea/text_to_speech/text_to_speech_request_model.dart
Normal file
38
lib/pangea/text_to_speech/text_to_speech_request_model.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
|
||||
class TextToSpeechRequestModel {
|
||||
String text;
|
||||
String langCode;
|
||||
String userL1;
|
||||
String userL2;
|
||||
List<PangeaTokenText> tokens;
|
||||
|
||||
TextToSpeechRequestModel({
|
||||
required this.text,
|
||||
required this.langCode,
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
required this.tokens,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.text: text,
|
||||
ModelKey.langCode: langCode,
|
||||
ModelKey.userL1: userL1,
|
||||
ModelKey.userL2: userL2,
|
||||
ModelKey.tokens: tokens.map((token) => token.toJson()).toList(),
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TextToSpeechRequestModel &&
|
||||
other.text == text &&
|
||||
other.langCode == langCode;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => text.hashCode ^ langCode.hashCode;
|
||||
}
|
||||
117
lib/pangea/text_to_speech/text_to_speech_response_model.dart
Normal file
117
lib/pangea/text_to_speech/text_to_speech_response_model.dart
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
|
||||
class TextToSpeechResponseModel {
|
||||
String audioContent;
|
||||
String mimeType;
|
||||
int durationMillis;
|
||||
List<int> waveform;
|
||||
String fileExtension;
|
||||
List<TTSToken> ttsTokens;
|
||||
|
||||
TextToSpeechResponseModel({
|
||||
required this.audioContent,
|
||||
required this.mimeType,
|
||||
required this.durationMillis,
|
||||
required this.waveform,
|
||||
required this.fileExtension,
|
||||
required this.ttsTokens,
|
||||
});
|
||||
|
||||
factory TextToSpeechResponseModel.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TextToSpeechResponseModel(
|
||||
audioContent: json["audio_content"],
|
||||
mimeType: json["mime_type"],
|
||||
durationMillis: json["duration_millis"],
|
||||
waveform: List<int>.from(json["wave_form"]),
|
||||
fileExtension: json["file_extension"],
|
||||
ttsTokens: List<TTSToken>.from(
|
||||
json["tts_tokens"].map((x) => TTSToken.fromJson(x)),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"audio_content": audioContent,
|
||||
"mime_type": mimeType,
|
||||
"duration_millis": durationMillis,
|
||||
"wave_form": List<dynamic>.from(waveform.map((x) => x)),
|
||||
"file_extension": fileExtension,
|
||||
"tts_tokens": List<dynamic>.from(ttsTokens.map((x) => x.toJson())),
|
||||
};
|
||||
|
||||
PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) {
|
||||
return PangeaAudioEventData(
|
||||
text: text,
|
||||
langCode: langCode,
|
||||
tokens: ttsTokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TTSToken {
|
||||
final int startMS;
|
||||
final int endMS;
|
||||
final PangeaTokenText text;
|
||||
|
||||
TTSToken({
|
||||
required this.startMS,
|
||||
required this.endMS,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
factory TTSToken.fromJson(Map<String, dynamic> json) => TTSToken(
|
||||
startMS: json["start_ms"],
|
||||
endMS: json["end_ms"],
|
||||
text: PangeaTokenText.fromJson(json["text"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"start_ms": startMS,
|
||||
"end_ms": endMS,
|
||||
"text": text.toJson(),
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TTSToken &&
|
||||
other.startMS == startMS &&
|
||||
other.endMS == endMS &&
|
||||
other.text == text;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode;
|
||||
}
|
||||
|
||||
class PangeaAudioEventData {
|
||||
final String text;
|
||||
final String langCode;
|
||||
final List<TTSToken> tokens;
|
||||
|
||||
PangeaAudioEventData({
|
||||
required this.text,
|
||||
required this.langCode,
|
||||
required this.tokens,
|
||||
});
|
||||
|
||||
factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData(
|
||||
text: json[ModelKey.text] as String,
|
||||
langCode: json[ModelKey.langCode] as String,
|
||||
tokens: List<TTSToken>.from(
|
||||
(json[ModelKey.tokens] as Iterable)
|
||||
.map((x) => TTSToken.fromJson(x))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.text: text,
|
||||
ModelKey.langCode: langCode,
|
||||
ModelKey.tokens:
|
||||
List<Map<String, dynamic>>.from(tokens.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
|
@ -20,7 +20,10 @@ import 'package:fluffychat/pangea/common/widgets/card_header.dart';
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart'
|
||||
|
|
@ -263,35 +266,26 @@ class TtsController {
|
|||
String langCode,
|
||||
List<PangeaTokenText> tokens,
|
||||
) async {
|
||||
TextToSpeechResponse? ttsRes;
|
||||
try {
|
||||
loadingChoreoStream.add(true);
|
||||
ttsRes = await MatrixState.pangeaController.textToSpeech.get(
|
||||
TextToSpeechRequest(
|
||||
text: text,
|
||||
langCode: langCode,
|
||||
tokens: tokens,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.activeL1Code() ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
userL2:
|
||||
MatrixState.pangeaController.languageController.activeL2Code() ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
error_handler.ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'text': text,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
loadingChoreoStream.add(false);
|
||||
}
|
||||
TextToSpeechResponseModel? ttsRes;
|
||||
|
||||
if (ttsRes == null) return;
|
||||
loadingChoreoStream.add(true);
|
||||
final result = await TextToSpeechRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
TextToSpeechRequestModel(
|
||||
text: text,
|
||||
langCode: langCode,
|
||||
tokens: tokens,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.activeL1Code() ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
userL2:
|
||||
MatrixState.pangeaController.languageController.activeL2Code() ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
),
|
||||
);
|
||||
loadingChoreoStream.add(false);
|
||||
if (result.isError) return;
|
||||
ttsRes = result.result!;
|
||||
|
||||
try {
|
||||
Logs().i('Speaking from choreo: $text, langCode: $langCode');
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
|
||||
class PangeaAudioEventData {
|
||||
final String text;
|
||||
final String langCode;
|
||||
final List<TTSToken> tokens;
|
||||
|
||||
PangeaAudioEventData({
|
||||
required this.text,
|
||||
required this.langCode,
|
||||
required this.tokens,
|
||||
});
|
||||
|
||||
factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData(
|
||||
text: json[ModelKey.text] as String,
|
||||
langCode: json[ModelKey.langCode] as String,
|
||||
tokens: List<TTSToken>.from(
|
||||
(json[ModelKey.tokens] as Iterable)
|
||||
.map((x) => TTSToken.fromJson(x))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.text: text,
|
||||
ModelKey.langCode: langCode,
|
||||
ModelKey.tokens:
|
||||
List<Map<String, dynamic>>.from(tokens.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class TTSToken {
|
||||
final int startMS;
|
||||
final int endMS;
|
||||
final PangeaTokenText text;
|
||||
|
||||
TTSToken({
|
||||
required this.startMS,
|
||||
required this.endMS,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
factory TTSToken.fromJson(Map<String, dynamic> json) => TTSToken(
|
||||
startMS: json["start_ms"],
|
||||
endMS: json["end_ms"],
|
||||
text: PangeaTokenText.fromJson(json["text"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"start_ms": startMS,
|
||||
"end_ms": endMS,
|
||||
"text": text.toJson(),
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TTSToken &&
|
||||
other.startMS == startMS &&
|
||||
other.endMS == endMS &&
|
||||
other.text == text;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode;
|
||||
}
|
||||
|
||||
class TextToSpeechRequest {
|
||||
String text;
|
||||
String langCode;
|
||||
String userL1;
|
||||
String userL2;
|
||||
List<PangeaTokenText> tokens;
|
||||
|
||||
TextToSpeechRequest({
|
||||
required this.text,
|
||||
required this.langCode,
|
||||
required this.userL1,
|
||||
required this.userL2,
|
||||
required this.tokens,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
ModelKey.text: text,
|
||||
ModelKey.langCode: langCode,
|
||||
ModelKey.userL1: userL1,
|
||||
ModelKey.userL2: userL2,
|
||||
ModelKey.tokens: tokens.map((token) => token.toJson()).toList(),
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TextToSpeechRequest &&
|
||||
other.text == text &&
|
||||
other.langCode == langCode;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => text.hashCode ^ langCode.hashCode;
|
||||
}
|
||||
|
||||
class TextToSpeechResponse {
|
||||
String audioContent;
|
||||
String mimeType;
|
||||
int durationMillis;
|
||||
List<int> waveform;
|
||||
String fileExtension;
|
||||
List<TTSToken> ttsTokens;
|
||||
|
||||
TextToSpeechResponse({
|
||||
required this.audioContent,
|
||||
required this.mimeType,
|
||||
required this.durationMillis,
|
||||
required this.waveform,
|
||||
required this.fileExtension,
|
||||
required this.ttsTokens,
|
||||
});
|
||||
|
||||
factory TextToSpeechResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TextToSpeechResponse(
|
||||
audioContent: json["audio_content"],
|
||||
mimeType: json["mime_type"],
|
||||
durationMillis: json["duration_millis"],
|
||||
waveform: List<int>.from(json["wave_form"]),
|
||||
fileExtension: json["file_extension"],
|
||||
ttsTokens: List<TTSToken>.from(
|
||||
json["tts_tokens"].map((x) => TTSToken.fromJson(x)),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"audio_content": audioContent,
|
||||
"mime_type": mimeType,
|
||||
"duration_millis": durationMillis,
|
||||
"wave_form": List<dynamic>.from(waveform.map((x) => x)),
|
||||
"file_extension": fileExtension,
|
||||
"tts_tokens": List<dynamic>.from(ttsTokens.map((x) => x.toJson())),
|
||||
};
|
||||
|
||||
PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) {
|
||||
return PangeaAudioEventData(
|
||||
text: text,
|
||||
langCode: langCode,
|
||||
tokens: ttsTokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextToSpeechCacheItem {
|
||||
Future<TextToSpeechResponse> data;
|
||||
|
||||
_TextToSpeechCacheItem({
|
||||
required this.data,
|
||||
});
|
||||
}
|
||||
|
||||
class TextToSpeechController {
|
||||
static final Map<TextToSpeechRequest, _TextToSpeechCacheItem> _cache = {};
|
||||
late final PangeaController _pangeaController;
|
||||
|
||||
Timer? _cacheClearTimer;
|
||||
|
||||
TextToSpeechController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
_initializeCacheClearing();
|
||||
}
|
||||
|
||||
void _initializeCacheClearing() {
|
||||
const duration = Duration(minutes: 15); // Adjust the duration as needed
|
||||
_cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
|
||||
}
|
||||
|
||||
void _clearCache() {
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_cacheClearTimer?.cancel();
|
||||
}
|
||||
|
||||
Future<TextToSpeechResponse> get(
|
||||
TextToSpeechRequest params,
|
||||
) async {
|
||||
if (_cache.containsKey(params)) {
|
||||
return _cache[params]!.data;
|
||||
} else {
|
||||
final Future<TextToSpeechResponse> response = _fetchResponse(
|
||||
_pangeaController.userController.accessToken,
|
||||
params,
|
||||
);
|
||||
_cache[params] = _TextToSpeechCacheItem(data: response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<TextToSpeechResponse> _fetchResponse(
|
||||
String accessToken,
|
||||
TextToSpeechRequest params,
|
||||
) async {
|
||||
final Requests request = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final Response res = await request.post(
|
||||
url: PApiUrls.textToSpeech,
|
||||
body: params.toJson(),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(res.body);
|
||||
|
||||
return TextToSpeechResponse.fromJson(json);
|
||||
}
|
||||
|
||||
static bool isOggFile(Uint8List bytes) {
|
||||
// Check if the file has enough bytes for the header
|
||||
if (bytes.length < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the magic number for OGG file
|
||||
return bytes[0] == 0x4F &&
|
||||
bytes[1] == 0x67 &&
|
||||
bytes[2] == 0x67 &&
|
||||
bytes[3] == 0x53;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_choice.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
|
||||
class MessageAudioCard extends StatefulWidget {
|
||||
final PangeaMessageEvent messageEvent;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_ev
|
|||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/message_token_text/tokens_util.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class WordAudioButton extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
|||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/utils/report_message.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_controller.dart';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue