fix: handle dublicated items in playback queue correctly #1852

This commit is contained in:
Kingkor Roy Tirtho 2024-09-15 13:59:09 +06:00
parent 36d161c05a
commit 9cb828bb55
6 changed files with 44 additions and 14 deletions

View File

@ -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();

19
lib/extensions/list.dart Normal file
View File

@ -0,0 +1,19 @@
extension UniqueItemExtension<T> on List<T> {
List<T> unique(bool Function(T a, T b) equals) {
final copy = <T>[];
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;
}
}

View File

@ -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),

View File

@ -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<AudioPlayerState> {
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<AudioPlayerState> {
Future<void> 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<AudioPlayerState> {
}
}
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<void> load(
List<Track> tracks, {
int initialIndex = 0,
bool autoPlay = false,
}) async {
final medias =
(_blacklist.filter(tracks).toList() as List<Track>).asMediaList();
final medias = (_blacklist.filter(tracks).toList() as List<Track>)
.asMediaList()
.unique((a, b) => _compareTracks(a.track, b.track));
// Giving the initial track a boost so MediaKit won't skip
// because of timeout

View File

@ -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:

View File

@ -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