103 lines
2.4 KiB
Dart
103 lines
2.4 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) {
|
|
debugPrint('Spelling web TTS error: $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();
|
|
}
|
|
}
|