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) { } catch (e, stackTrace) {
// Removing tracks that were not found to avoid queue interruption // Removing tracks that were not found to avoid queue interruption
// TODO: Add a flag to enable/disable skip not found tracks if (e is TrackNotFoundError) {
if (e is TrackNotFoundException) {
final oldTrack = final oldTrack =
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull; mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
await removeTrack(oldTrack!.id!); await removeTrack(oldTrack!.id!);

View File

@ -1,7 +1,12 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
class TrackNotFoundException implements Exception { class TrackNotFoundError extends Error {
factory TrackNotFoundException(Track track) { final Track track;
throw Exception("Failed to find any results for ${track.name}");
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:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.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/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_info.dart';
import 'package:spotube/services/sourced_track/models/source_map.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/jiosaavn.dart';
import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/piped.dart';
import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
abstract class SourcedTrack extends Track { abstract class SourcedTrack extends Track {
final SourceMap source; final SourceMap source;
@ -101,9 +107,8 @@ abstract class SourcedTrack extends Track {
required Track track, required Track track,
required Ref ref, required Ref ref,
}) async { }) async {
final preferences = ref.read(userPreferencesProvider);
try { try {
final preferences = ref.read(userPreferencesProvider);
return switch (preferences.audioSource) { return switch (preferences.audioSource) {
AudioSource.piped => AudioSource.piped =>
await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref), await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
@ -112,8 +117,35 @@ abstract class SourcedTrack extends Track {
AudioSource.jiosaavn => AudioSource.jiosaavn =>
await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref), 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) { } 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({ static Future<SourcedTrack> fetchFromTrack({
required Track track, required Track track,
required Ref ref, required Ref ref,
bool weakMatch = false,
}) async { }) async {
final cachedSource = await SourceMatch.box.get(track.id); final cachedSource = await SourceMatch.box.get(track.id);
if (cachedSource == null || if (cachedSource == null ||
cachedSource.sourceType != SourceType.jiosaavn) { 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) { if (siblings.isEmpty) {
throw TrackNotFoundException(track); throw TrackNotFoundError(track);
} }
await SourceMatch.box.put( await SourceMatch.box.put(
@ -119,6 +121,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
static Future<List<SiblingType>> fetchSiblings({ static Future<List<SiblingType>> fetchSiblings({
required Track track, required Track track,
required Ref ref, required Ref ref,
bool weakMatch = false,
}) async { }) async {
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
@ -126,9 +129,12 @@ class JioSaavnSourcedTrack extends SourcedTrack {
await jiosaavnClient.search.songs(query, limit: 20); await jiosaavnClient.search.songs(query, limit: 20);
final trackArtistNames = track.artists?.map((ar) => ar.name).toList(); final trackArtistNames = track.artists?.map((ar) => ar.name).toList();
return results
final matchedResults = results
.where( .where(
(s) { (s) {
s.name?.unescapeHtml().contains(track.name!) ?? false;
final sameName = s.name?.unescapeHtml() == track.name; final sameName = s.name?.unescapeHtml() == track.name;
final artistNames = [ final artistNames = [
s.primaryArtists, s.primaryArtists,
@ -139,12 +145,27 @@ class JioSaavnSourcedTrack extends SourcedTrack {
(artist) => (artist) =>
trackArtistNames?.any((ar) => artist == ar) ?? false, 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; return sameName && sameArtists;
}, },
) )
.map(toSiblingType) .map(toSiblingType)
.toList(); .toList();
if (weakMatch && matchedResults.isEmpty) {
return results.map(toSiblingType).toList();
}
return matchedResults;
} }
@override @override

View File

@ -55,7 +55,7 @@ class PipedSourcedTrack extends SourcedTrack {
if (cachedSource == null) { if (cachedSource == null) {
final siblings = await fetchSiblings(ref: ref, track: track); final siblings = await fetchSiblings(ref: ref, track: track);
if (siblings.isEmpty) { if (siblings.isEmpty) {
throw TrackNotFoundException(track); throw TrackNotFoundError(track);
} }
await SourceMatch.box.put( await SourceMatch.box.put(
@ -160,13 +160,16 @@ class PipedSourcedTrack extends SourcedTrack {
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
final PipedSearchResult(items: searchResults) = await pipedClient.search( final PipedSearchResult(items: searchResults) = await pipedClient.search(
"$query - Topic", query,
preference.searchMode == SearchMode.youtube preference.searchMode == SearchMode.youtube
? PipedFilter.video ? PipedFilter.video
: PipedFilter.musicSongs, : 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) { if (isYouTubeMusic) {
final artists = (track.artists ?? []) final artists = (track.artists ?? [])

View File

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

View File

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

View File

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

View File

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