refactor: drop fallback support to different sources

This commit is contained in:
Kingkor Roy Tirtho 2025-02-11 21:30:58 +06:00
parent 4ea0523692
commit fb0d9620d0
9 changed files with 50 additions and 132 deletions

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:collection/collection.dart';
import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/logger/logger.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -110,7 +109,7 @@ final localTracksProvider =
return null; return null;
} }
}), }),
).then((value) => value.whereNotNull().toList()); ).then((value) => value.nonNulls.toList());
final tracksFromMetadata = filesWithMetadata final tracksFromMetadata = filesWithMetadata
.map( .map(

View File

@ -11,7 +11,6 @@ import 'package:shelf/shelf.dart';
import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/extensions/track.dart'; import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/models/parser/range_headers.dart'; import 'package:spotube/models/parser/range_headers.dart';
import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/audio_player/state.dart';
@ -125,14 +124,9 @@ class ServerPlaybackRoutes {
) )
.catchError((e, stack) async { .catchError((e, stack) async {
AppLogger.reportError(e, stack); AppLogger.reportError(e, stack);
final sourcedTrack = userPreferences.audioSource == AudioSource.youtube && final sourcedTrack = await ref
e is DioException
? await ref
.read(sourcedTrackProvider(SpotubeMedia(track)).notifier) .read(sourcedTrackProvider(SpotubeMedia(track)).notifier)
.refreshStreamingUrl() .refreshStreamingUrl();
: await ref
.read(sourcedTrackProvider(SpotubeMedia(track)).notifier)
.switchToAlternativeSources();
ref.read(activeSourcedTrackProvider.notifier).update(sourcedTrack); ref.read(activeSourcedTrackProvider.notifier).update(sourcedTrack);

View File

@ -41,18 +41,6 @@ class SourcedTrackNotifier
); );
}); });
} }
Future<SourcedTrack?> switchToAlternativeSources() async {
if (arg == null) {
return null;
}
return await update((prev) async {
return await SourcedTrack.fetchFromTrackAltSource(
track: arg!.track,
ref: ref,
);
});
}
} }
final sourcedTrackProvider = AsyncNotifierProviderFamily<SourcedTrackNotifier, final sourcedTrackProvider = AsyncNotifierProviderFamily<SourcedTrackNotifier,

View File

@ -39,7 +39,7 @@ class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
(json) => PlaylistsFeatured.fromJson(json), (json) => PlaylistsFeatured.fromJson(json),
).getPage(limit, offset); ).getPage(limit, offset);
final items = playlists.items?.whereNotNull().toList() ?? []; final items = playlists.items?.nonNulls.toList() ?? [];
return ( return (
items: items, items: items,

View File

@ -1,15 +1,9 @@
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:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.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/invidious.dart'; import 'package:spotube/services/sourced_track/sources/invidious.dart';
@ -17,7 +11,6 @@ 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;
@ -97,11 +90,8 @@ abstract class SourcedTrack extends Track {
} }
static String getSearchTerm(Track track) { static String getSearchTerm(Track track) {
final artists = (track.artists ?? []) final artists =
.map((ar) => ar.name) (track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList();
.toList()
.whereNotNull()
.toList();
final title = ServiceUtils.getTitle( final title = ServiceUtils.getTitle(
track.name!, track.name!,
@ -112,100 +102,21 @@ abstract class SourcedTrack extends Track {
return "$title - ${artists.join(", ")}"; return "$title - ${artists.join(", ")}";
} }
static fetchFromTrackAltSource({
required Track track,
required Ref ref,
}) async {
final preferences = ref.read(userPreferencesProvider);
try {
return switch (preferences.audioSource) {
AudioSource.piped ||
AudioSource.invidious ||
AudioSource.jiosaavn =>
await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref),
AudioSource.youtube =>
await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref),
};
} on TrackNotFoundError catch (_) {
return switch (preferences.audioSource) {
AudioSource.piped ||
AudioSource.youtube ||
AudioSource.invidious =>
await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: true,
),
AudioSource.jiosaavn =>
await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref),
};
} on HttpClientClosedException catch (_) {
return await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref);
} on VideoUnplayableException catch (_) {
return await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref);
} catch (e) {
if (e is DioException || e is ClientException || e is SocketException) {
return await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: preferences.audioSource == AudioSource.jiosaavn,
);
}
rethrow;
}
}
static Future<SourcedTrack> fetchFromTrack({ static Future<SourcedTrack> fetchFromTrack({
required Track track, required Track track,
required Ref ref, required Ref ref,
}) async { }) async {
final preferences = ref.read(userPreferencesProvider); final preferences = ref.read(userPreferencesProvider);
try {
return switch (preferences.audioSource) { return switch (preferences.audioSource) {
AudioSource.piped =>
await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
AudioSource.youtube => AudioSource.youtube =>
await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref),
AudioSource.jiosaavn => AudioSource.piped =>
await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref), await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
AudioSource.invidious => AudioSource.invidious =>
await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref), await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref),
};
} on TrackNotFoundError catch (_) {
return switch (preferences.audioSource) {
AudioSource.piped ||
AudioSource.youtube ||
AudioSource.invidious =>
await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: true,
),
AudioSource.jiosaavn => AudioSource.jiosaavn =>
await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref),
}; };
} on HttpClientClosedException catch (_) {
return await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref);
} on VideoUnplayableException catch (_) {
return await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref);
} catch (e) {
if (e is DioException || e is ClientException || e is SocketException) {
return switch (preferences.audioSource) {
AudioSource.piped ||
AudioSource.invidious =>
await YoutubeSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
),
_ => await JioSaavnSourcedTrack.fetchFromTrack(
track: track,
ref: ref,
weakMatch: preferences.audioSource == AudioSource.jiosaavn,
)
};
}
rethrow;
}
} }
static Future<List<SiblingType>> fetchSiblings({ static Future<List<SiblingType>> fetchSiblings({

View File

@ -50,6 +50,22 @@ class InvidiousSourcedTrack extends SourcedTrack {
required Track track, required Track track,
required Ref ref, required Ref ref,
}) async { }) async {
// Indicates a stream url refresh
if (track is InvidiousSourcedTrack) {
final manifest = await ref
.read(invidiousProvider)
.videos
.get(track.sourceInfo.id, local: true);
return InvidiousSourcedTrack(
ref: ref,
siblings: track.siblings,
source: toSourceMap(manifest),
sourceInfo: track.sourceInfo,
track: track,
);
}
final database = ref.read(databaseProvider); final database = ref.read(databaseProvider);
final cachedSource = await (database.select(database.sourceMatchTable) final cachedSource = await (database.select(database.sourceMatchTable)
..where((s) => s.trackId.equals(track.id!)) ..where((s) => s.trackId.equals(track.id!))

View File

@ -50,6 +50,19 @@ class PipedSourcedTrack extends SourcedTrack {
required Track track, required Track track,
required Ref ref, required Ref ref,
}) async { }) async {
// Means it wants a refresh of the stream
if (track is PipedSourcedTrack) {
final manifest =
await ref.read(pipedProvider).streams(track.sourceInfo.id);
return PipedSourcedTrack(
ref: ref,
siblings: track.siblings,
sourceInfo: track.sourceInfo,
source: toSourceMap(manifest),
track: track,
);
}
final database = ref.read(databaseProvider); final database = ref.read(databaseProvider);
final cachedSource = await (database.select(database.sourceMatchTable) final cachedSource = await (database.select(database.sourceMatchTable)
..where((s) => s.trackId.equals(track.id!)) ..where((s) => s.trackId.equals(track.id!))
@ -183,11 +196,8 @@ class PipedSourcedTrack extends SourcedTrack {
: preference.searchMode == SearchMode.youtubeMusic; : preference.searchMode == SearchMode.youtubeMusic;
if (isYouTubeMusic) { if (isYouTubeMusic) {
final artists = (track.artists ?? []) final artists =
.map((ar) => ar.name) (track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList();
.toList()
.whereNotNull()
.toList();
return await Future.wait( return await Future.wait(
searchResults searchResults

View File

@ -268,7 +268,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
final searchResults = final searchResults =
await ref.read(youtubeEngineProvider).searchVideos("$query - Topic"); await ref.read(youtubeEngineProvider).searchVideos(query);
if (ServiceUtils.onlyContainsEnglish(query)) { if (ServiceUtils.onlyContainsEnglish(query)) {
return await Future.wait(searchResults return await Future.wait(searchResults

View File

@ -96,7 +96,7 @@ class NewPipeEngine implements YouTubeEngine {
Future<List<Video>> searchVideos(String query) async { Future<List<Video>> searchVideos(String query) async {
final results = await NewPipeExtractor.search( final results = await NewPipeExtractor.search(
query, query,
contentFilters: [SearchContentFilters.musicSongs], contentFilters: [SearchContentFilters.videos],
); );
final resultsWithVideos = results final resultsWithVideos = results