main
dart 291 lines 9.17 KB
Raw
1 import 'dart:async';
2
3 import 'package:flutter/foundation.dart';
4 import 'package:flutter_riverpod/flutter_riverpod.dart';
5
6 import '../data/model_downloader.dart';
7 import '../data/kitten_tts_model.dart';
8 import '../data/kitten_tts_downloader.dart';
9 import '../data/piper_tts_model.dart';
10 import '../data/piper_tts_downloader.dart';
11
12 /// Provider for the KittenTTS downloader instance.
13 final kittenTtsDownloaderProvider = Provider<KittenTtsDownloader>((ref) {
14 return KittenTtsDownloader();
15 });
16
17 /// Provider for the Piper TTS downloader instance.
18 final piperTtsDownloaderProvider = Provider<PiperTtsDownloader>((ref) {
19 return PiperTtsDownloader();
20 });
21
22 final modelDownloaderProvider = Provider<ModelDownloader>((ref) {
23 return ModelDownloader();
24 });
25
26 /// Static list of all available KittenTTS models.
27 final kittenTtsModelsProvider = Provider<List<KittenTtsModel>>((ref) {
28 return KittenTtsModel.allModels;
29 });
30
31 /// Async provider for the set of downloaded KittenTTS variants.
32 final downloadedKittenTtsVariantsProvider =
33 FutureProvider<Set<KittenTtsModelVariant>>((ref) async {
34 final downloader = ref.read(kittenTtsDownloaderProvider);
35 return downloader.getDownloadedVariants();
36 });
37
38 /// Async provider for the set of downloaded Piper variants.
39 final downloadedPiperTtsVariantsProvider =
40 FutureProvider<Set<PiperTtsModelVariant>>((ref) async {
41 final downloader = ref.read(piperTtsDownloaderProvider);
42 return downloader.getDownloadedVariants();
43 });
44
45 // ── KittenTTS Download Progress ──
46
47 /// Notifier that tracks per-file download progress for KittenTTS models.
48 final ttsDownloadProgressProvider =
49 NotifierProvider<
50 TtsDownloadNotifier,
51 Map<KittenTtsModelVariant, Map<String, KittenTtsFileProgress>>
52 >(() => TtsDownloadNotifier());
53
54 class TtsDownloadNotifier
55 extends
56 Notifier<
57 Map<KittenTtsModelVariant, Map<String, KittenTtsFileProgress>>
58 > {
59 final Map<KittenTtsModelVariant, StreamSubscription<KittenTtsFileProgress>>
60 _subscriptions = {};
61
62 @override
63 Map<KittenTtsModelVariant, Map<String, KittenTtsFileProgress>> build() {
64 ref.onDispose(() {
65 for (final sub in _subscriptions.values) {
66 sub.cancel();
67 }
68 _subscriptions.clear();
69 });
70 return {};
71 }
72
73 /// Start downloading a model variant.
74 Future<void> startDownload(KittenTtsModel model) async {
75 final variant = model.variant;
76 final completer = Completer<void>();
77
78 final currentVariantProgress = Map<String, KittenTtsFileProgress>.from(
79 state[variant] ?? {},
80 );
81
82 state = {...state, variant: currentVariantProgress};
83
84 final downloader = ref.read(kittenTtsDownloaderProvider);
85
86 _subscriptions[variant]?.cancel();
87 _subscriptions[variant] = downloader
88 .downloadModel(model)
89 .listen(
90 (progress) {
91 if (!ref.mounted) return;
92 final variantMap = Map<String, KittenTtsFileProgress>.from(
93 state[variant] ?? {},
94 );
95 variantMap[progress.fileName] = progress;
96 state = {...state, variant: variantMap};
97 },
98 onDone: () {
99 _subscriptions.remove(variant);
100 if (ref.mounted) {
101 ref.invalidate(downloadedKittenTtsVariantsProvider);
102 }
103 if (!completer.isCompleted) completer.complete();
104 },
105 onError: (e) {
106 _ModelLog.error(
107 'KittenTTS download error for ${variant.displayName}: $e',
108 );
109 _subscriptions.remove(variant);
110 if (!completer.isCompleted) completer.complete();
111 },
112 );
113 return completer.future;
114 }
115
116 /// Cancel an in-flight download.
117 void cancelDownload(KittenTtsModelVariant variant) {
118 if (!ref.mounted) return;
119 ref.read(kittenTtsDownloaderProvider).cancelDownload(variant);
120 _subscriptions[variant]?.cancel();
121 _subscriptions.remove(variant);
122
123 final newState =
124 Map<KittenTtsModelVariant, Map<String, KittenTtsFileProgress>>.from(
125 state,
126 );
127 newState.remove(variant);
128 state = newState;
129 }
130
131 /// Delete a downloaded variant.
132 Future<void> deleteVariant(KittenTtsModelVariant variant) async {
133 if (!ref.mounted) return;
134 cancelDownload(variant);
135 await ref.read(kittenTtsDownloaderProvider).deleteVariant(variant);
136 if (!ref.mounted) return;
137
138 final newState =
139 Map<KittenTtsModelVariant, Map<String, KittenTtsFileProgress>>.from(
140 state,
141 );
142 newState.remove(variant);
143 state = newState;
144
145 ref.invalidate(downloadedKittenTtsVariantsProvider);
146 }
147
148 /// Check if a variant is currently being downloaded.
149 bool isDownloading(KittenTtsModelVariant variant) {
150 final variantProgress = state[variant];
151 if (variantProgress == null || variantProgress.isEmpty) return false;
152 return !variantProgress.values.every((f) => f.isComplete);
153 }
154
155 /// Get overall progress fraction for a variant (0.0–1.0).
156 double getOverallFraction(KittenTtsModelVariant variant) {
157 final variantProgress = state[variant];
158 if (variantProgress == null || variantProgress.isEmpty) return 0.0;
159 final total = variantProgress.values
160 .map((f) => f.fraction)
161 .fold<double>(0.0, (a, b) => a + b);
162 return total / variantProgress.length;
163 }
164 }
165
166 // ── Piper TTS Download Progress ──
167
168 /// Notifier that tracks per-file download progress for Piper TTS models.
169 final piperTtsDownloadProgressProvider =
170 NotifierProvider<
171 PiperTtsDownloadNotifier,
172 Map<PiperTtsModelVariant, Map<String, PiperTtsFileProgress>>
173 >(() => PiperTtsDownloadNotifier());
174
175 class PiperTtsDownloadNotifier
176 extends
177 Notifier<Map<PiperTtsModelVariant, Map<String, PiperTtsFileProgress>>> {
178 final Map<PiperTtsModelVariant, StreamSubscription<PiperTtsFileProgress>>
179 _subscriptions = {};
180 final Map<PiperTtsModelVariant, Completer<void>> _completers = {};
181
182 @override
183 Map<PiperTtsModelVariant, Map<String, PiperTtsFileProgress>> build() {
184 ref.onDispose(() {
185 for (final sub in _subscriptions.values) {
186 sub.cancel();
187 }
188 _subscriptions.clear();
189 _completers.clear();
190 });
191 return {};
192 }
193
194 Future<void> startDownload(PiperTtsModelVariant variant) async {
195 final completer = Completer<void>();
196 _completers[variant] = completer;
197 final currentVariantProgress = Map<String, PiperTtsFileProgress>.from(
198 state[variant] ?? {},
199 );
200
201 state = {...state, variant: currentVariantProgress};
202
203 final downloader = ref.read(piperTtsDownloaderProvider);
204
205 _subscriptions[variant]?.cancel();
206 _subscriptions[variant] = downloader
207 .downloadModel(variant)
208 .listen(
209 (progress) {
210 if (!ref.mounted) return;
211 final variantMap = Map<String, PiperTtsFileProgress>.from(
212 state[variant] ?? {},
213 );
214 variantMap[progress.fileName] = progress;
215 state = {...state, variant: variantMap};
216 },
217 onDone: () {
218 _subscriptions.remove(variant);
219 _completers.remove(variant);
220 if (ref.mounted) {
221 ref.invalidate(downloadedPiperTtsVariantsProvider);
222 }
223 if (!completer.isCompleted) completer.complete();
224 },
225 onError: (e) {
226 _ModelLog.error(
227 'Piper TTS download error for ${variant.displayName}: $e',
228 );
229 _subscriptions.remove(variant);
230 _completers.remove(variant);
231 if (!completer.isCompleted) completer.complete();
232 },
233 );
234 return completer.future;
235 }
236
237 void cancelDownload(PiperTtsModelVariant variant) {
238 if (!ref.mounted) return;
239 ref.read(piperTtsDownloaderProvider).cancelDownload(variant);
240 _subscriptions[variant]?.cancel();
241 _subscriptions.remove(variant);
242 final completer = _completers.remove(variant);
243 if (completer != null && !completer.isCompleted) {
244 completer.complete();
245 }
246
247 final newState =
248 Map<PiperTtsModelVariant, Map<String, PiperTtsFileProgress>>.from(
249 state,
250 );
251 newState.remove(variant);
252 state = newState;
253 }
254
255 Future<void> deleteVariant(PiperTtsModelVariant variant) async {
256 if (!ref.mounted) return;
257 cancelDownload(variant);
258 await ref.read(piperTtsDownloaderProvider).deleteVariant(variant);
259 if (!ref.mounted) return;
260
261 final newState =
262 Map<PiperTtsModelVariant, Map<String, PiperTtsFileProgress>>.from(
263 state,
264 );
265 newState.remove(variant);
266 state = newState;
267
268 ref.invalidate(downloadedPiperTtsVariantsProvider);
269 }
270
271 bool isDownloading(PiperTtsModelVariant variant) {
272 final variantProgress = state[variant];
273 if (variantProgress == null || variantProgress.isEmpty) return false;
274 return !variantProgress.values.every((f) => f.isComplete);
275 }
276
277 double getOverallFraction(PiperTtsModelVariant variant) {
278 final variantProgress = state[variant];
279 if (variantProgress == null || variantProgress.isEmpty) return 0.0;
280 final total = variantProgress.values
281 .map((f) => f.fraction)
282 .fold<double>(0.0, (a, b) => a + b);
283 return total / variantProgress.length;
284 }
285 }
286
287 class _ModelLog {
288 static void error(String message) {
289 debugPrint('[TtsModel] ERROR: $message');
290 }
291 }