cyberhybridhub/lib/services/spelling_tts/spelling_tts_web.dart
2026-06-07 03:10:21 +00:00

115 lines
2.7 KiB
Dart

import 'dart:async';
import 'dart:js_interop';
import 'package:flutter/foundation.dart';
import 'spelling_tts_backend.dart';
SpellingTtsBackend createSpellingTtsBackend() => _WebSpeechBackend();
@JS('speechSynthesis')
external SpeechSynthesis get _speechSynthesis;
@JS()
extension type SpeechSynthesis._(JSObject _) implements JSObject {
external void speak(SpeechSynthesisUtterance utterance);
external void cancel();
}
@JS()
extension type SpeechSynthesisUtterance._(JSObject _) implements JSObject {
external SpeechSynthesisUtterance();
external String text;
external String lang;
external double rate;
external double pitch;
@JS('onend')
external set onEnd(JSFunction? listener);
@JS('onerror')
external set onError(JSFunction? listener);
}
class _WebSpeechBackend implements SpellingTtsBackend {
Completer<void>? _pending;
@override
Future<void> speak(String text, {String? title}) async {
final String trimmed = text.trim();
if (trimmed.isEmpty) {
return;
}
if (title != null && title.isNotEmpty) {
debugPrint('Spelling TTS utterance title: $title');
}
await stop();
final Completer<void> done = Completer<void>();
_pending = done;
final SpeechSynthesisUtterance utterance = SpeechSynthesisUtterance();
utterance.text = trimmed;
utterance.lang = 'en-US';
utterance.rate = 0.85;
utterance.pitch = 1.0;
utterance.onEnd = (() {
if (!done.isCompleted) {
done.complete();
}
}).toJS;
utterance.onError = ((JSAny event) {
String details = 'unknown';
if (event.isA<JSObject>()) {
try {
final errEvent = event as JSObject;
details = (errEvent as SpeechSynthesisErrorEvent).error;
} catch (_) {}
}
debugPrint('Spelling web TTS error: $details ($event)');
if (!done.isCompleted) {
done.complete();
}
}).toJS;
try {
_speechSynthesis.speak(utterance);
await done.future.timeout(
const Duration(seconds: 30),
onTimeout: () {
debugPrint('Spelling web TTS timed out');
},
);
} catch (e, st) {
debugPrint('Spelling web TTS failed: $e\n$st');
if (!done.isCompleted) {
done.complete();
}
} finally {
if (identical(_pending, done)) {
_pending = null;
}
}
}
@override
Future<void> stop() async {
final Completer<void>? pending = _pending;
_pending = null;
try {
_speechSynthesis.cancel();
} catch (e, st) {
debugPrint('Spelling web TTS stop failed: $e\n$st');
}
pending?.complete();
}
}
@JS()
extension type SpeechSynthesisErrorEvent._(JSObject _) implements JSObject {
external String get error;
}