♻️ Moved player state from shared_prefs to drift
🍱 Update icons
This commit is contained in:
@ -1,19 +1,16 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:get/get.dart' hide Value;
|
||||
import 'package:media_kit/media_kit.dart' hide Track;
|
||||
import 'package:rhythm_box/providers/database.dart';
|
||||
import 'package:rhythm_box/services/audio_player/state.dart';
|
||||
import 'package:rhythm_box/services/local_track.dart';
|
||||
import 'package:rhythm_box/services/server/sourced_track.dart';
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:spotify/spotify.dart' hide Playlist;
|
||||
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class AudioPlayerProvider extends GetxController {
|
||||
late final SharedPreferences _prefs;
|
||||
|
||||
RxBool isPlaying = false.obs;
|
||||
|
||||
Rx<AudioPlayerState> state = Rx(AudioPlayerState(
|
||||
@ -28,41 +25,39 @@ class AudioPlayerProvider extends GetxController {
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
SharedPreferences.getInstance().then((ins) async {
|
||||
_prefs = ins;
|
||||
final res = await _readSavedState();
|
||||
if (res != null) {
|
||||
state.value = res;
|
||||
} else {
|
||||
state.value = AudioPlayerState(
|
||||
loopMode: audioPlayer.loopMode,
|
||||
playing: audioPlayer.isPlaying,
|
||||
playlist: audioPlayer.playlist,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
collections: [],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
_subscriptions = [
|
||||
audioPlayer.playingStream.listen((playing) async {
|
||||
state.value = state.value.copyWith(playing: playing);
|
||||
await _updateSavedState();
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
playing: Value(playing),
|
||||
),
|
||||
);
|
||||
}),
|
||||
audioPlayer.loopModeStream.listen((loopMode) async {
|
||||
state.value = state.value.copyWith(loopMode: loopMode);
|
||||
await _updateSavedState();
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
loopMode: Value(loopMode),
|
||||
),
|
||||
);
|
||||
}),
|
||||
audioPlayer.shuffledStream.listen((shuffled) async {
|
||||
state.value = state.value.copyWith(shuffled: shuffled);
|
||||
await _updateSavedState();
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
shuffled: Value(shuffled),
|
||||
),
|
||||
);
|
||||
}),
|
||||
audioPlayer.playlistStream.listen((playlist) async {
|
||||
state.value = state.value.copyWith(playlist: playlist);
|
||||
await _updateSavedState();
|
||||
await _updatePlaylist(playlist);
|
||||
}),
|
||||
];
|
||||
|
||||
_readSavedState();
|
||||
|
||||
audioPlayer.playingStream.listen((playing) {
|
||||
isPlaying.value = playing;
|
||||
});
|
||||
@ -80,16 +75,124 @@ class AudioPlayerProvider extends GetxController {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<AudioPlayerState?> _readSavedState() async {
|
||||
final data = _prefs.getString('player_state');
|
||||
if (data == null) return null;
|
||||
Future<void> _readSavedState() async {
|
||||
final database = Get.find<DatabaseProvider>().database;
|
||||
|
||||
return AudioPlayerState.fromJson(jsonDecode(data));
|
||||
var playerState =
|
||||
await database.select(database.audioPlayerStateTable).getSingleOrNull();
|
||||
|
||||
if (playerState == null) {
|
||||
await database.into(database.audioPlayerStateTable).insert(
|
||||
AudioPlayerStateTableCompanion.insert(
|
||||
playing: audioPlayer.isPlaying,
|
||||
loopMode: audioPlayer.loopMode,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
collections: <String>[],
|
||||
id: const Value(0),
|
||||
),
|
||||
);
|
||||
|
||||
playerState =
|
||||
await database.select(database.audioPlayerStateTable).getSingle();
|
||||
} else {
|
||||
await audioPlayer.setLoopMode(playerState.loopMode);
|
||||
await audioPlayer.setShuffle(playerState.shuffled);
|
||||
}
|
||||
|
||||
var playlist =
|
||||
await database.select(database.playlistTable).getSingleOrNull();
|
||||
var medias = await database.select(database.playlistMediaTable).get();
|
||||
|
||||
if (playlist == null) {
|
||||
await database.into(database.playlistTable).insert(
|
||||
PlaylistTableCompanion.insert(
|
||||
audioPlayerStateId: 0,
|
||||
index: audioPlayer.playlist.index,
|
||||
id: const Value(0),
|
||||
),
|
||||
);
|
||||
|
||||
playlist = await database.select(database.playlistTable).getSingle();
|
||||
}
|
||||
|
||||
if (medias.isEmpty && audioPlayer.playlist.medias.isNotEmpty) {
|
||||
await database.batch((batch) {
|
||||
batch.insertAll(
|
||||
database.playlistMediaTable,
|
||||
[
|
||||
for (final media in audioPlayer.playlist.medias)
|
||||
PlaylistMediaTableCompanion.insert(
|
||||
playlistId: playlist!.id,
|
||||
uri: media.uri,
|
||||
extras: Value(media.extras),
|
||||
httpHeaders: Value(media.httpHeaders),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
} else if (medias.isNotEmpty) {
|
||||
await audioPlayer.openPlaylist(
|
||||
medias
|
||||
.map(
|
||||
(media) => RhythmMedia.fromMedia(
|
||||
Media(
|
||||
media.uri,
|
||||
extras: media.extras,
|
||||
httpHeaders: media.httpHeaders,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
initialIndex: playlist.index,
|
||||
autoPlay: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (playerState.collections.isNotEmpty) {
|
||||
state.value = state.value.copyWith(
|
||||
collections: playerState.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateSavedState() async {
|
||||
final out = jsonEncode(state.value.toJson());
|
||||
await _prefs.setString('player_state', out);
|
||||
Future<void> _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion companion,
|
||||
) async {
|
||||
final database = Get.find<DatabaseProvider>().database;
|
||||
|
||||
await (database.update(database.audioPlayerStateTable)
|
||||
..where((tb) => tb.id.equals(0)))
|
||||
.write(companion);
|
||||
}
|
||||
|
||||
Future<void> _updatePlaylist(
|
||||
Playlist playlist,
|
||||
) async {
|
||||
final database = Get.find<DatabaseProvider>().database;
|
||||
|
||||
await database.batch((batch) {
|
||||
batch.update(
|
||||
database.playlistTable,
|
||||
PlaylistTableCompanion(index: Value(playlist.index)),
|
||||
where: (tb) => tb.id.equals(0),
|
||||
);
|
||||
|
||||
batch.deleteAll(database.playlistMediaTable);
|
||||
|
||||
if (playlist.medias.isEmpty) return;
|
||||
batch.insertAll(
|
||||
database.playlistMediaTable,
|
||||
[
|
||||
for (final media in playlist.medias)
|
||||
PlaylistMediaTableCompanion.insert(
|
||||
playlistId: 0,
|
||||
uri: media.uri,
|
||||
extras: Value(media.extras),
|
||||
httpHeaders: Value(media.httpHeaders),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addCollections(List<String> collectionIds) async {
|
||||
@ -98,7 +201,11 @@ class AudioPlayerProvider extends GetxController {
|
||||
...collectionIds,
|
||||
]);
|
||||
|
||||
await _updateSavedState();
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
collections: Value(state.value.collections),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addCollection(String collectionId) async {
|
||||
@ -112,7 +219,11 @@ class AudioPlayerProvider extends GetxController {
|
||||
.toList(),
|
||||
);
|
||||
|
||||
await _updateSavedState();
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
collections: Value(state.value.collections),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeCollection(String collectionId) async {
|
||||
|
@ -21,9 +21,16 @@ class AudioPlayerStreamProvider extends GetxController {
|
||||
List<StreamSubscription>? _subscriptions;
|
||||
|
||||
AudioPlayerStreamProvider() {
|
||||
AudioServices.create().then(
|
||||
(value) => notificationService = value,
|
||||
);
|
||||
_initNotificationService();
|
||||
}
|
||||
|
||||
Future<void> _initNotificationService() async {
|
||||
try {
|
||||
final res = await AudioServices.create();
|
||||
notificationService = res;
|
||||
} catch (err) {
|
||||
log('[AudioService] Unable to init audio service: $err');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -50,7 +57,7 @@ class AudioPlayerStreamProvider extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> updatePalette() async {
|
||||
if (!Get.find<UserPreferences>().albumColorSync) {
|
||||
if (!Get.find<UserPreferencesProvider>().state.value.albumColorSync) {
|
||||
if (palette.value != null) {
|
||||
palette.value = null;
|
||||
}
|
||||
|
@ -155,7 +155,10 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
||||
),
|
||||
child: Slider(
|
||||
value: _draggingValue ??
|
||||
_durationCurrent.inMilliseconds.toDouble(),
|
||||
(_durationCurrent.inMilliseconds <=
|
||||
_durationTotal.inMilliseconds
|
||||
? _durationCurrent.inMilliseconds.toDouble()
|
||||
: 0),
|
||||
min: 0,
|
||||
max: _durationTotal.inMilliseconds.toDouble(),
|
||||
onChanged: (value) {
|
||||
|
@ -27,7 +27,7 @@ class AudioServices with WidgetsBindingObserver {
|
||||
: 'dev.solsynth.rhythmBox',
|
||||
androidNotificationChannelName: 'RhythmBox',
|
||||
androidNotificationOngoing: false,
|
||||
androidNotificationIcon: 'drawable/ic_launcher_monochrome',
|
||||
androidNotificationIcon: 'drawable/ic_stat_name',
|
||||
androidStopForegroundOnPause: false,
|
||||
androidNotificationChannelDescription: 'RhythmBox Music',
|
||||
),
|
||||
|
@ -5,6 +5,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:media_kit/media_kit.dart' hide Track;
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rhythm_box/services/color.dart';
|
||||
@ -27,6 +28,7 @@ part 'tables/skip_segment.dart';
|
||||
part 'tables/source_match.dart';
|
||||
part 'tables/history.dart';
|
||||
part 'tables/lyrics.dart';
|
||||
part 'tables/audio_player_state.dart';
|
||||
|
||||
part 'typeconverters/color.dart';
|
||||
part 'typeconverters/locale.dart';
|
||||
@ -44,6 +46,9 @@ part 'typeconverters/subtitle.dart';
|
||||
SourceMatchTable,
|
||||
HistoryTable,
|
||||
LyricsTable,
|
||||
AudioPlayerStateTable,
|
||||
PlaylistTable,
|
||||
PlaylistMediaTable,
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
|
File diff suppressed because it is too large
Load Diff
27
lib/services/database/tables/audio_player_state.dart
Executable file
27
lib/services/database/tables/audio_player_state.dart
Executable file
@ -0,0 +1,27 @@
|
||||
part of '../database.dart';
|
||||
|
||||
class AudioPlayerStateTable extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
BoolColumn get playing => boolean()();
|
||||
TextColumn get loopMode => textEnum<PlaylistMode>()();
|
||||
BoolColumn get shuffled => boolean()();
|
||||
TextColumn get collections => text().map(const StringListConverter())();
|
||||
}
|
||||
|
||||
class PlaylistTable extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
IntColumn get audioPlayerStateId =>
|
||||
integer().references(AudioPlayerStateTable, #id)();
|
||||
IntColumn get index => integer()();
|
||||
}
|
||||
|
||||
class PlaylistMediaTable extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
IntColumn get playlistId => integer().references(PlaylistTable, #id)();
|
||||
|
||||
TextColumn get uri => text()();
|
||||
TextColumn get extras =>
|
||||
text().nullable().map(const MapTypeConverter<String, dynamic>())();
|
||||
TextColumn get httpHeaders =>
|
||||
text().nullable().map(const MapTypeConverter<String, String>())();
|
||||
}
|
@ -13,24 +13,18 @@ abstract class KVStoreService {
|
||||
_sharedPreferences = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
static bool get doneGettingStarted =>
|
||||
sharedPreferences.getBool('doneGettingStarted') ?? false;
|
||||
static Future<void> setDoneGettingStarted(bool value) async =>
|
||||
await sharedPreferences.setBool('doneGettingStarted', value);
|
||||
|
||||
static bool get askedForBatteryOptimization =>
|
||||
sharedPreferences.getBool('askedForBatteryOptimization') ?? false;
|
||||
sharedPreferences.getBool('asked_for_battery_optimization') ?? false;
|
||||
static Future<void> setAskedForBatteryOptimization(bool value) async =>
|
||||
await sharedPreferences.setBool('askedForBatteryOptimization', value);
|
||||
await sharedPreferences.setBool('asked_for_battery_optimization', value);
|
||||
|
||||
static List<String> get recentSearches =>
|
||||
sharedPreferences.getStringList('recentSearches') ?? [];
|
||||
|
||||
sharedPreferences.getStringList('recent_searches') ?? [];
|
||||
static Future<void> setRecentSearches(List<String> value) async =>
|
||||
await sharedPreferences.setStringList('recentSearches', value);
|
||||
await sharedPreferences.setStringList('recent_searches', value);
|
||||
|
||||
static WindowSize? get windowSize {
|
||||
final raw = sharedPreferences.getString('windowSize');
|
||||
final raw = sharedPreferences.getString('window_size');
|
||||
|
||||
if (raw == null) {
|
||||
return null;
|
||||
@ -40,7 +34,7 @@ abstract class KVStoreService {
|
||||
|
||||
static Future<void> setWindowSize(WindowSize value) async =>
|
||||
await sharedPreferences.setString(
|
||||
'windowSize',
|
||||
'window_size',
|
||||
jsonEncode(
|
||||
value.toJson(),
|
||||
),
|
||||
@ -82,9 +76,4 @@ abstract class KVStoreService {
|
||||
static double get volume => sharedPreferences.getDouble('volume') ?? 1.0;
|
||||
static Future<void> setVolume(double value) async =>
|
||||
await sharedPreferences.setDouble('volume', value);
|
||||
|
||||
static bool get hasMigratedToDrift =>
|
||||
sharedPreferences.getBool('hasMigratedToDrift') ?? false;
|
||||
static Future<void> setHasMigratedToDrift(bool value) async =>
|
||||
await sharedPreferences.setBool('hasMigratedToDrift', value);
|
||||
}
|
||||
|
@ -70,7 +70,11 @@ class _PlaylistTrackListState extends State<PlaylistTrackList> {
|
||||
),
|
||||
onTap: () {
|
||||
if (item == null) return;
|
||||
Get.find<AudioPlayerProvider>().load([item], autoPlay: true);
|
||||
Get.find<AudioPlayerProvider>().load(
|
||||
_tracks!,
|
||||
initialIndex: idx,
|
||||
autoPlay: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
Reference in New Issue
Block a user