mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
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:
parent
cd669e22c1
commit
90f7c531cd
@ -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!);
|
||||||
|
@ -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(", ")}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 ?? [])
|
||||||
|
@ -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(
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user