fix: non-banger songs breaking the queue if sources not found

Now fallbacks to another audio source if not found in one
This commit is contained in:
Kingkor Roy Tirtho 2024-02-17 21:33:09 +06:00
parent cd669e22c1
commit 90f7c531cd
9 changed files with 79 additions and 18 deletions

View File

@ -144,8 +144,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
}
} catch (e, stackTrace) {
// Removing tracks that were not found to avoid queue interruption
// TODO: Add a flag to enable/disable skip not found tracks
if (e is TrackNotFoundException) {
if (e is TrackNotFoundError) {
final oldTrack =
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
await removeTrack(oldTrack!.id!);

View File

@ -1,7 +1,12 @@
import 'package:spotify/spotify.dart';
class TrackNotFoundException implements Exception {
factory TrackNotFoundException(Track track) {
throw Exception("Failed to find any results for ${track.name}");
class TrackNotFoundError extends Error {
final Track track;
TrackNotFoundError(this.track);
@override
String toString() {
return '[TrackNotFoundError] ${track.name} - ${track.artists?.map((e) => e.name).join(", ")}';
}
}

View File

@ -1,15 +1,21 @@
import 'dart:io';
import 'package:http/http.dart';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/services/sourced_track/enums.dart';
import 'package:spotube/services/sourced_track/exceptions.dart';
import 'package:spotube/services/sourced_track/models/source_info.dart';
import 'package:spotube/services/sourced_track/models/source_map.dart';
import 'package:spotube/services/sourced_track/sources/jiosaavn.dart';
import 'package:spotube/services/sourced_track/sources/piped.dart';
import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
abstract class SourcedTrack extends Track {
final SourceMap source;
@ -101,9 +107,8 @@ abstract class SourcedTrack extends Track {
required Track track,
required Ref ref,
}) async {
final preferences = ref.read(userPreferencesProvider);
try {
final preferences = ref.read(userPreferencesProvider);
return switch (preferences.audioSource) {
AudioSource.piped =>
await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
@ -112,8 +117,35 @@ abstract class SourcedTrack extends Track {
AudioSource.jiosaavn =>
await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref),
};
} on TrackNotFoundError catch (_) {
return switch (preferences.audioSource) {
AudioSource.piped ||
AudioSource.youtube =>
await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: true,
),
AudioSource.jiosaavn =>
await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
};
} on HttpClientClosedException catch (_) {
return await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref);
} catch (e) {
return YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref);
if (e is DioException || e is ClientException || e is SocketException) {
if (preferences.audioSource == AudioSource.jiosaavn) {
return await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: true,
);
}
return await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
);
}
rethrow;
}
}

View File

@ -37,15 +37,17 @@ class JioSaavnSourcedTrack extends SourcedTrack {
static Future<SourcedTrack> fetchFromTrack({
required Track track,
required Ref ref,
bool weakMatch = false,
}) async {
final cachedSource = await SourceMatch.box.get(track.id);
if (cachedSource == null ||
cachedSource.sourceType != SourceType.jiosaavn) {
final siblings = await fetchSiblings(ref: ref, track: track);
final siblings =
await fetchSiblings(ref: ref, track: track, weakMatch: weakMatch);
if (siblings.isEmpty) {
throw TrackNotFoundException(track);
throw TrackNotFoundError(track);
}
await SourceMatch.box.put(
@ -119,6 +121,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
static Future<List<SiblingType>> fetchSiblings({
required Track track,
required Ref ref,
bool weakMatch = false,
}) async {
final query = SourcedTrack.getSearchTerm(track);
@ -126,9 +129,12 @@ class JioSaavnSourcedTrack extends SourcedTrack {
await jiosaavnClient.search.songs(query, limit: 20);
final trackArtistNames = track.artists?.map((ar) => ar.name).toList();
return results
final matchedResults = results
.where(
(s) {
s.name?.unescapeHtml().contains(track.name!) ?? false;
final sameName = s.name?.unescapeHtml() == track.name;
final artistNames = [
s.primaryArtists,
@ -139,12 +145,27 @@ class JioSaavnSourcedTrack extends SourcedTrack {
(artist) =>
trackArtistNames?.any((ar) => artist == ar) ?? false,
);
if (weakMatch) {
final containsName =
s.name?.unescapeHtml().contains(track.name!) ?? false;
final containsPrimaryArtist = s.primaryArtists
.unescapeHtml()
.contains(trackArtistNames?.first ?? "");
return containsName && containsPrimaryArtist;
}
return sameName && sameArtists;
},
)
.map(toSiblingType)
.toList();
if (weakMatch && matchedResults.isEmpty) {
return results.map(toSiblingType).toList();
}
return matchedResults;
}
@override

View File

@ -55,7 +55,7 @@ class PipedSourcedTrack extends SourcedTrack {
if (cachedSource == null) {
final siblings = await fetchSiblings(ref: ref, track: track);
if (siblings.isEmpty) {
throw TrackNotFoundException(track);
throw TrackNotFoundError(track);
}
await SourceMatch.box.put(
@ -160,13 +160,16 @@ class PipedSourcedTrack extends SourcedTrack {
final query = SourcedTrack.getSearchTerm(track);
final PipedSearchResult(items: searchResults) = await pipedClient.search(
"$query - Topic",
query,
preference.searchMode == SearchMode.youtube
? PipedFilter.video
: PipedFilter.musicSongs,
);
final isYouTubeMusic = preference.searchMode == SearchMode.youtubeMusic;
// when falling back to piped API make sure to use the YouTube mode
final isYouTubeMusic = preference.audioSource != AudioSource.piped
? false
: preference.searchMode == SearchMode.youtubeMusic;
if (isYouTubeMusic) {
final artists = (track.artists ?? [])

View File

@ -48,7 +48,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) {
final siblings = await fetchSiblings(ref: ref, track: track);
if (siblings.isEmpty) {
throw TrackNotFoundException(track);
throw TrackNotFoundError(track);
}
await SourceMatch.box.put(

View File

@ -1619,10 +1619,10 @@ packages:
dependency: "direct main"
description:
name: piped_client
sha256: "8b96e1f9d8533c1da7eff7fbbd4bf188256fc76a20900d378b52be09418ea771"
sha256: "87b04b2ebf4e008cfbb0ac85e9920ab3741f5aa697be2dd44919658a3297a4bc"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
version: "0.1.1"
platform:
dependency: transitive
description:

View File

@ -76,7 +76,7 @@ dependencies:
path: ^1.8.0
path_provider: ^2.0.8
permission_handler: ^11.0.1
piped_client: ^0.1.0
piped_client: ^0.1.1
popover: ^0.2.6+3
scrobblenaut:
git:

View File

@ -77,6 +77,7 @@
],
"nl": [
"audio_source",
"start_a_radio",
"how_to_start_radio",
"replace_queue_question",