Skip to content

Commit

Permalink
initial tts support for android
Browse files Browse the repository at this point in the history
  • Loading branch information
appdevelpo committed Feb 9, 2024
1 parent c274010 commit 6575ec0
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 84 deletions.
15 changes: 12 additions & 3 deletions assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"login": "Login",
"no-data": "No data",
"clear": "Clear",
"export": "Export"
"export": "Export",
"tts":"Text to speech",
"common": "Common"
},
"home": {
"continue-watching": "Continue",
Expand Down Expand Up @@ -202,11 +204,18 @@
"battery": "Battery",
"time": "Time",
"page-indicator": "Page Indicator",
"battery-icon": "Battery Icon"
"battery-icon": "Battery Icon",
"indicator-alignment": "Indicator Alignment"
},
"novel-settings": {
"font-size": "Font size",
"line":"Lines"
"line":"Lines",
"enable-tts": "Enable TTS",
"tts-rate": "TTS Rate",
"tts-lang": "TTS Language",
"tts-volume": "TTS Volume",
"tts-pitch": "TTS Pitch",
"text-color": "Text Color"
},
"bugreport": {
"auto-remove-subtitle": "delete in ~ days",
Expand Down
22 changes: 19 additions & 3 deletions lib/controllers/watch/comic_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ class ComicController extends ReaderController<ExtensionMangaWatch> {
scrollOffsetListener.changes.listen((event) {
hideControlPanel();
});
ever(height, (callback) {
super.height.value = callback;
});
// ever(height, (callback) {
// super.height.value = callback;
// });
ever(readType, (callback) {
_jumpPage(currentGlobalProgress.value);
// 保存设置
Expand All @@ -81,6 +81,22 @@ class ComicController extends ReaderController<ExtensionMangaWatch> {
callback,
);
});
ever(enableAutoScroll, (callback) {
if (callback) {
autoScrollTimer = Timer.periodic(
Duration(milliseconds: autoScrollInterval.value), (timer) {
if (isScrolled.value) {
scrollOffsetController.animateScroll(
duration: const Duration(milliseconds: 100),
curve: Curves.ease,
offset: autoScrollOffset.value,
);
}
});
return;
}
autoScrollTimer?.cancel();
});
//control footer 的 slider 改變時,更新頁碼
ever(progress, (callback) {
// 防止逆向回饋
Expand Down
62 changes: 59 additions & 3 deletions lib/controllers/watch/novel_controller.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:get/get.dart';
import 'package:miru_app/models/index.dart';
import 'package:miru_app/controllers/watch/reader_controller.dart';
Expand All @@ -23,21 +25,43 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {

// 字体大小
final fontSize = (18.0).obs;
final ttsRate = 0.3.obs;
final ttsVolume = 0.3.obs;
final ttsPitch = 0.3.obs;
final itemPositionsListener = ItemPositionsListener.create();
final isRecover = false.obs;
late final FlutterTts flutterTts;
final RxBool enableSelectText = false.obs;
final RxList ttsLang = [].obs;
final RxString ttsLangValue = ''.obs;
final playBackIsComplete = false;
late final RxList<String> subtitles =
List.generate(playList.length, (index) => "").obs;
final Rx<Color> textColor = Colors.white.obs;
initTts() {
ttsVolume.value = MiruStorage.getSetting(SettingKey.ttsVolume);
ttsRate.value = MiruStorage.getSetting(SettingKey.ttsRate);
ttsPitch.value = MiruStorage.getSetting(SettingKey.ttsPitch);
flutterTts = FlutterTts();
flutterTts.awaitSpeakCompletion(true);
flutterTts.setCompletionHandler(() {
debugPrint("completed");
});
}

@override
void onInit() async {
super.onInit();
getContent();
initTts();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
flutterTts = FlutterTts();
fontSize.value = MiruStorage.getSetting(SettingKey.novelFontSize);
// textColor.value = MiruStorage.getSetting(SettingKey.textColor);
WakelockPlus.toggle(
enable: MiruStorage.getSetting(SettingKey.enableWakelock));
List<dynamic> languages = await flutterTts.getLanguages;
debugPrint(languages.toString());
ttsLangValue.value = MiruStorage.getSetting(SettingKey.ttsLanguage);
ttsLang.value = await flutterTts.getLanguages;
debugPrint(ttsLang.toString());
itemPositionsListener.itemPositions.addListener(() {
if (itemPositionsListener.itemPositions.value.isEmpty) {
return;
Expand Down Expand Up @@ -83,6 +107,26 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
currentGlobalProgress.value = callback;
_jumpLine(callback);
});
// tts 播放
ever(enableAutoScroll, (callback) async {
await flutterTts.setLanguage(ttsLangValue.value);
await flutterTts.setSpeechRate(ttsRate.value);
await flutterTts.setVolume(ttsVolume.value);
await flutterTts.setPitch(ttsPitch.value);
for (int i = currentLocalProgress.value;
i < itemlength[index.value];
i++) {
if (!enableAutoScroll.value) {
await flutterTts.stop();
break;
}
final readingProgress = items[index.value][i];
debugPrint("current reading: $readingProgress , progress: $i");
animeScrollTo(localToGloabalProgress(i) - 1);
await flutterTts.speak(items[index.value][i]);
}
enableAutoScroll.value = false;
});
ever(currentGlobalProgress, (callback) {
if (updateSlider.value) {
progress.value = callback;
Expand Down Expand Up @@ -131,6 +175,13 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
itemScrollController.jumpTo(index: index);
}

animeScrollTo(index) {
itemScrollController.scrollTo(
index: index,
duration: const Duration(milliseconds: 10),
);
}

@override
void onClose() {
if (super.watchData.value != null) {
Expand All @@ -141,10 +192,12 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
);
}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
flutterTts.stop();
mouseTimer?.cancel();
super.onClose();
}

//獲取目標章節內容,但不更新當前頁面
@override
Future<void> loadTargetContent(int targetIndex) async {
try {
Expand All @@ -155,6 +208,7 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
await runtime.watch(playList[targetIndex].url);
items[targetIndex] = updatedData.content as List<String>;
itemlength[targetIndex] = updatedData.content.length;
subtitles[targetIndex] = updatedData.subtitle ?? '';
} catch (e) {
error.value = e.toString();
}
Expand All @@ -166,6 +220,7 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
return;
}

// 加載上一章節,並跳轉到剛才的位置
@override
Future<void> loadPrevChapter() async {
await loadTargetContent(index.value - 1);
Expand All @@ -185,6 +240,7 @@ class NovelController extends ReaderController<ExtensionFikushonWatch> {
await runtime.watch(cuurentPlayUrl) as ExtensionFikushonWatch;
itemlength[index.value] = (watchData.value as dynamic)?.content.length;
items[index.value] = (watchData.value as dynamic)?.content;
subtitles[index.value] = (watchData.value as dynamic)?.subtitle ?? '';
} catch (e) {
error.value = e.toString();
}
Expand Down
40 changes: 12 additions & 28 deletions lib/controllers/watch/reader_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ abstract class ReaderController<T> extends GetxController {
final isScrolled = true.obs;
final updateSlider = true.obs;
final isInfinityScrollMode = false.obs;
final isLoading = false.obs;
Timer? _barreryTimer;
//點擊區域是否反轉
final RxBool tapRegionIsReversed = false.obs;
Expand All @@ -62,7 +61,7 @@ abstract class ReaderController<T> extends GetxController {
final RxDouble nextPageHitBox = 0.3.obs;
final RxDouble prevPageHitBox = 0.3.obs;
final enableAutoScroll = false.obs;
final height = 1000.0.obs;
// final height = 1000.0.obs;
final RxBool isMouseHover = false.obs;
final RxBool setControllPanel = false.obs;
Timer? mouseTimer;
Expand Down Expand Up @@ -102,26 +101,7 @@ abstract class ReaderController<T> extends GetxController {
await _statusBar();
_barreryTimer =
Timer.periodic(const Duration(seconds: 10), (timer) => _statusBar());
// ever(index, (callback) {
// getContent();
// });

ever(enableAutoScroll, (callback) {
if (callback) {
autoScrollTimer = Timer.periodic(
Duration(milliseconds: autoScrollInterval.value), (timer) {
if (isScrolled.value) {
scrollOffsetController.animateScroll(
duration: const Duration(milliseconds: 100),
curve: Curves.ease,
offset: autoScrollOffset.value,
);
}
});
return;
}
autoScrollTimer?.cancel();
});
mouseTimer = Timer.periodic(const Duration(milliseconds: 300), (timer) {
if (setControllPanel.value) {
isShowControlPanel.value = true;
Expand All @@ -141,16 +121,20 @@ abstract class ReaderController<T> extends GetxController {
return progress;
}

int globalToLocalProgress(int globalProgress) {
int progress = globalProgress;
for (int i = 0; i < index.value; i++) {
if (globalProgress < itemlength[i]) {
List<int> globalToLocalProgress(int globalProgress) {
int fullIndex = 0;
int localProgress = 0;
int chapter = 0;
// debugPrint(currentLocalProgress.value.toString());
for (int i = 0; i < itemlength.length; i++) {
fullIndex += itemlength[i];
if (fullIndex > globalProgress) {
chapter = i;
localProgress = globalProgress - (fullIndex - itemlength[i]);
break;
}
globalProgress -= itemlength[i];
}
debugPrint(progress.toString());
return progress;
return [localProgress, chapter];
}

void previousPage() {}
Expand Down
13 changes: 13 additions & 0 deletions lib/utils/color.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,17 @@ class ColorUtils {
][colorIndex];
return color!;
}

static List<Color> baseColors = [
Colors.white,
Colors.black,
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.cyan,
Colors.blue,
Colors.purple,
Colors.transparent,
];
}
9 changes: 9 additions & 0 deletions lib/utils/miru_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ class MiruStorage {
await _initSetting(SettingKey.prevPageHitBox, 0.2);
await _initSetting(SettingKey.autoScrollInterval, 300);
await _initSetting(SettingKey.autoScrollOffset, 20.0);
await _initSetting(
SettingKey.ttsLanguage, Platform.localeName.split('_')[0]);
await _initSetting(SettingKey.ttsPitch, 0.3);
await _initSetting(SettingKey.ttsRate, 0.3);
await _initSetting(SettingKey.ttsVolume, 0.5);
}

static _initSetting(String key, dynamic value) async {
Expand Down Expand Up @@ -198,4 +203,8 @@ class SettingKey {
static String prevPageHitBox = "PrevPageHitBox";
static String autoScrollInterval = "AutoScrollInterval";
static String autoScrollOffset = "AutoScrollOffset";
static String ttsLanguage = "TTSLanguage";
static String ttsPitch = "TTSPitch";
static String ttsRate = "TTSRate";
static String ttsVolume = "TTSVolume";
}
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ class _ComicReaderContentState extends State<ComicReaderContent> {

@override
Widget build(BuildContext context) {
_c.height.value = MediaQuery.of(context).size.height;
// _c.height.value = MediaQuery.of(context).size.height;
return PlatformBuildWidget(
androidBuilder: (context) {
return Scaffold(
Expand Down
18 changes: 9 additions & 9 deletions lib/views/pages/watch/reader/novel/novel_reader_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ class _NovelReaderContentState extends State<NovelReaderContent> {
if (_c.watchData.value == null) {
return const Center(child: ProgressRing());
}

final watchData = _c.watchData.value!;

final listviewPadding =
maxWidth > 800 ? ((maxWidth - 800) / 2) : 16.0;

Expand Down Expand Up @@ -108,23 +105,25 @@ class _NovelReaderContentState extends State<NovelReaderContent> {
vertical: 16,
),
itemBuilder: (context, index) {
if (_c.globalToLocalProgress(index) == 0) {
final localProgress = _c.globalToLocalProgress(index);
if (localProgress[0] == 0) {
return Column(children: [
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Text(
_c.title + _c.playList[_c.index.value].name,
_c.title + _c.playList[localProgress[1]].name,
style: const TextStyle(fontSize: 26),
),
),
if (watchData.subtitle != null) ...[
if (_c
.subtitles[localProgress[1]].isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Text(
watchData.subtitle!,
_c.subtitles[localProgress[1]],
style: const TextStyle(fontSize: 20),
),
)
Expand Down Expand Up @@ -186,7 +185,7 @@ class _NovelReaderContentState extends State<NovelReaderContent> {

Widget _textContent(int index, double fontSize) {
final content = _c.items.expand((element) => element).toList();
return Padding(
return Obx(() => Padding(
padding: const EdgeInsets.only(bottom: 20),
child: SelectableText.rich(
onTap: () {
Expand All @@ -198,6 +197,7 @@ class _NovelReaderContentState extends State<NovelReaderContent> {
TextSpan(
text: content[index],
style: TextStyle(
color: _c.textColor.value,
fontSize: fontSize,
fontWeight: FontWeight.w400,
height: 2,
Expand All @@ -207,7 +207,7 @@ class _NovelReaderContentState extends State<NovelReaderContent> {
),
],
),
));
)));
// Obx(() => Column(
// children: [
// _c.enableSelectText.value
Expand Down
Loading

0 comments on commit 6575ec0

Please sign in to comment.