From e9b24712c5c8bacf7e4ac8d6137aefc4b5d0b497 Mon Sep 17 00:00:00 2001 From: zarzet Date: Fri, 27 Mar 2026 04:21:11 +0700 Subject: [PATCH] feat: cache spectrogram as PNG for instant loading on subsequent views --- lib/widgets/audio_analysis_widget.dart | 55 +++++++++++++++++++++----- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/lib/widgets/audio_analysis_widget.dart b/lib/widgets/audio_analysis_widget.dart index 8aac021..6b2b0a9 100644 --- a/lib/widgets/audio_analysis_widget.dart +++ b/lib/widgets/audio_analysis_widget.dart @@ -150,14 +150,12 @@ class _AudioAnalysisCardState extends State { _data = cached; _checkingCache = false; }); - if (cached.spectrum != null && cached.spectrum!.sliceCount > 0) { - final image = await _renderSpectrogramToImage(cached.spectrum!); - if (mounted) { - setState(() { - _spectrogramImage?.dispose(); - _spectrogramImage = image; - }); - } + final image = await _loadSpectrogramFromCache(widget.filePath); + if (image != null && mounted) { + setState(() { + _spectrogramImage?.dispose(); + _spectrogramImage = image; + }); } return; } @@ -177,17 +175,25 @@ class _AudioAnalysisCardState extends State { try { final cached = await _loadFromCache(widget.filePath); AudioAnalysisData data; + bool fromCache = false; if (cached != null) { data = cached; + fromCache = true; } else { data = await _runAnalysis(widget.filePath); _saveToCache(widget.filePath, data); } ui.Image? image; - if (data.spectrum != null && data.spectrum!.sliceCount > 0) { + if (fromCache) { + image = await _loadSpectrogramFromCache(widget.filePath); + } + if (image == null && + data.spectrum != null && + data.spectrum!.sliceCount > 0) { image = await _renderSpectrogramToImage(data.spectrum!); + _saveSpectrogramToCache(widget.filePath, image); } if (mounted) { @@ -259,6 +265,37 @@ class _AudioAnalysisCardState extends State { } catch (_) {} } + static Future _saveSpectrogramToCache( + String filePath, + ui.Image image, + ) async { + try { + final dir = await _cacheDir(); + final key = _cacheKey(filePath); + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData != null) { + final file = File('${dir.path}/$key.png'); + await file.writeAsBytes(byteData.buffer.asUint8List()); + } + } catch (_) {} + } + + static Future _loadSpectrogramFromCache(String filePath) async { + try { + final dir = await _cacheDir(); + final key = _cacheKey(filePath); + final file = File('${dir.path}/$key.png'); + if (!await file.exists()) return null; + + final bytes = await file.readAsBytes(); + final completer = Completer(); + ui.decodeImageFromList(bytes, completer.complete); + return completer.future; + } catch (_) { + return null; + } + } + Future _runAnalysis(String filePath) async { await FFmpegKitConfig.setLogLevel(Level.avLogError);