import 'dart:math'; /// Caches successful tag reads by stable media identity. Descriptor paths are /// short-lived and require their display-name hint to select the audio parser. class PlaybackNormalizationCache { final Future> Function(String, {String? displayName}) readMetadata; final void Function(Object)? onReadError; final Map _volumes = {}; static final _gainNumber = RegExp(r'-?\d+(\.\d+)?'); PlaybackNormalizationCache({required this.readMetadata, this.onReadError}); Future volumeFor( String path, { String? cacheKey, String? displayName, }) async { final key = cacheKey ?? path; final cached = _volumes[key]; if (cached != null) return cached; try { final metadata = await readMetadata(path, displayName: displayName); if (metadata['error'] != null) { onReadError?.call(metadata['error'] as Object); return 1.0; } final gain = _gain(metadata['replaygain_track_gain']) ?? _gain(metadata['replaygain_album_gain']); final volume = gain == null ? 1.0 : pow(10.0, gain / 20.0).toDouble().clamp(0.0, 1.0); if (_volumes.length >= 128) _volumes.remove(_volumes.keys.first); _volumes[key] = volume; return volume; } catch (error) { onReadError?.call(error); return 1.0; } } static double? _gain(Object? value) { final match = _gainNumber.firstMatch(value?.toString() ?? ''); return match == null ? null : double.tryParse(match.group(0)!); } }