mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-24 10:30:43 +02:00
fix(download): estimate sizes from track quality metadata
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/l10n/app_localizations.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/widgets/download_service_picker.dart';
|
||||
@@ -56,6 +57,8 @@ class _PickerSettings extends SettingsNotifier {
|
||||
Future<void> _openPicker(
|
||||
WidgetTester tester, {
|
||||
Duration? duration,
|
||||
String? audioQuality = '16bit/44.1kHz',
|
||||
String? source = 'provider-a',
|
||||
AppSettings settings = const AppSettings(),
|
||||
Locale locale = const Locale('en'),
|
||||
double textScale = 1,
|
||||
@@ -83,7 +86,17 @@ Future<void> _openPicker(
|
||||
onPressed: () => DownloadServicePicker.show(
|
||||
context,
|
||||
trackName: 'Selected tracks',
|
||||
duration: duration,
|
||||
tracks: [
|
||||
Track(
|
||||
id: 'sample',
|
||||
name: 'Sample',
|
||||
artistName: 'Artist',
|
||||
albumName: 'Album',
|
||||
duration: duration?.inSeconds ?? 0,
|
||||
source: source,
|
||||
audioQuality: audioQuality,
|
||||
),
|
||||
],
|
||||
onSelect: onSelect ?? (_, _) {},
|
||||
),
|
||||
child: const Text('Open'),
|
||||
@@ -107,9 +120,7 @@ void main() {
|
||||
duration: const Duration(minutes: 4),
|
||||
onSelect: (quality, service) => selected = (quality, service),
|
||||
);
|
||||
expect(find.text('≈ 26.2 MB'), findsOneWidget);
|
||||
expect(find.text('≈ 85.7 MB if 24-bit/96kHz'), findsOneWidget);
|
||||
expect(find.text('≈ 171.4 MB if 24-bit/192kHz'), findsOneWidget);
|
||||
expect(find.text('≈ 26.2 MB'), findsNWidgets(3));
|
||||
expect(find.textContaining('MB–'), findsNothing);
|
||||
await tester.tap(find.text('Audio B'));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -135,16 +146,37 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('capped estimates show their quality assumption in Indonesian', (
|
||||
testWidgets('metadata caps both hi-res tiers to the available sample rate', (
|
||||
tester,
|
||||
) async {
|
||||
await _openPicker(
|
||||
tester,
|
||||
duration: const Duration(minutes: 4),
|
||||
audioQuality: '24bit/96kHz',
|
||||
locale: const Locale('id'),
|
||||
);
|
||||
expect(find.text('≈ 85.7 MB jika 24-bit/96kHz'), findsOneWidget);
|
||||
expect(find.text('≈ 171.4 MB jika 24-bit/192kHz'), findsOneWidget);
|
||||
expect(find.text('≈ 85.7 MB'), findsNWidgets(2));
|
||||
expect(find.text('≈ 26.2 MB'), findsOneWidget);
|
||||
expect(find.textContaining('171.4'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('foreign or incomplete track quality stays unavailable', (
|
||||
tester,
|
||||
) async {
|
||||
await _openPicker(
|
||||
tester,
|
||||
duration: const Duration(minutes: 4),
|
||||
source: 'provider-b',
|
||||
);
|
||||
expect(find.text('Size estimate unavailable'), findsNWidgets(3));
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pumpAndSettle();
|
||||
await _openPicker(
|
||||
tester,
|
||||
duration: const Duration(minutes: 4),
|
||||
audioQuality: '24bit',
|
||||
);
|
||||
expect(find.text('Size estimate unavailable'), findsNWidgets(3));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@@ -164,7 +196,7 @@ void main() {
|
||||
autoConvertBitrate: '256k',
|
||||
),
|
||||
);
|
||||
expect(find.text('≈ 26.2 MB'), findsOneWidget);
|
||||
expect(find.text('≈ 26.2 MB'), findsNWidgets(3));
|
||||
final conversionNote = find.textContaining(
|
||||
'After conversion to OPUS: ≈ 7.3 MB',
|
||||
);
|
||||
|
||||
@@ -3,18 +3,76 @@ import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/download_size_estimate.dart';
|
||||
|
||||
Track _track(int duration, {String? itemType}) => Track(
|
||||
Track _track(
|
||||
int duration, {
|
||||
String? itemType,
|
||||
String? audioQuality,
|
||||
String? source = 'provider-a',
|
||||
}) => Track(
|
||||
id: 'track',
|
||||
name: 'Track',
|
||||
artistName: 'Artist',
|
||||
albumName: 'Album',
|
||||
duration: duration,
|
||||
itemType: itemType,
|
||||
audioQuality: audioQuality,
|
||||
source: source,
|
||||
);
|
||||
|
||||
void main() {
|
||||
const duration = Duration(minutes: 4);
|
||||
|
||||
test('track metadata caps quality and sums each recording separately', () {
|
||||
final cd = _track(240, audioQuality: '16bit/44.1kHz');
|
||||
final hiRes = _track(120, audioQuality: '24-bit/96000Hz');
|
||||
for (final id in ['LOSSLESS', 'HI_RES', 'HI_RES_LOSSLESS']) {
|
||||
expect(
|
||||
estimateDownloadSize(
|
||||
duration: duration,
|
||||
quality: QualityOption(id: id, label: ''),
|
||||
tracks: [cd],
|
||||
providerId: 'provider-a',
|
||||
)!.bytes,
|
||||
27518400,
|
||||
);
|
||||
}
|
||||
final tracks = [cd, hiRes];
|
||||
expect(
|
||||
estimateDownloadSize(
|
||||
duration: totalDownloadDuration(tracks),
|
||||
quality: QualityOption.fromJson({
|
||||
'id': 'best',
|
||||
'label': 'Best',
|
||||
'kind': 'lossless',
|
||||
}),
|
||||
tracks: tracks,
|
||||
providerId: 'provider-a',
|
||||
)!.bytes,
|
||||
27518400 + 44928000,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'lossless metadata must be complete and belong to selected provider',
|
||||
() {
|
||||
for (final track in [
|
||||
_track(240),
|
||||
_track(240, audioQuality: '24bit'),
|
||||
_track(240, audioQuality: '16bit/44.1kHz', source: 'provider-b'),
|
||||
]) {
|
||||
expect(
|
||||
estimateDownloadSize(
|
||||
duration: duration,
|
||||
quality: const QualityOption(id: 'LOSSLESS', label: ''),
|
||||
tracks: [track],
|
||||
providerId: 'provider-a',
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test('four minutes at 256 kbps is 7,680,000 bytes before overhead', () {
|
||||
for (final quality in [
|
||||
const QualityOption(id: 'opus_256', label: 'Opus'),
|
||||
@@ -30,8 +88,6 @@ void main() {
|
||||
quality: quality,
|
||||
)!;
|
||||
expect(estimate.bytes, 7680000);
|
||||
expect(estimate.assumedBitDepth, isNull);
|
||||
expect(estimate.assumedSampleRate, isNull);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,16 +95,15 @@ void main() {
|
||||
const cd = QualityOption(id: 'LOSSLESS', label: 'Lossless');
|
||||
final cdSize = estimateDownloadSize(duration: duration, quality: cd)!;
|
||||
expect(cdSize.bytes, 27518400);
|
||||
expect(cdSize.assumedSampleRate, isNull);
|
||||
final batchSize = estimateDownloadSize(
|
||||
duration: totalDownloadDuration([_track(240), _track(240)]),
|
||||
quality: cd,
|
||||
)!;
|
||||
expect(batchSize.bytes, cdSize.bytes * 2);
|
||||
|
||||
for (final (id, rate, bytes) in [
|
||||
('HI_RES', 96000, 89856000),
|
||||
('HI_RES_LOSSLESS', 192000, 179712000),
|
||||
for (final (id, bytes) in [
|
||||
('HI_RES', 89856000),
|
||||
('HI_RES_LOSSLESS', 179712000),
|
||||
]) {
|
||||
final size = estimateDownloadSize(
|
||||
duration: duration,
|
||||
@@ -56,8 +111,6 @@ void main() {
|
||||
)!;
|
||||
expect(size.bytes, bytes);
|
||||
expect(size.bytes, greaterThan(cdSize.bytes));
|
||||
expect(size.assumedBitDepth, 24);
|
||||
expect(size.assumedSampleRate, rate);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,8 +127,6 @@ void main() {
|
||||
quality: quality,
|
||||
)!;
|
||||
expect(estimate.bytes, 22464000);
|
||||
expect(estimate.assumedBitDepth, isNull);
|
||||
expect(estimate.assumedSampleRate, isNull);
|
||||
final capped = estimateDownloadSize(
|
||||
duration: duration,
|
||||
quality: QualityOption.fromJson({
|
||||
@@ -89,8 +140,6 @@ void main() {
|
||||
}),
|
||||
)!;
|
||||
expect(capped.bytes, 179712000);
|
||||
expect(capped.assumedBitDepth, 24);
|
||||
expect(capped.assumedSampleRate, 192000);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user