mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
212 lines
6.7 KiB
Dart
212 lines
6.7 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:spotube/models/database/database.dart';
|
|
import 'package:spotube/models/playback/track_sources.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/sources/invidious.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';
|
|
|
|
abstract class SourcedTrack extends BasicSourcedTrack {
|
|
final Ref ref;
|
|
|
|
SourcedTrack({
|
|
required this.ref,
|
|
required super.info,
|
|
required super.query,
|
|
required super.source,
|
|
required super.siblings,
|
|
required super.sources,
|
|
});
|
|
|
|
static SourcedTrack fromJson(
|
|
Map<String, dynamic> json, {
|
|
required Ref ref,
|
|
}) {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
|
|
final info = TrackSourceInfo.fromJson(json["info"]);
|
|
final query = TrackSourceQuery.fromJson(json["query"]);
|
|
final source = AudioSource.values.firstWhereOrNull(
|
|
(source) => source.name == json["source"],
|
|
) ??
|
|
preferences.audioSource;
|
|
final siblings = (json["siblings"] as List)
|
|
.map((s) => TrackSourceInfo.fromJson(s))
|
|
.toList();
|
|
final sources =
|
|
(json["sources"] as List).map((s) => TrackSource.fromJson(s)).toList();
|
|
|
|
return switch (preferences.audioSource) {
|
|
AudioSource.youtube => YoutubeSourcedTrack(
|
|
ref: ref,
|
|
source: source,
|
|
siblings: siblings,
|
|
info: info,
|
|
query: query,
|
|
sources: sources,
|
|
),
|
|
AudioSource.piped => PipedSourcedTrack(
|
|
ref: ref,
|
|
source: source,
|
|
siblings: siblings,
|
|
info: info,
|
|
query: query,
|
|
sources: sources,
|
|
),
|
|
AudioSource.jiosaavn => JioSaavnSourcedTrack(
|
|
ref: ref,
|
|
source: source,
|
|
siblings: siblings,
|
|
info: info,
|
|
query: query,
|
|
sources: sources,
|
|
),
|
|
AudioSource.invidious => InvidiousSourcedTrack(
|
|
ref: ref,
|
|
source: source,
|
|
siblings: siblings,
|
|
info: info,
|
|
query: query,
|
|
sources: sources,
|
|
),
|
|
};
|
|
}
|
|
|
|
static String getSearchTerm(TrackSourceQuery track) {
|
|
final title = ServiceUtils.getTitle(
|
|
track.title,
|
|
artists: track.artists,
|
|
onlyCleanArtist: true,
|
|
).trim();
|
|
|
|
return "$title - ${track.artists.join(", ")}";
|
|
}
|
|
|
|
static Future<SourcedTrack> fetchFromQuery({
|
|
required TrackSourceQuery query,
|
|
required Ref ref,
|
|
}) async {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
try {
|
|
return switch (preferences.audioSource) {
|
|
AudioSource.youtube =>
|
|
await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref),
|
|
AudioSource.piped =>
|
|
await PipedSourcedTrack.fetchFromTrack(query: query, ref: ref),
|
|
AudioSource.invidious =>
|
|
await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref),
|
|
AudioSource.jiosaavn =>
|
|
await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref),
|
|
};
|
|
} catch (e) {
|
|
if (preferences.audioSource == AudioSource.youtube) {
|
|
rethrow;
|
|
}
|
|
|
|
return await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref);
|
|
}
|
|
}
|
|
|
|
static Future<List<SiblingType>> fetchSiblings({
|
|
required TrackSourceQuery query,
|
|
required Ref ref,
|
|
}) {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
|
|
return switch (preferences.audioSource) {
|
|
AudioSource.piped =>
|
|
PipedSourcedTrack.fetchSiblings(query: query, ref: ref),
|
|
AudioSource.youtube =>
|
|
YoutubeSourcedTrack.fetchSiblings(query: query, ref: ref),
|
|
AudioSource.jiosaavn =>
|
|
JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref),
|
|
AudioSource.invidious =>
|
|
InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref),
|
|
};
|
|
}
|
|
|
|
Future<SourcedTrack> copyWithSibling();
|
|
|
|
Future<SourcedTrack?> swapWithSibling(TrackSourceInfo sibling);
|
|
|
|
Future<SourcedTrack?> swapWithSiblingOfIndex(int index) {
|
|
return swapWithSibling(siblings[index]);
|
|
}
|
|
|
|
Future<SourcedTrack> refreshStream();
|
|
String get url {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
|
|
final codec = preferences.audioSource == AudioSource.jiosaavn
|
|
? SourceCodecs.m4a
|
|
: preferences.streamMusicCodec;
|
|
|
|
return getUrlOfCodec(codec);
|
|
}
|
|
|
|
/// Returns the URL of the track based on the codec and quality preferences.
|
|
/// If an exact match is not found, it will return the closest match based on
|
|
/// the user's audio quality preference.
|
|
///
|
|
/// If no sources match the codec, it will return the first or last source
|
|
/// based on the user's audio quality preference.
|
|
String getUrlOfCodec(SourceCodecs codec) {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
|
|
final exactMatch = sources.firstWhereOrNull(
|
|
(source) =>
|
|
source.codec == codec && source.quality == preferences.audioQuality,
|
|
);
|
|
|
|
if (exactMatch != null) {
|
|
return exactMatch.url;
|
|
}
|
|
|
|
final sameCodecSources = sources
|
|
.where((source) => source.codec == codec)
|
|
.toList()
|
|
.sorted((a, b) {
|
|
final aDiff = (a.quality.index - preferences.audioQuality.index).abs();
|
|
final bDiff = (b.quality.index - preferences.audioQuality.index).abs();
|
|
return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index;
|
|
}).toList();
|
|
|
|
if (sameCodecSources.isNotEmpty) {
|
|
return preferences.audioQuality != SourceQualities.low
|
|
? sameCodecSources.first.url
|
|
: sameCodecSources.last.url;
|
|
}
|
|
|
|
final fallbackSource = sources.sorted((a, b) {
|
|
final aDiff = (a.quality.index - preferences.audioQuality.index).abs();
|
|
final bDiff = (b.quality.index - preferences.audioQuality.index).abs();
|
|
return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index;
|
|
});
|
|
|
|
return preferences.audioQuality != SourceQualities.low
|
|
? fallbackSource.first.url
|
|
: fallbackSource.last.url;
|
|
}
|
|
|
|
SourceCodecs get codec {
|
|
final preferences = ref.read(userPreferencesProvider);
|
|
|
|
return preferences.audioSource == AudioSource.jiosaavn
|
|
? SourceCodecs.m4a
|
|
: preferences.streamMusicCodec;
|
|
}
|
|
|
|
TrackSource get activeTrackSource {
|
|
final audioQuality = ref.read(userPreferencesProvider).audioQuality;
|
|
return sources.firstWhereOrNull(
|
|
(source) => source.codec == codec && source.quality == audioQuality,
|
|
) ??
|
|
sources.first;
|
|
}
|
|
}
|