mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
feat: add jiosaavn as audio source
This commit is contained in:
parent
84ed9e1c6e
commit
3de153e478
@ -59,12 +59,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setYoutubeApiType(value);
|
||||
preferences.setAudioSource(value);
|
||||
},
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.audioSource == AudioSource.youtube
|
||||
child: preferences.audioSource != AudioSource.piped
|
||||
? const SizedBox.shrink()
|
||||
: Consumer(builder: (context, ref, child) {
|
||||
final instanceList = ref.watch(pipedInstancesFutureProvider);
|
||||
@ -131,7 +131,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.audioSource == AudioSource.youtube
|
||||
child: preferences.audioSource != AudioSource.piped
|
||||
? const SizedBox.shrink()
|
||||
: AdaptiveSelectTile<SearchMode>(
|
||||
secondary: const Icon(SpotubeIcons.search),
|
||||
@ -151,17 +151,18 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.searchMode == SearchMode.youtubeMusic &&
|
||||
preferences.audioSource == AudioSource.piped
|
||||
? const SizedBox.shrink()
|
||||
: SwitchListTile(
|
||||
child: preferences.searchMode == SearchMode.youtube &&
|
||||
(preferences.audioSource == AudioSource.piped ||
|
||||
preferences.audioSource == AudioSource.youtube)
|
||||
? SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.skip),
|
||||
title: Text(context.l10n.skip_non_music),
|
||||
value: preferences.skipNonMusic,
|
||||
onChanged: (state) {
|
||||
preferences.setSkipNonMusic(state);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||
@ -178,44 +179,46 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
value: preferences.normalizeAudio,
|
||||
onChanged: preferences.setNormalizeAudio,
|
||||
),
|
||||
AdaptiveSelectTile<MusicCodec>(
|
||||
secondary: const Icon(SpotubeIcons.stream),
|
||||
title: Text(context.l10n.streaming_music_codec),
|
||||
value: preferences.streamMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: MusicCodec.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setStreamMusicCodec(value);
|
||||
},
|
||||
),
|
||||
AdaptiveSelectTile<MusicCodec>(
|
||||
secondary: const Icon(SpotubeIcons.file),
|
||||
title: Text(context.l10n.download_music_codec),
|
||||
value: preferences.downloadMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: MusicCodec.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setDownloadMusicCodec(value);
|
||||
},
|
||||
),
|
||||
if (preferences.audioSource != AudioSource.jiosaavn)
|
||||
AdaptiveSelectTile<SourceCodecs>(
|
||||
secondary: const Icon(SpotubeIcons.stream),
|
||||
title: Text(context.l10n.streaming_music_codec),
|
||||
value: preferences.streamMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: SourceCodecs.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setStreamMusicCodec(value);
|
||||
},
|
||||
),
|
||||
if (preferences.audioSource != AudioSource.jiosaavn)
|
||||
AdaptiveSelectTile<SourceCodecs>(
|
||||
secondary: const Icon(SpotubeIcons.file),
|
||||
title: Text(context.l10n.download_music_codec),
|
||||
value: preferences.downloadMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: SourceCodecs.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setDownloadMusicCodec(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:path/path.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/download_manager/download_manager.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
@ -42,7 +43,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
//? WebA audiotagging is not supported yet
|
||||
//? Although in future by converting weba to opus & then tagging it
|
||||
//? is possible using vorbis comments
|
||||
downloadCodec == MusicCodec.weba) return;
|
||||
downloadCodec == SourceCodecs.weba) return;
|
||||
|
||||
final file = File(request.path);
|
||||
|
||||
@ -90,7 +91,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
|
||||
String get downloadDirectory =>
|
||||
ref.read(userPreferencesProvider.select((s) => s.downloadLocation));
|
||||
MusicCodec get downloadCodec =>
|
||||
SourceCodecs get downloadCodec =>
|
||||
ref.read(userPreferencesProvider.select((s) => s.downloadMusicCodec));
|
||||
|
||||
int get $downloadCount => dl
|
||||
|
||||
@ -171,10 +171,11 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final isYTMusicMode = preferences.audioSource == AudioSource.piped &&
|
||||
preferences.searchMode == SearchMode.youtubeMusic;
|
||||
final isNotYTMode = preferences.audioSource != AudioSource.youtube ||
|
||||
(preferences.audioSource == AudioSource.piped &&
|
||||
preferences.searchMode == SearchMode.youtubeMusic);
|
||||
|
||||
if (isYTMusicMode || !preferences.skipNonMusic) return;
|
||||
if (isNotYTMode || !preferences.skipNonMusic) return;
|
||||
|
||||
final isNotSameSegmentId =
|
||||
currentSegments.value?.source != audioPlayer.currentSource;
|
||||
|
||||
@ -37,14 +37,6 @@ enum AudioSource {
|
||||
String get label => name[0].toUpperCase() + name.substring(1);
|
||||
}
|
||||
|
||||
enum MusicCodec {
|
||||
m4a._("M4a (Best for downloaded music)"),
|
||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||
|
||||
final String label;
|
||||
const MusicCodec._(this.label);
|
||||
}
|
||||
|
||||
enum SearchMode {
|
||||
youtube,
|
||||
youtubeMusic;
|
||||
@ -91,8 +83,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
String pipedInstance;
|
||||
ThemeMode themeMode;
|
||||
AudioSource audioSource;
|
||||
MusicCodec streamMusicCodec;
|
||||
MusicCodec downloadMusicCodec;
|
||||
SourceCodecs streamMusicCodec;
|
||||
SourceCodecs downloadMusicCodec;
|
||||
|
||||
final Ref ref;
|
||||
|
||||
@ -115,8 +107,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
this.systemTitleBar = false,
|
||||
this.amoledDarkTheme = false,
|
||||
this.normalizeAudio = true,
|
||||
this.streamMusicCodec = MusicCodec.weba,
|
||||
this.downloadMusicCodec = MusicCodec.m4a,
|
||||
this.streamMusicCodec = SourceCodecs.weba,
|
||||
this.downloadMusicCodec = SourceCodecs.m4a,
|
||||
SpotubeColor? accentColorScheme,
|
||||
}) : super() {
|
||||
this.accentColorScheme =
|
||||
@ -144,22 +136,22 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
setPipedInstance("https://pipedapi.kavin.rocks");
|
||||
setSearchMode(SearchMode.youtube);
|
||||
setSkipNonMusic(true);
|
||||
setYoutubeApiType(AudioSource.youtube);
|
||||
setAudioSource(AudioSource.youtube);
|
||||
setSystemTitleBar(false);
|
||||
setAmoledDarkTheme(false);
|
||||
setNormalizeAudio(true);
|
||||
setAccentColorScheme(SpotubeColor(Colors.blue.value, name: "Blue"));
|
||||
setStreamMusicCodec(MusicCodec.weba);
|
||||
setDownloadMusicCodec(MusicCodec.m4a);
|
||||
setStreamMusicCodec(SourceCodecs.weba);
|
||||
setDownloadMusicCodec(SourceCodecs.m4a);
|
||||
}
|
||||
|
||||
void setStreamMusicCodec(MusicCodec codec) {
|
||||
void setStreamMusicCodec(SourceCodecs codec) {
|
||||
streamMusicCodec = codec;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setDownloadMusicCodec(MusicCodec codec) {
|
||||
void setDownloadMusicCodec(SourceCodecs codec) {
|
||||
downloadMusicCodec = codec;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
@ -255,7 +247,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setYoutubeApiType(AudioSource type) {
|
||||
void setAudioSource(AudioSource type) {
|
||||
audioSource = type;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
@ -358,14 +350,14 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
normalizeAudio = map["normalizeAudio"] ?? normalizeAudio;
|
||||
audioPlayer.setAudioNormalization(normalizeAudio);
|
||||
|
||||
streamMusicCodec = MusicCodec.values.firstWhere(
|
||||
streamMusicCodec = SourceCodecs.values.firstWhere(
|
||||
(codec) => codec.name == map["streamMusicCodec"],
|
||||
orElse: () => MusicCodec.weba,
|
||||
orElse: () => SourceCodecs.weba,
|
||||
);
|
||||
|
||||
downloadMusicCodec = MusicCodec.values.firstWhere(
|
||||
downloadMusicCodec = SourceCodecs.values.firstWhere(
|
||||
(codec) => codec.name == map["downloadMusicCodec"],
|
||||
orElse: () => MusicCodec.m4a,
|
||||
orElse: () => SourceCodecs.m4a,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,11 @@ import 'package:spotube/services/sourced_track/models/source_info.dart';
|
||||
import 'package:spotube/services/sourced_track/models/source_map.dart';
|
||||
|
||||
enum SourceCodecs {
|
||||
mp4,
|
||||
weba,
|
||||
m4a,
|
||||
m4a._("M4a (Best for downloaded music)"),
|
||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||
|
||||
final String label;
|
||||
const SourceCodecs._(this.label);
|
||||
}
|
||||
|
||||
enum SourceQualities {
|
||||
|
||||
@ -34,12 +34,10 @@ class SourceQualityMap {
|
||||
|
||||
@JsonSerializable()
|
||||
class SourceMap {
|
||||
final SourceQualityMap? mp4;
|
||||
final SourceQualityMap? weba;
|
||||
final SourceQualityMap? m4a;
|
||||
|
||||
const SourceMap({
|
||||
this.mp4,
|
||||
this.weba,
|
||||
this.m4a,
|
||||
});
|
||||
@ -51,8 +49,6 @@ class SourceMap {
|
||||
|
||||
operator [](SourceCodecs key) {
|
||||
switch (key) {
|
||||
case SourceCodecs.mp4:
|
||||
return mp4;
|
||||
case SourceCodecs.weba:
|
||||
return weba;
|
||||
case SourceCodecs.m4a:
|
||||
|
||||
@ -21,9 +21,6 @@ Map<String, dynamic> _$SourceQualityMapToJson(SourceQualityMap instance) =>
|
||||
};
|
||||
|
||||
SourceMap _$SourceMapFromJson(Map<String, dynamic> json) => SourceMap(
|
||||
mp4: json['mp4'] == null
|
||||
? null
|
||||
: SourceQualityMap.fromJson(json['mp4'] as Map<String, dynamic>),
|
||||
weba: json['weba'] == null
|
||||
? null
|
||||
: SourceQualityMap.fromJson(json['weba'] as Map<String, dynamic>),
|
||||
@ -33,7 +30,6 @@ SourceMap _$SourceMapFromJson(Map<String, dynamic> json) => SourceMap(
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SourceMapToJson(SourceMap instance) => <String, dynamic>{
|
||||
'mp4': instance.mp4,
|
||||
'weba': instance.weba,
|
||||
'm4a': instance.m4a,
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.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';
|
||||
@ -60,14 +61,22 @@ abstract class SourcedTrack extends Track {
|
||||
source: source,
|
||||
siblings: siblings,
|
||||
sourceInfo: sourceInfo,
|
||||
track: track),
|
||||
track: track,
|
||||
),
|
||||
AudioSource.piped => PipedSourcedTrack(
|
||||
ref: ref,
|
||||
source: source,
|
||||
siblings: siblings,
|
||||
sourceInfo: sourceInfo,
|
||||
track: track),
|
||||
AudioSource.jiosaavn => throw UnimplementedError(),
|
||||
track: track,
|
||||
),
|
||||
AudioSource.jiosaavn => JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
source: source,
|
||||
siblings: siblings,
|
||||
sourceInfo: sourceInfo,
|
||||
track: track,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -90,16 +99,22 @@ abstract class SourcedTrack extends Track {
|
||||
static Future<SourcedTrack> fetchFromTrack({
|
||||
required Track track,
|
||||
required Ref ref,
|
||||
}) {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
}) async {
|
||||
try {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
|
||||
return switch (preferences.audioSource) {
|
||||
AudioSource.piped =>
|
||||
PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
|
||||
AudioSource.youtube =>
|
||||
YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref),
|
||||
AudioSource.jiosaavn => throw UnimplementedError(),
|
||||
};
|
||||
return switch (preferences.audioSource) {
|
||||
AudioSource.piped =>
|
||||
await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref),
|
||||
AudioSource.youtube =>
|
||||
await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref),
|
||||
AudioSource.jiosaavn =>
|
||||
await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref),
|
||||
};
|
||||
} catch (e) {
|
||||
print("Got error: $e");
|
||||
return YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<SiblingType>> fetchSiblings({
|
||||
@ -113,7 +128,8 @@ abstract class SourcedTrack extends Track {
|
||||
PipedSourcedTrack.fetchSiblings(track: track, ref: ref),
|
||||
AudioSource.youtube =>
|
||||
YoutubeSourcedTrack.fetchSiblings(track: track, ref: ref),
|
||||
AudioSource.jiosaavn => throw UnimplementedError(),
|
||||
AudioSource.jiosaavn =>
|
||||
JioSaavnSourcedTrack.fetchSiblings(track: track, ref: ref),
|
||||
};
|
||||
}
|
||||
|
||||
@ -129,28 +145,26 @@ abstract class SourcedTrack extends Track {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
|
||||
final codec = preferences.audioSource == AudioSource.jiosaavn
|
||||
? SourceCodecs.mp4
|
||||
: switch (preferences.streamMusicCodec) {
|
||||
MusicCodec.m4a => SourceCodecs.m4a,
|
||||
MusicCodec.weba => SourceCodecs.weba,
|
||||
};
|
||||
? SourceCodecs.m4a
|
||||
: preferences.streamMusicCodec;
|
||||
|
||||
return source[codec]![preferences.audioQuality]!;
|
||||
return getUrlOfCodec(codec);
|
||||
}
|
||||
|
||||
String getUrlOfCodec(MusicCodec codec) {
|
||||
String getUrlOfCodec(SourceCodecs codec) {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
|
||||
return source[codec == MusicCodec.m4a
|
||||
? SourceCodecs.m4a
|
||||
: SourceCodecs.weba]![preferences.audioQuality]!;
|
||||
return source[codec]?[preferences.audioQuality] ??
|
||||
// this will ensure playback doesn't break
|
||||
source[codec == SourceCodecs.m4a ? SourceCodecs.weba : SourceCodecs.m4a]
|
||||
[preferences.audioQuality];
|
||||
}
|
||||
|
||||
MusicCodec get codec {
|
||||
SourceCodecs get codec {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
|
||||
return preferences.audioSource == AudioSource.jiosaavn
|
||||
? MusicCodec.m4a
|
||||
? SourceCodecs.m4a
|
||||
: preferences.streamMusicCodec;
|
||||
}
|
||||
}
|
||||
|
||||
159
lib/services/sourced_track/sources/jiosaavn.dart
Normal file
159
lib/services/sourced_track/sources/jiosaavn.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/source_match.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/sourced_track.dart';
|
||||
import 'package:jiosaavn/jiosaavn.dart';
|
||||
|
||||
final jiosaavnClient = JioSaavnClient();
|
||||
|
||||
class JioSaavnSourcedTrack extends SourcedTrack {
|
||||
JioSaavnSourcedTrack({
|
||||
required super.ref,
|
||||
required super.source,
|
||||
required super.siblings,
|
||||
required super.sourceInfo,
|
||||
required super.track,
|
||||
});
|
||||
|
||||
static Future<SourcedTrack> fetchFromTrack({
|
||||
required Track track,
|
||||
required Ref ref,
|
||||
}) async {
|
||||
final cachedSource = await SourceMatch.box.get(track.id);
|
||||
|
||||
if (cachedSource == null ||
|
||||
cachedSource.sourceType != SourceType.jiosaavn) {
|
||||
final siblings = await fetchSiblings(ref: ref, track: track);
|
||||
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundException(track);
|
||||
}
|
||||
|
||||
await SourceMatch.box.put(
|
||||
track.id!,
|
||||
SourceMatch(
|
||||
id: track.id!,
|
||||
sourceType: SourceType.jiosaavn,
|
||||
createdAt: DateTime.now(),
|
||||
sourceId: siblings.first.info.id,
|
||||
),
|
||||
);
|
||||
|
||||
return JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
source: siblings.first.source!,
|
||||
sourceInfo: siblings.first.info,
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
final [item] =
|
||||
await jiosaavnClient.songs.detailsById([cachedSource.sourceId]);
|
||||
|
||||
final (:info, :source) = toSiblingType(item);
|
||||
|
||||
return JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: [],
|
||||
source: source!,
|
||||
sourceInfo: info,
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
static SiblingType toSiblingType(SongResponse result) {
|
||||
final SiblingType sibling = (
|
||||
info: SourceInfo(
|
||||
artist: [
|
||||
result.primaryArtists,
|
||||
if (result.featuredArtists.isNotEmpty) ", ",
|
||||
result.featuredArtists
|
||||
].join("").replaceAll("&", "&"),
|
||||
artistUrl:
|
||||
"https://www.jiosaavn.com/artist/${result.primaryArtistsId.split(",").firstOrNull ?? ""}",
|
||||
duration: Duration(seconds: int.parse(result.duration)),
|
||||
id: result.id,
|
||||
pageUrl: result.url,
|
||||
thumbnail: result.image?.last.link ?? "",
|
||||
title: result.name!,
|
||||
album: result.album.name,
|
||||
),
|
||||
source: SourceMap(
|
||||
m4a: SourceQualityMap(
|
||||
high: result.downloadUrl!
|
||||
.firstWhere((element) => element.quality == "320kbps")
|
||||
.link,
|
||||
medium: result.downloadUrl!
|
||||
.firstWhere((element) => element.quality == "160kbps")
|
||||
.link,
|
||||
low: result.downloadUrl!
|
||||
.firstWhere((element) => element.quality == "96kbps")
|
||||
.link,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return sibling;
|
||||
}
|
||||
|
||||
static Future<List<SiblingType>> fetchSiblings({
|
||||
required Track track,
|
||||
required Ref ref,
|
||||
}) async {
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
final SongSearchResponse(:results) =
|
||||
await jiosaavnClient.search.songs(query, limit: 20);
|
||||
|
||||
return results.map(toSiblingType).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<JioSaavnSourcedTrack> copyWithSibling() async {
|
||||
if (siblings.isNotEmpty) {
|
||||
return this;
|
||||
}
|
||||
final fetchedSiblings = await fetchSiblings(ref: ref, track: this);
|
||||
|
||||
return JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: fetchedSiblings
|
||||
.where((s) => s.info.id != sourceInfo.id)
|
||||
.map((s) => s.info)
|
||||
.toList(),
|
||||
source: source,
|
||||
sourceInfo: sourceInfo,
|
||||
track: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<JioSaavnSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
|
||||
if (sibling.id == sourceInfo.id ||
|
||||
siblings.none((s) => s.id == sibling.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id);
|
||||
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
|
||||
..insert(0, sourceInfo);
|
||||
|
||||
final [item] = await jiosaavnClient.songs.detailsById([newSourceInfo.id]);
|
||||
|
||||
final (:info, :source) = toSiblingType(item);
|
||||
|
||||
return JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: newSiblings,
|
||||
source: source!,
|
||||
sourceInfo: info,
|
||||
track: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -55,28 +55,27 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
sourceInfo: siblings.first.info,
|
||||
track: track,
|
||||
);
|
||||
} else {
|
||||
final item = await youtubeClient.videos.get(cachedSource.sourceId);
|
||||
final manifest = await youtubeClient.videos.streamsClient.getManifest(
|
||||
cachedSource.sourceId,
|
||||
);
|
||||
return YoutubeSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: [],
|
||||
source: toSourceMap(manifest),
|
||||
sourceInfo: SourceInfo(
|
||||
id: item.id.value,
|
||||
artist: item.author,
|
||||
artistUrl: "https://www.youtube.com/channel/${item.channelId}",
|
||||
pageUrl: item.url,
|
||||
thumbnail: item.thumbnails.highResUrl,
|
||||
title: item.title,
|
||||
duration: item.duration ?? Duration.zero,
|
||||
album: null,
|
||||
),
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
final item = await youtubeClient.videos.get(cachedSource.sourceId);
|
||||
final manifest = await youtubeClient.videos.streamsClient.getManifest(
|
||||
cachedSource.sourceId,
|
||||
);
|
||||
return YoutubeSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: [],
|
||||
source: toSourceMap(manifest),
|
||||
sourceInfo: SourceInfo(
|
||||
id: item.id.value,
|
||||
artist: item.author,
|
||||
artistUrl: "https://www.youtube.com/channel/${item.channelId}",
|
||||
pageUrl: item.url,
|
||||
thumbnail: item.thumbnails.highResUrl,
|
||||
title: item.title,
|
||||
duration: item.duration ?? Duration.zero,
|
||||
album: null,
|
||||
),
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
static SourceMap toSourceMap(StreamManifest manifest) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user