Skip to content

Commit 9c4b1ce

Browse files
committed
feat(queue): implement unique queue entry IDs for better management
1 parent 05acde7 commit 9c4b1ce

2 files changed

Lines changed: 82 additions & 41 deletions

File tree

lib/services/audio_service.dart

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class MusifyAudioHandler extends BaseAudioHandler {
7878
final List<Map> _historyList = [];
7979
final BehaviorSubject<List<Map>> _queueMapStream =
8080
BehaviorSubject<List<Map>>.seeded([]);
81+
int _queueEntryCounter = 0;
8182
int _currentQueueIndex = 0;
8283
int _currentLoadingIndex = -1;
8384
int _currentLoadingTransitionId = -1;
@@ -237,8 +238,40 @@ class MusifyAudioHandler extends BaseAudioHandler {
237238
});
238239
}
239240

240-
MediaItem _getMediaItemForQueue(Map song, int index) {
241-
return mapToMediaItem(song).copyWith(id: '${song['ytid']}_$index');
241+
String _generateQueueEntryId() {
242+
return 'queue-${DateTime.now().microsecondsSinceEpoch}-${_queueEntryCounter++}';
243+
}
244+
245+
String _ensureQueueEntryId(Map song) {
246+
final existingId = song['queueEntryId']?.toString();
247+
if (existingId != null && existingId.isNotEmpty) {
248+
return existingId;
249+
}
250+
251+
final generatedId = _generateQueueEntryId();
252+
song['queueEntryId'] = generatedId;
253+
return generatedId;
254+
}
255+
256+
Map<String, dynamic> _createQueueSong(Map song) {
257+
final queueSong = Map<String, dynamic>.from(song);
258+
queueSong['queueEntryId'] = _generateQueueEntryId();
259+
return queueSong;
260+
}
261+
262+
void _ensureQueueEntryIds(Iterable<Map> songs) {
263+
for (final song in songs) {
264+
_ensureQueueEntryId(song);
265+
}
266+
}
267+
268+
void _hydrateQueueEntryIds() {
269+
_ensureQueueEntryIds(_queueList);
270+
_ensureQueueEntryIds(_originalQueueList);
271+
}
272+
273+
MediaItem _getMediaItemForQueue(Map song) {
274+
return mapToMediaItem(song).copyWith(id: _ensureQueueEntryId(song));
242275
}
243276

244277
void _updateCurrentMediaItemWithDuration(Duration duration) {
@@ -256,10 +289,7 @@ class MusifyAudioHandler extends BaseAudioHandler {
256289
}
257290

258291
final currentSong = _queueList[capturedQueueIndex];
259-
final currentMediaItem = _getMediaItemForQueue(
260-
currentSong,
261-
capturedQueueIndex,
262-
);
292+
final currentMediaItem = _getMediaItemForQueue(currentSong);
263293
final uniqueId = currentMediaItem.id;
264294
final currentItem = mediaItem.valueOrNull;
265295

@@ -270,16 +300,11 @@ class MusifyAudioHandler extends BaseAudioHandler {
270300
mediaItem.add(currentMediaItem.copyWith(duration: duration));
271301
}
272302

273-
List<MediaItem> newQueue;
274-
if (queue.hasValue && queue.value.length == _queueList.length) {
275-
newQueue = List<MediaItem>.from(queue.value);
276-
} else {
277-
newQueue = _queueList
278-
.asMap()
279-
.entries
280-
.map((entry) => _getMediaItemForQueue(entry.value, entry.key))
281-
.toList();
282-
}
303+
final newQueue = _queueList
304+
.asMap()
305+
.entries
306+
.map((entry) => _getMediaItemForQueue(entry.value))
307+
.toList();
283308

284309
if (capturedQueueIndex < newQueue.length) {
285310
newQueue[capturedQueueIndex] = newQueue[capturedQueueIndex].copyWith(
@@ -742,7 +767,7 @@ class MusifyAudioHandler extends BaseAudioHandler {
742767
insertIndex = _queueList.length;
743768
}
744769

745-
_queueList.insert(insertIndex, song);
770+
_queueList.insert(insertIndex, _createQueueSong(song));
746771

747772
if (_currentQueueIndex < 0) {
748773
_currentQueueIndex = 0;
@@ -821,14 +846,15 @@ class MusifyAudioHandler extends BaseAudioHandler {
821846
for (var i = 0; i < songs.length; i++) {
822847
final song = songs[i];
823848
if (song['ytid'] != null && song['ytid'].toString().isNotEmpty) {
824-
_queueList.add(song);
849+
_queueList.add(_createQueueSong(song));
825850

826851
if (replace && startIndex == i) {
827852
targetQueueIndex = _queueList.length - 1;
828853
}
829854
}
830855
}
831856

857+
_hydrateQueueEntryIds();
832858
_updateQueueMediaItems();
833859

834860
if (targetQueueIndex != null) {
@@ -854,11 +880,12 @@ class MusifyAudioHandler extends BaseAudioHandler {
854880
if (index < 0 || index >= _queueList.length) return;
855881

856882
final removedSong = _queueList[index];
883+
final removedQueueEntryId = _ensureQueueEntryId(removedSong);
857884
_queueList.removeAt(index);
858885

859886
if (shuffleNotifier.value && _originalQueueList.isNotEmpty) {
860887
_originalQueueList.removeWhere(
861-
(s) => s['ytid'] != null && s['ytid'] == removedSong['ytid'],
888+
(s) => _ensureQueueEntryId(s) == removedQueueEntryId,
862889
);
863890
}
864891

@@ -877,6 +904,7 @@ class MusifyAudioHandler extends BaseAudioHandler {
877904
await _playFromQueue(_currentQueueIndex);
878905
}
879906

907+
_hydrateQueueEntryIds();
880908
_updateQueueMediaItems();
881909
} catch (e, stackTrace) {
882910
logger.log('Error removing from queue', error: e, stackTrace: stackTrace);
@@ -885,6 +913,8 @@ class MusifyAudioHandler extends BaseAudioHandler {
885913

886914
Future<void> reorderQueue(int oldIndex, int newIndex) async {
887915
try {
916+
_ensureQueueEntryIds(_queueList);
917+
888918
if (oldIndex < 0 ||
889919
oldIndex >= _queueList.length ||
890920
newIndex < 0 ||
@@ -927,10 +957,12 @@ class MusifyAudioHandler extends BaseAudioHandler {
927957

928958
void _updateQueueMediaItems() {
929959
try {
960+
_ensureQueueEntryIds(_queueList);
961+
930962
final mediaItems = _queueList
931963
.asMap()
932964
.entries
933-
.map((entry) => _getMediaItemForQueue(entry.value, entry.key))
965+
.map((entry) => _getMediaItemForQueue(entry.value))
934966
.toList();
935967
queue.add(mediaItems);
936968

@@ -1036,10 +1068,7 @@ class MusifyAudioHandler extends BaseAudioHandler {
10361068
_currentQueueIndex = index;
10371069

10381070
final currentSong = _queueList[_currentQueueIndex];
1039-
final currentMediaItem = _getMediaItemForQueue(
1040-
currentSong,
1041-
_currentQueueIndex,
1042-
);
1071+
final currentMediaItem = _getMediaItemForQueue(currentSong);
10431072
final uniqueId = currentMediaItem.id;
10441073

10451074
await Future.microtask(() {
@@ -1660,41 +1689,43 @@ class MusifyAudioHandler extends BaseAudioHandler {
16601689
if (_queueList.isEmpty) return;
16611690

16621691
if (shuffleEnabled && !wasShuffled) {
1692+
_hydrateQueueEntryIds();
1693+
16631694
_originalQueueList
16641695
..clear()
16651696
..addAll(_queueList);
16661697

16671698
final currentSong = _queueList[_currentQueueIndex];
1668-
final currentYtId = currentSong['ytid'];
1699+
final currentQueueEntryId = _ensureQueueEntryId(currentSong);
16691700

16701701
_queueList.shuffle();
16711702

1672-
if (currentYtId != null) {
1673-
final newCurrentIndex = _queueList.indexWhere(
1674-
(song) => song['ytid'] == currentYtId,
1675-
);
1703+
final newCurrentIndex = _queueList.indexWhere(
1704+
(song) => _ensureQueueEntryId(song) == currentQueueEntryId,
1705+
);
16761706

1677-
if (newCurrentIndex != -1 && newCurrentIndex != 0) {
1678-
_queueList
1679-
..removeAt(newCurrentIndex)
1680-
..insert(0, currentSong);
1681-
}
1707+
if (newCurrentIndex != -1 && newCurrentIndex != 0) {
1708+
_queueList
1709+
..removeAt(newCurrentIndex)
1710+
..insert(0, currentSong);
16821711
}
16831712

16841713
_currentQueueIndex = 0;
16851714
_updateQueueMediaItems();
16861715
} else if (!shuffleEnabled && wasShuffled) {
16871716
if (_originalQueueList.isNotEmpty) {
1717+
_hydrateQueueEntryIds();
1718+
16881719
final currentSong = _queueList[_currentQueueIndex];
1689-
final currentYtId = currentSong['ytid'];
1720+
final currentQueueEntryId = _ensureQueueEntryId(currentSong);
16901721

16911722
_queueList
16921723
..clear()
16931724
..addAll(_originalQueueList);
16941725

1695-
_currentQueueIndex = currentYtId != null
1696-
? _queueList.indexWhere((song) => song['ytid'] == currentYtId)
1697-
: 0;
1726+
_currentQueueIndex = _queueList.indexWhere(
1727+
(song) => _ensureQueueEntryId(song) == currentQueueEntryId,
1728+
);
16981729

16991730
if (_currentQueueIndex == -1) {
17001731
_currentQueueIndex = 0;

lib/widgets/queue_list_view.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class _QueueWidgetState extends State<QueueWidget> {
246246
);
247247
}
248248

249+
String _queueEntryKey(Map song, int index) {
250+
return song['queueEntryId']?.toString() ?? 'legacy_${song['ytid']}_$index';
251+
}
252+
249253
Widget _buildList(
250254
BuildContext context,
251255
ColorScheme colorScheme,
@@ -273,10 +277,12 @@ class _QueueWidgetState extends State<QueueWidget> {
273277
itemBuilder: (context, index) {
274278
final song = _queue[index];
275279
final isCurrentSong = index == currentIndex;
280+
final queueEntryId = _queueEntryKey(song, index);
276281
return QueueTile(
277-
key: ValueKey('queue_song_${song['ytid']}'),
282+
key: ValueKey(queueEntryId),
278283
song: song,
279284
index: index,
285+
queueEntryId: queueEntryId,
280286
isCurrentSong: isCurrentSong,
281287
colorScheme: colorScheme,
282288
onTap: () {
@@ -288,7 +294,9 @@ class _QueueWidgetState extends State<QueueWidget> {
288294
return true;
289295
},
290296
onDismissed: () {
291-
final actualIndex = _queue.indexOf(song);
297+
final actualIndex = _queue.indexWhere(
298+
(item) => item['queueEntryId']?.toString() == queueEntryId,
299+
);
292300
if (actualIndex == -1) return;
293301
setState(() {
294302
_isDismissing = false;
@@ -307,6 +315,7 @@ class QueueTile extends StatelessWidget {
307315
super.key,
308316
required this.song,
309317
required this.index,
318+
required this.queueEntryId,
310319
required this.isCurrentSong,
311320
required this.colorScheme,
312321
required this.onTap,
@@ -316,6 +325,7 @@ class QueueTile extends StatelessWidget {
316325

317326
final Map song;
318327
final int index;
328+
final String queueEntryId;
319329
final bool isCurrentSong;
320330
final ColorScheme colorScheme;
321331
final VoidCallback onTap;
@@ -328,7 +338,7 @@ class QueueTile extends StatelessWidget {
328338
@override
329339
Widget build(BuildContext context) {
330340
return Dismissible(
331-
key: ValueKey('queue_dismiss_${song['ytid']}'),
341+
key: ValueKey(queueEntryId),
332342
confirmDismiss: confirmDismiss,
333343
onDismissed: (_) => onDismissed(),
334344
background: _DismissBackground(

0 commit comments

Comments
 (0)