From 9cb828bb55e42b2084396395c541d6c3e70199dc Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 15 Sep 2024 13:59:09 +0600 Subject: [PATCH] fix: handle dublicated items in playback queue correctly #1852 --- .../sections/body/track_view_body.dart | 3 ++- lib/extensions/list.dart | 19 ++++++++++++++++++ lib/pages/track/track.dart | 7 +++++-- lib/provider/audio_player/audio_player.dart | 20 +++++++++++++++++-- pubspec.lock | 8 -------- pubspec.yaml | 1 - 6 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 lib/extensions/list.dart diff --git a/lib/components/tracks_view/sections/body/track_view_body.dart b/lib/components/tracks_view/sections/body/track_view_body.dart index faba247a7..0f161b0c0 100644 --- a/lib/components/tracks_view/sections/body/track_view_body.dart +++ b/lib/components/tracks_view/sections/body/track_view_body.dart @@ -15,6 +15,7 @@ import 'package:spotube/components/tracks_view/sections/body/track_view_body_hea import 'package:spotube/components/tracks_view/sections/body/use_is_user_playlist.dart'; import 'package:spotube/components/tracks_view/track_view_props.dart'; import 'package:spotube/components/tracks_view/track_view_provider.dart'; +import 'package:spotube/extensions/list.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; @@ -96,7 +97,7 @@ class TrackViewBodySection extends HookConsumerWidget { ); } } else { - if (isActive || playlist.tracks.contains(track)) { + if (isActive || playlist.tracks.containsBy(track, (a) => a.id)) { await playlistNotifier.jumpToTrack(track); } else { final tracks = await props.pagination.onFetchAll(); diff --git a/lib/extensions/list.dart b/lib/extensions/list.dart new file mode 100644 index 000000000..ddd36e4d6 --- /dev/null +++ b/lib/extensions/list.dart @@ -0,0 +1,19 @@ +extension UniqueItemExtension on List { + List unique(bool Function(T a, T b) equals) { + final copy = []; + + for (final item in this) { + if (copy.any((element) => equals(element, item))) continue; + copy.add(item); + } + + return copy; + } + + bool containsBy(T item, dynamic Function(T a) fn) { + for (final el in this) { + if (fn(el) == fn(item)) return true; + } + return false; + } +} diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 6f3af0e4b..84c53b747 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -14,6 +14,7 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/extensions/list.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -167,7 +168,8 @@ class TrackPage extends HookConsumerWidget { children: [ const Gap(5), if (!isActive && - !playlist.tracks.contains(track)) + !playlist.tracks + .containsBy(track, (t) => t.id)) OutlinedButton.icon( icon: const Icon(SpotubeIcons.queueAdd), label: Text(context.l10n.queue), @@ -177,7 +179,8 @@ class TrackPage extends HookConsumerWidget { ), const Gap(5), if (!isActive && - !playlist.tracks.contains(track)) + !playlist.tracks + .containsBy(track, (t) => t.id)) IconButton.outlined( icon: const Icon(SpotubeIcons.lightning), diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 50e90dcd6..7c1b68979 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -4,6 +4,7 @@ import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotify/spotify.dart' hide Playlist; +import 'package:spotube/extensions/list.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; @@ -256,6 +257,10 @@ class AudioPlayerNotifier extends Notifier { for (int i = 0; i < tracks.length; i++) { final track = tracks.elementAt(i); + if (state.tracks.any((element) => _compareTracks(element, track))) { + continue; + } + await audioPlayer.addTrackAt( SpotubeMedia(track), max(state.playlist.index, 0) + i + 1, @@ -265,6 +270,7 @@ class AudioPlayerNotifier extends Notifier { Future addTrack(Track track) async { if (_blacklist.contains(track)) return; + if (state.tracks.any((element) => _compareTracks(element, track))) return; await audioPlayer.addTrack(SpotubeMedia(track)); } @@ -289,13 +295,23 @@ class AudioPlayerNotifier extends Notifier { } } + bool _compareTracks(Track a, Track b) { + if ((a is LocalTrack && b is! LocalTrack) || + (a is! LocalTrack && b is LocalTrack)) return false; + + return a is LocalTrack && b is LocalTrack + ? (a).path == (b).path + : a.id == b.id; + } + Future load( List tracks, { int initialIndex = 0, bool autoPlay = false, }) async { - final medias = - (_blacklist.filter(tracks).toList() as List).asMediaList(); + final medias = (_blacklist.filter(tracks).toList() as List) + .asMediaList() + .unique((a, b) => _compareTracks(a.track, b.track)); // Giving the initial track a boost so MediaKit won't skip // because of timeout diff --git a/pubspec.lock b/pubspec.lock index a1494a2d9..3249c759a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -758,14 +758,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.5" - flutter_hooks_lint: - dependency: "direct dev" - description: - name: flutter_hooks_lint - sha256: fc6e18505b597737e5d620656e340ac60e7a58980cca29e18c1216bd15083674 - url: "https://pub.dev" - source: hosted - version: "1.2.0" flutter_inappwebview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 402cd474b..d69ab5dbd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -158,7 +158,6 @@ dev_dependencies: xml: ^6.5.0 io: ^1.0.4 drift_dev: ^2.18.0 - flutter_hooks_lint: ^1.2.0 dependency_overrides: uuid: ^4.4.0