diff --git a/lib/providers/user_preferences.dart b/lib/providers/user_preferences.dart index 4cd9c57..8adb9e1 100644 --- a/lib/providers/user_preferences.dart +++ b/lib/providers/user_preferences.dart @@ -178,4 +178,8 @@ class UserPreferencesProvider extends GetxController { setData(PreferencesTableCompanion(playerWakelock: Value(wakelock))); WakelockPlus.toggle(enable: wakelock); } + + void setOverrideCacheProvider(bool override) { + setData(PreferencesTableCompanion(overrideCacheProvider: Value(override))); + } } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index e9155cb..381c415 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -220,6 +220,19 @@ class _SettingsScreenState extends State { ), ), const Divider(thickness: 0.3, height: 1), + Obx( + () => CheckboxListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + secondary: const Icon(Icons.all_inclusive), + title: const Text('Override Cache Provider'), + subtitle: const Text( + 'Decide whether use original cached source or query a new one from current audio provider'), + value: _preferences.state.value.endlessPlayback, + onChanged: (value) => + _preferences.setOverrideCacheProvider(value ?? false), + ), + ), + const Divider(thickness: 0.3, height: 1), Obx( () => SwitchListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24), diff --git a/lib/services/database/database.dart b/lib/services/database/database.dart index a0b0832..55ce135 100755 --- a/lib/services/database/database.dart +++ b/lib/services/database/database.dart @@ -55,7 +55,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 1; + int get schemaVersion => 2; @override MigrationStrategy get migration { @@ -63,7 +63,14 @@ class AppDatabase extends _$AppDatabase { onCreate: (Migrator m) async { await m.createAll(); }, - onUpgrade: (Migrator m, int from, int to) async {}, + onUpgrade: (Migrator m, int from, int to) async { + if (from < 2) { + await m.addColumn( + preferencesTable, + preferencesTable.overrideCacheProvider, + ); + } + }, ); } } diff --git a/lib/services/database/database.g.dart b/lib/services/database/database.g.dart index f31d6d9..67805a5 100644 --- a/lib/services/database/database.g.dart +++ b/lib/services/database/database.g.dart @@ -640,6 +640,16 @@ class $PreferencesTableTable extends PreferencesTable defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("player_wakelock" IN (0, 1))'), defaultValue: const Constant(true)); + static const VerificationMeta _overrideCacheProviderMeta = + const VerificationMeta('overrideCacheProvider'); + @override + late final GeneratedColumn overrideCacheProvider = + GeneratedColumn('override_cache_provider', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("override_cache_provider" IN (0, 1))'), + defaultValue: const Constant(true)); @override List get $columns => [ id, @@ -665,7 +675,8 @@ class $PreferencesTableTable extends PreferencesTable streamMusicCodec, downloadMusicCodec, endlessPlayback, - playerWakelock + playerWakelock, + overrideCacheProvider ]; @override String get aliasedName => _alias ?? actualTableName; @@ -760,6 +771,12 @@ class $PreferencesTableTable extends PreferencesTable playerWakelock.isAcceptableOrUnknown( data['player_wakelock']!, _playerWakelockMeta)); } + if (data.containsKey('override_cache_provider')) { + context.handle( + _overrideCacheProviderMeta, + overrideCacheProvider.isAcceptableOrUnknown( + data['override_cache_provider']!, _overrideCacheProviderMeta)); + } return context; } @@ -830,6 +847,9 @@ class $PreferencesTableTable extends PreferencesTable .read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!, playerWakelock: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}player_wakelock'])!, + overrideCacheProvider: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}override_cache_provider'])!, ); } @@ -894,6 +914,7 @@ class PreferencesTableData extends DataClass final SourceCodecs downloadMusicCodec; final bool endlessPlayback; final bool playerWakelock; + final bool overrideCacheProvider; const PreferencesTableData( {required this.id, required this.audioQuality, @@ -918,7 +939,8 @@ class PreferencesTableData extends DataClass required this.streamMusicCodec, required this.downloadMusicCodec, required this.endlessPlayback, - required this.playerWakelock}); + required this.playerWakelock, + required this.overrideCacheProvider}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -986,6 +1008,7 @@ class PreferencesTableData extends DataClass } map['endless_playback'] = Variable(endlessPlayback); map['player_wakelock'] = Variable(playerWakelock); + map['override_cache_provider'] = Variable(overrideCacheProvider); return map; } @@ -1015,6 +1038,7 @@ class PreferencesTableData extends DataClass downloadMusicCodec: Value(downloadMusicCodec), endlessPlayback: Value(endlessPlayback), playerWakelock: Value(playerWakelock), + overrideCacheProvider: Value(overrideCacheProvider), ); } @@ -1058,6 +1082,8 @@ class PreferencesTableData extends DataClass .fromJson(serializer.fromJson(json['downloadMusicCodec'])), endlessPlayback: serializer.fromJson(json['endlessPlayback']), playerWakelock: serializer.fromJson(json['playerWakelock']), + overrideCacheProvider: + serializer.fromJson(json['overrideCacheProvider']), ); } @override @@ -1100,6 +1126,7 @@ class PreferencesTableData extends DataClass .toJson(downloadMusicCodec)), 'endlessPlayback': serializer.toJson(endlessPlayback), 'playerWakelock': serializer.toJson(playerWakelock), + 'overrideCacheProvider': serializer.toJson(overrideCacheProvider), }; } @@ -1127,7 +1154,8 @@ class PreferencesTableData extends DataClass SourceCodecs? streamMusicCodec, SourceCodecs? downloadMusicCodec, bool? endlessPlayback, - bool? playerWakelock}) => + bool? playerWakelock, + bool? overrideCacheProvider}) => PreferencesTableData( id: id ?? this.id, audioQuality: audioQuality ?? this.audioQuality, @@ -1153,6 +1181,8 @@ class PreferencesTableData extends DataClass downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, endlessPlayback: endlessPlayback ?? this.endlessPlayback, playerWakelock: playerWakelock ?? this.playerWakelock, + overrideCacheProvider: + overrideCacheProvider ?? this.overrideCacheProvider, ); PreferencesTableData copyWithCompanion(PreferencesTableCompanion data) { return PreferencesTableData( @@ -1216,6 +1246,9 @@ class PreferencesTableData extends DataClass playerWakelock: data.playerWakelock.present ? data.playerWakelock.value : this.playerWakelock, + overrideCacheProvider: data.overrideCacheProvider.present + ? data.overrideCacheProvider.value + : this.overrideCacheProvider, ); } @@ -1245,7 +1278,8 @@ class PreferencesTableData extends DataClass ..write('streamMusicCodec: $streamMusicCodec, ') ..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('endlessPlayback: $endlessPlayback, ') - ..write('playerWakelock: $playerWakelock') + ..write('playerWakelock: $playerWakelock, ') + ..write('overrideCacheProvider: $overrideCacheProvider') ..write(')')) .toString(); } @@ -1275,7 +1309,8 @@ class PreferencesTableData extends DataClass streamMusicCodec, downloadMusicCodec, endlessPlayback, - playerWakelock + playerWakelock, + overrideCacheProvider ]); @override bool operator ==(Object other) => @@ -1304,7 +1339,8 @@ class PreferencesTableData extends DataClass other.streamMusicCodec == this.streamMusicCodec && other.downloadMusicCodec == this.downloadMusicCodec && other.endlessPlayback == this.endlessPlayback && - other.playerWakelock == this.playerWakelock); + other.playerWakelock == this.playerWakelock && + other.overrideCacheProvider == this.overrideCacheProvider); } class PreferencesTableCompanion extends UpdateCompanion { @@ -1332,6 +1368,7 @@ class PreferencesTableCompanion extends UpdateCompanion { final Value downloadMusicCodec; final Value endlessPlayback; final Value playerWakelock; + final Value overrideCacheProvider; const PreferencesTableCompanion({ this.id = const Value.absent(), this.audioQuality = const Value.absent(), @@ -1357,6 +1394,7 @@ class PreferencesTableCompanion extends UpdateCompanion { this.downloadMusicCodec = const Value.absent(), this.endlessPlayback = const Value.absent(), this.playerWakelock = const Value.absent(), + this.overrideCacheProvider = const Value.absent(), }); PreferencesTableCompanion.insert({ this.id = const Value.absent(), @@ -1383,6 +1421,7 @@ class PreferencesTableCompanion extends UpdateCompanion { this.downloadMusicCodec = const Value.absent(), this.endlessPlayback = const Value.absent(), this.playerWakelock = const Value.absent(), + this.overrideCacheProvider = const Value.absent(), }); static Insertable custom({ Expression? id, @@ -1409,6 +1448,7 @@ class PreferencesTableCompanion extends UpdateCompanion { Expression? downloadMusicCodec, Expression? endlessPlayback, Expression? playerWakelock, + Expression? overrideCacheProvider, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1439,6 +1479,8 @@ class PreferencesTableCompanion extends UpdateCompanion { 'download_music_codec': downloadMusicCodec, if (endlessPlayback != null) 'endless_playback': endlessPlayback, if (playerWakelock != null) 'player_wakelock': playerWakelock, + if (overrideCacheProvider != null) + 'override_cache_provider': overrideCacheProvider, }); } @@ -1466,7 +1508,8 @@ class PreferencesTableCompanion extends UpdateCompanion { Value? streamMusicCodec, Value? downloadMusicCodec, Value? endlessPlayback, - Value? playerWakelock}) { + Value? playerWakelock, + Value? overrideCacheProvider}) { return PreferencesTableCompanion( id: id ?? this.id, audioQuality: audioQuality ?? this.audioQuality, @@ -1492,6 +1535,8 @@ class PreferencesTableCompanion extends UpdateCompanion { downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, endlessPlayback: endlessPlayback ?? this.endlessPlayback, playerWakelock: playerWakelock ?? this.playerWakelock, + overrideCacheProvider: + overrideCacheProvider ?? this.overrideCacheProvider, ); } @@ -1589,6 +1634,10 @@ class PreferencesTableCompanion extends UpdateCompanion { if (playerWakelock.present) { map['player_wakelock'] = Variable(playerWakelock.value); } + if (overrideCacheProvider.present) { + map['override_cache_provider'] = + Variable(overrideCacheProvider.value); + } return map; } @@ -1618,7 +1667,8 @@ class PreferencesTableCompanion extends UpdateCompanion { ..write('streamMusicCodec: $streamMusicCodec, ') ..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('endlessPlayback: $endlessPlayback, ') - ..write('playerWakelock: $playerWakelock') + ..write('playerWakelock: $playerWakelock, ') + ..write('overrideCacheProvider: $overrideCacheProvider') ..write(')')) .toString(); } @@ -4109,6 +4159,7 @@ typedef $$PreferencesTableTableCreateCompanionBuilder Value downloadMusicCodec, Value endlessPlayback, Value playerWakelock, + Value overrideCacheProvider, }); typedef $$PreferencesTableTableUpdateCompanionBuilder = PreferencesTableCompanion Function({ @@ -4136,6 +4187,7 @@ typedef $$PreferencesTableTableUpdateCompanionBuilder Value downloadMusicCodec, Value endlessPlayback, Value playerWakelock, + Value overrideCacheProvider, }); class $$PreferencesTableTableTableManager extends RootTableManager< @@ -4180,6 +4232,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager< Value downloadMusicCodec = const Value.absent(), Value endlessPlayback = const Value.absent(), Value playerWakelock = const Value.absent(), + Value overrideCacheProvider = const Value.absent(), }) => PreferencesTableCompanion( id: id, @@ -4206,6 +4259,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager< downloadMusicCodec: downloadMusicCodec, endlessPlayback: endlessPlayback, playerWakelock: playerWakelock, + overrideCacheProvider: overrideCacheProvider, ), createCompanionCallback: ({ Value id = const Value.absent(), @@ -4232,6 +4286,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager< Value downloadMusicCodec = const Value.absent(), Value endlessPlayback = const Value.absent(), Value playerWakelock = const Value.absent(), + Value overrideCacheProvider = const Value.absent(), }) => PreferencesTableCompanion.insert( id: id, @@ -4258,6 +4313,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager< downloadMusicCodec: downloadMusicCodec, endlessPlayback: endlessPlayback, playerWakelock: playerWakelock, + overrideCacheProvider: overrideCacheProvider, ), )); } @@ -4408,6 +4464,11 @@ class $$PreferencesTableTableFilterComposer column: $state.table.playerWakelock, builder: (column, joinBuilders) => ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get overrideCacheProvider => $state.composableBuilder( + column: $state.table.overrideCacheProvider, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); } class $$PreferencesTableTableOrderingComposer @@ -4532,6 +4593,11 @@ class $$PreferencesTableTableOrderingComposer column: $state.table.playerWakelock, builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get overrideCacheProvider => $state.composableBuilder( + column: $state.table.overrideCacheProvider, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); } typedef $$ScrobblerTableTableCreateCompanionBuilder = ScrobblerTableCompanion diff --git a/lib/services/database/tables/preferences.dart b/lib/services/database/tables/preferences.dart index 5e9e3be..c135b1a 100755 --- a/lib/services/database/tables/preferences.dart +++ b/lib/services/database/tables/preferences.dart @@ -90,6 +90,8 @@ class PreferencesTable extends Table { boolean().withDefault(const Constant(true))(); BoolColumn get playerWakelock => boolean().withDefault(const Constant(true))(); + BoolColumn get overrideCacheProvider => + boolean().withDefault(const Constant(true))(); // Default values as PreferencesTableData static PreferencesTableData defaults() { @@ -118,6 +120,7 @@ class PreferencesTable extends Table { downloadMusicCodec: SourceCodecs.m4a, endlessPlayback: true, playerWakelock: true, + overrideCacheProvider: true, ); } } diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index 3d66ca2..96781ba 100755 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -1,5 +1,7 @@ import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; import 'package:get/get.dart'; +import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/error_notifier.dart'; import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/services/database/database.dart'; @@ -97,9 +99,43 @@ abstract class SourcedTrack extends Track { static Future fetchFromTrack({ required Track track, + AudioSource? fallbackTo, }) async { final preferences = Get.find().state.value; - final audioSource = preferences.audioSource; + var audioSource = preferences.audioSource; + + if (!preferences.overrideCacheProvider && fallbackTo == null) { + final DatabaseProvider db = Get.find(); + final cachedSource = + await (db.database.select(db.database.sourceMatchTable) + ..where((s) => s.trackId.equals(track.id!)) + ..limit(1) + ..orderBy([ + (s) => OrderingTerm( + expression: s.createdAt, mode: OrderingMode.desc), + ])) + .get() + .then((s) => s.firstOrNull); + + final ytOrPiped = preferences.audioSource == AudioSource.youtube + ? AudioSource.youtube + : AudioSource.piped; + final sourceTypeTrackMap = { + SourceType.youtube: ytOrPiped, + SourceType.youtubeMusic: ytOrPiped, + SourceType.netease: AudioSource.netease, + SourceType.kugou: AudioSource.kugou, + }; + + if (cachedSource != null) { + final cachedAudioSource = sourceTypeTrackMap[cachedSource.sourceType]!; + audioSource = cachedAudioSource; + } + } + + if (fallbackTo != null) { + audioSource = fallbackTo; + } try { return switch (audioSource) { @@ -115,14 +151,20 @@ abstract class SourcedTrack extends Track { Get.find().showError( '${err.toString()} via ${preferences.audioSource.label}, querying in fallback sources...', ); - return switch (preferences.audioSource) { - AudioSource.piped || - AudioSource.youtube => - await NeteaseSourcedTrack.fetchFromTrack(track: track), + + if (fallbackTo != null) { + // Prevent infinite fallback + if (audioSource == AudioSource.youtube || + audioSource == AudioSource.piped) rethrow; + } + + return switch (audioSource) { AudioSource.netease => - await KugouSourcedTrack.fetchFromTrack(track: track), + await fetchFromTrack(track: track, fallbackTo: AudioSource.kugou), AudioSource.kugou => - await YoutubeSourcedTrack.fetchFromTrack(track: track), + await fetchFromTrack(track: track, fallbackTo: AudioSource.youtube), + _ => + await fetchFromTrack(track: track, fallbackTo: AudioSource.netease), }; } on HttpClientClosedException catch (_) { return await PipedSourcedTrack.fetchFromTrack(track: track);