@@ -7,6 +7,7 @@ import 'package:jplayer/main.dart';
77import 'package:jplayer/src/data/dto/dto.dart' ;
88import 'package:jplayer/src/data/params/params.dart' ;
99import 'package:jplayer/src/data/providers/providers.dart' ;
10+ import 'package:jplayer/src/data/storages/playback_storage.dart' ;
1011import 'package:jplayer/src/domain/models/models.dart' ;
1112import 'package:jplayer/src/domain/providers/current_user_provider.dart' ;
1213import 'package:jplayer/src/domain/providers/download_manager_provider.dart' ;
@@ -52,17 +53,30 @@ class PlaybackNotifier extends StateNotifier<PlaybackState> {
5253 mediaSourceId: state.album? .id,
5354 ),
5455 );
56+ final album = state.album;
57+ if (album != null ) {
58+ unawaited (_saveToStorage (songId: nextSong.id, positionMs: 0 ));
59+ }
5560 }
5661 }
5762 }
5863 })
59- // Listen for song completion
6064 ..positionStream.listen ((position) {
6165 state = state.copyWith (
6266 position: position,
6367 totalDuration: _audioPlayer.duration,
6468 currentMediaIndex: _audioPlayer.currentIndex,
6569 );
70+
71+ final now = DateTime .now ();
72+ if (state.status.isPlaying && (_lastPositionSave == null || now.difference (_lastPositionSave! ).inSeconds >= 5 )) {
73+ _lastPositionSave = now;
74+ final currentIndex = _audioPlayer.currentIndex;
75+ final currentSong = currentIndex != null ? state.songs.elementAtOrNull (currentIndex) : null ;
76+ if (currentSong != null ) {
77+ unawaited (_saveToStorage (songId: currentSong.id, positionMs: position.inMilliseconds));
78+ }
79+ }
6680 })
6781 // Handle other player states as needed
6882 ..playerStateStream.listen ((playerState) {
@@ -78,12 +92,15 @@ class PlaybackNotifier extends StateNotifier<PlaybackState> {
7892 final Ref _ref;
7993 late AudioPlayer _audioPlayer;
8094 var _playSessionId = '' ;
95+ DateTime ? _lastPositionSave;
8196
8297 Future <void > play (
8398 ItemDTO playSong,
8499 List <ItemDTO > songs,
85- ItemDTO album,
86- ) async {
100+ ItemDTO album, {
101+ Duration ? initialPosition,
102+ bool autoPlay = true ,
103+ }) async {
87104 try {
88105 final audioSources = await Future .wait (
89106 songs.map ((song) async {
@@ -145,19 +162,26 @@ class PlaybackNotifier extends StateNotifier<PlaybackState> {
145162 );
146163
147164 _playSessionId = DateTime .now ().toIso8601String (); // Any unique ID
165+ final startIndex = songs.indexWhere ((s) => s.id == playSong.id);
166+ final startPosition = initialPosition ?? Duration .zero;
148167 await _audioPlayer.setAudioSources (
149168 audioSources,
150- initialIndex: songs.indexOf (playSong),
151- preload: false ,
169+ initialIndex: startIndex >= 0 ? startIndex : 0 ,
170+ initialPosition: startPosition,
171+ preload: true ,
152172 );
173+
153174 state = state.copyWith (
154175 songs: songs,
155176 album: album,
156- status: PlaybackStatus .playing,
157- position: Duration .zero ,
177+ status: autoPlay ? PlaybackStatus .playing : PlaybackStatus .paused ,
178+ position: startPosition ,
158179 totalDuration: _audioPlayer.duration,
180+ currentMediaIndex: startIndex >= 0 ? startIndex : 0 ,
159181 );
160- unawaited (_audioPlayer.play ());
182+
183+ unawaited (_saveToStorage (songId: playSong.id, positionMs: startPosition.inMilliseconds, songs: songs, album: album));
184+ if (autoPlay) unawaited (_audioPlayer.play ());
161185 } catch (e) {
162186 if (e.toString ().indexOf ('setPitch' ) > 0 ) {
163187 // This is hack to avoid playback state being error on ios*`
@@ -176,6 +200,11 @@ class PlaybackNotifier extends StateNotifier<PlaybackState> {
176200 Future <void > seek (Duration position) async {
177201 await _audioPlayer.seek (position);
178202 state = state.copyWith (position: position);
203+ final currentIndex = _audioPlayer.currentIndex;
204+ final currentSong = currentIndex != null ? state.songs.elementAtOrNull (currentIndex) : null ;
205+ if (currentSong != null ) {
206+ unawaited (_saveToStorage (songId: currentSong.id, positionMs: position.inMilliseconds));
207+ }
179208 }
180209
181210 Future <void > playPause () => switch (state.status.isPlaying) {
@@ -272,6 +301,46 @@ class PlaybackNotifier extends StateNotifier<PlaybackState> {
272301 }
273302 }
274303
304+ Future <void > _saveToStorage ({
305+ required String songId,
306+ required int positionMs,
307+ List <ItemDTO >? songs,
308+ ItemDTO ? album,
309+ }) async {
310+ final effectiveSongs = songs ?? state.songs;
311+ final effectiveAlbum = album ?? state.album;
312+ if (effectiveSongs.isEmpty || effectiveAlbum == null ) {
313+ return ;
314+ }
315+ await PlaybackStorage ().save (
316+ songs: effectiveSongs,
317+ album: effectiveAlbum,
318+ songId: songId,
319+ positionMs: positionMs,
320+ );
321+ }
322+
323+ Future <bool > tryRestore () async {
324+ final snapshot = await PlaybackStorage ().load ();
325+ if (snapshot == null ) {
326+ return false ;
327+ }
328+
329+ final song = snapshot.songs.firstWhere (
330+ (s) => s.id == snapshot.songId,
331+ orElse: () => snapshot.songs.first,
332+ );
333+
334+ await play (
335+ song,
336+ snapshot.songs,
337+ snapshot.album,
338+ initialPosition: Duration (milliseconds: snapshot.positionMs),
339+ autoPlay: false ,
340+ );
341+ return true ;
342+ }
343+
275344 @override
276345 void dispose () {
277346 _audioPlayer.dispose ();
0 commit comments