From c7817909ce0920fac6d70706bc596e6fa3039a09 Mon Sep 17 00:00:00 2001
From: Amin <23167933+aminsaedi@users.noreply.github.com>
Date: Sun, 29 Oct 2023 05:56:48 -0400
Subject: [PATCH 01/33] Updated README.md file (#847)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 71589794..d82af783 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ This handy table lists all methods you can use to install Spotube:
-
Then run: sudo apt install Spotube-linux-x86_64.deb
+ Then run: sudo apt install ./Spotube-linux-x86_64.deb
From 14069cd4fe08597c8d9aa0810270fb4c386c1d55 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 15 Nov 2023 18:34:46 +0600
Subject: [PATCH 02/33] feat: Add JioSaavn as audio source (#881)
* feat: implement new SourcedTrack for youtube and piped
* refactor: replace old spotube track with sourced track
* feat: add jiosaavn as audio source
* fix: download not working other than jiosaavn
* Merge branch 'dev' into feat-jiosaavn
---
.../library/user_downloads/download_item.dart | 36 +--
.../player/sibling_tracks_sheet.dart | 57 ++--
.../shared/dialogs/track_details_dialog.dart | 31 +-
.../shared/track_table/track_options.dart | 2 +-
.../shared/track_table/tracks_table_view.dart | 4 +-
lib/main.dart | 14 +-
lib/models/current_playlist.dart | 9 +-
lib/models/matched_track.dart | 69 -----
lib/models/matched_track.g.dart | 86 ------
lib/models/source_match.dart | 54 ++++
lib/models/source_match.g.dart | 119 ++++++++
lib/models/spotube_track.dart | 274 ------------------
lib/pages/album/album.dart | 4 +-
lib/pages/playlist/playlist.dart | 4 +-
lib/pages/settings/sections/playback.dart | 113 ++++----
lib/provider/download_manager_provider.dart | 73 ++---
lib/provider/piped_instances_provider.dart | 7 +-
.../proxy_playlist/next_fetcher_mixin.dart | 53 +---
.../proxy_playlist/proxy_playlist.dart | 18 +-
.../proxy_playlist_provider.dart | 91 +++---
.../user_preferences_provider.dart | 12 +-
.../user_preferences_state.dart | 63 ++--
.../user_preferences_state.g.dart | 54 ++--
lib/provider/youtube_provider.dart | 8 -
lib/services/audio_player/audio_player.dart | 2 +-
.../audio_player/audio_player_impl.dart | 8 +-
.../audio_services/audio_services.dart | 6 +-
.../audio_services/linux_audio_service.dart | 7 +-
lib/services/queries/lyrics.dart | 4 +-
lib/services/sourced_track/enums.dart | 18 ++
lib/services/sourced_track/exceptions.dart | 7 +
.../sourced_track/models/source_info.dart | 33 +++
.../sourced_track/models/source_info.g.dart | 30 ++
.../sourced_track/models/source_map.dart | 58 ++++
.../sourced_track/models/source_map.g.dart | 35 +++
.../sourced_track/models/video_info.dart | 114 ++++++++
lib/services/sourced_track/sourced_track.dart | 171 +++++++++++
.../sourced_track/sources/jiosaavn.dart | 159 ++++++++++
lib/services/sourced_track/sources/piped.dart | 257 ++++++++++++++++
.../sourced_track/sources/youtube.dart | 256 ++++++++++++++++
lib/services/supabase.dart | 6 +-
lib/services/youtube/youtube.dart | 248 ----------------
lib/utils/service_utils.dart | 6 +-
pubspec.lock | 17 ++
pubspec.yaml | 3 +
45 files changed, 1691 insertions(+), 1009 deletions(-)
delete mode 100644 lib/models/matched_track.dart
delete mode 100644 lib/models/matched_track.g.dart
create mode 100644 lib/models/source_match.dart
create mode 100644 lib/models/source_match.g.dart
delete mode 100644 lib/models/spotube_track.dart
delete mode 100644 lib/provider/youtube_provider.dart
create mode 100644 lib/services/sourced_track/enums.dart
create mode 100644 lib/services/sourced_track/exceptions.dart
create mode 100644 lib/services/sourced_track/models/source_info.dart
create mode 100644 lib/services/sourced_track/models/source_info.g.dart
create mode 100644 lib/services/sourced_track/models/source_map.dart
create mode 100644 lib/services/sourced_track/models/source_map.g.dart
create mode 100644 lib/services/sourced_track/models/video_info.dart
create mode 100644 lib/services/sourced_track/sourced_track.dart
create mode 100644 lib/services/sourced_track/sources/jiosaavn.dart
create mode 100644 lib/services/sourced_track/sources/piped.dart
create mode 100644 lib/services/sourced_track/sources/youtube.dart
delete mode 100644 lib/services/youtube/youtube.dart
diff --git a/lib/components/library/user_downloads/download_item.dart b/lib/components/library/user_downloads/download_item.dart
index ae8a2513..10dec410 100644
--- a/lib/components/library/user_downloads/download_item.dart
+++ b/lib/components/library/user_downloads/download_item.dart
@@ -5,9 +5,9 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/context.dart';
-import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/services/download_manager/download_status.dart';
+import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
class DownloadItem extends HookConsumerWidget {
@@ -24,25 +24,25 @@ class DownloadItem extends HookConsumerWidget {
final taskStatus = useState(null);
useEffect(() {
- if (track is! SpotubeTrack) return null;
- final notifier = downloadManager.getStatusNotifier(track as SpotubeTrack);
+ if (track is! SourcedTrack) return null;
+ final notifier = downloadManager.getStatusNotifier(track as SourcedTrack);
taskStatus.value = notifier?.value;
- listener() {
+
+ void listener() {
taskStatus.value = notifier?.value;
}
- downloadManager
- .getStatusNotifier(track as SpotubeTrack)
- ?.addListener(listener);
+ notifier?.addListener(listener);
return () {
- downloadManager
- .getStatusNotifier(track as SpotubeTrack)
- ?.removeListener(listener);
+ notifier?.removeListener(listener);
};
}, [track]);
+ final isQueryingSourceInfo =
+ taskStatus.value == null || track is! SourcedTrack;
+
return ListTile(
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
@@ -63,7 +63,7 @@ class DownloadItem extends HookConsumerWidget {
track.artists ?? [],
mainAxisAlignment: WrapAlignment.start,
),
- trailing: taskStatus.value == null || track is! SpotubeTrack
+ trailing: isQueryingSourceInfo
? Text(
context.l10n.querying_info,
style: Theme.of(context).textTheme.labelMedium,
@@ -72,7 +72,7 @@ class DownloadItem extends HookConsumerWidget {
DownloadStatus.downloading => HookBuilder(builder: (context) {
final taskProgress = useListenable(useMemoized(
() => downloadManager
- .getProgressNotifier(track as SpotubeTrack),
+ .getProgressNotifier(track as SourcedTrack),
[track],
));
return SizedBox(
@@ -86,13 +86,13 @@ class DownloadItem extends HookConsumerWidget {
IconButton(
icon: const Icon(SpotubeIcons.pause),
onPressed: () {
- downloadManager.pause(track as SpotubeTrack);
+ downloadManager.pause(track as SourcedTrack);
}),
const SizedBox(width: 10),
IconButton(
icon: const Icon(SpotubeIcons.close),
onPressed: () {
- downloadManager.cancel(track as SpotubeTrack);
+ downloadManager.cancel(track as SourcedTrack);
}),
],
),
@@ -104,13 +104,13 @@ class DownloadItem extends HookConsumerWidget {
IconButton(
icon: const Icon(SpotubeIcons.play),
onPressed: () {
- downloadManager.resume(track as SpotubeTrack);
+ downloadManager.resume(track as SourcedTrack);
}),
const SizedBox(width: 10),
IconButton(
icon: const Icon(SpotubeIcons.close),
onPressed: () {
- downloadManager.cancel(track as SpotubeTrack);
+ downloadManager.cancel(track as SourcedTrack);
})
],
),
@@ -126,7 +126,7 @@ class DownloadItem extends HookConsumerWidget {
IconButton(
icon: const Icon(SpotubeIcons.refresh),
onPressed: () {
- downloadManager.retry(track as SpotubeTrack);
+ downloadManager.retry(track as SourcedTrack);
},
),
],
@@ -137,7 +137,7 @@ class DownloadItem extends HookConsumerWidget {
DownloadStatus.queued => IconButton(
icon: const Icon(SpotubeIcons.close),
onPressed: () {
- downloadManager.removeFromQueue(track as SpotubeTrack);
+ downloadManager.removeFromQueue(track as SourcedTrack);
}),
},
);
diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart
index ee8d9719..cf1429b9 100644
--- a/lib/components/player/sibling_tracks_sheet.dart
+++ b/lib/components/player/sibling_tracks_sheet.dart
@@ -1,5 +1,6 @@
import 'dart:ui';
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -12,13 +13,13 @@ import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/hooks/utils/use_debounce.dart';
-import 'package:spotube/models/matched_track.dart';
-import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_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/youtube_provider.dart';
-import 'package:spotube/services/youtube/youtube.dart';
+import 'package:spotube/services/sourced_track/models/source_info.dart';
+import 'package:spotube/services/sourced_track/models/video_info.dart';
+import 'package:spotube/services/sourced_track/sourced_track.dart';
+import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
@@ -35,7 +36,6 @@ class SiblingTracksSheet extends HookConsumerWidget {
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final preferences = ref.watch(userPreferencesProvider);
- final youtube = ref.watch(youtubeProvider);
final isSearching = useState(false);
final searchMode = useState(preferences.searchMode);
@@ -61,18 +61,31 @@ class SiblingTracksSheet extends HookConsumerWidget {
final searchRequest = useMemoized(() async {
if (searchTerm.trim().isEmpty) {
- return [];
+ return [];
}
- return youtube.search(searchTerm.trim());
+ final results = await youtubeClient.search.search(searchTerm.trim());
+
+ return await Future.wait(
+ results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async {
+ final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video);
+ return siblingType.info;
+ }),
+ );
}, [
searchTerm,
searchMode.value,
]);
- final siblings = playlist.isFetching == false
- ? (playlist.activeTrack as SpotubeTrack).siblings
- : [];
+ final siblings = useMemoized(
+ () => playlist.isFetching == false
+ ? [
+ (playlist.activeTrack as SourcedTrack).sourceInfo,
+ ...(playlist.activeTrack as SourcedTrack).siblings,
+ ]
+ : [],
+ [playlist.isFetching, playlist.activeTrack],
+ );
final borderRadius = floating
? BorderRadius.circular(10)
@@ -82,21 +95,21 @@ class SiblingTracksSheet extends HookConsumerWidget {
);
useEffect(() {
- if (playlist.activeTrack is SpotubeTrack &&
- (playlist.activeTrack as SpotubeTrack).siblings.isEmpty) {
+ if (playlist.activeTrack is SourcedTrack &&
+ (playlist.activeTrack as SourcedTrack).siblings.isEmpty) {
playlistNotifier.populateSibling();
}
return null;
}, [playlist.activeTrack]);
final itemBuilder = useCallback(
- (YoutubeVideoInfo video) {
+ (SourceInfo sourceInfo) {
return ListTile(
- title: Text(video.title),
+ title: Text(sourceInfo.title),
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: UniversalImage(
- path: video.thumbnailUrl,
+ path: sourceInfo.thumbnail,
height: 60,
width: 60,
),
@@ -104,16 +117,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
- trailing: Text(video.duration.toHumanReadableString()),
- subtitle: Text(video.channelName),
+ trailing: Text(sourceInfo.duration.toHumanReadableString()),
+ subtitle: Text(sourceInfo.artist),
enabled: playlist.isFetching != true,
selected: playlist.isFetching != true &&
- video.id == (playlist.activeTrack as SpotubeTrack).ytTrack.id,
+ sourceInfo.id ==
+ (playlist.activeTrack as SourcedTrack).sourceInfo.id,
selectedTileColor: theme.popupMenuTheme.color,
onTap: () {
if (playlist.isFetching == false &&
- video.id != (playlist.activeTrack as SpotubeTrack).ytTrack.id) {
- playlistNotifier.swapSibling(video);
+ sourceInfo.id !=
+ (playlist.activeTrack as SourcedTrack).sourceInfo.id) {
+ playlistNotifier.swapSibling(sourceInfo);
Navigator.of(context).pop();
}
},
@@ -175,7 +190,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
},
)
else ...[
- if (preferences.youtubeApiType == YoutubeApiType.piped)
+ if (preferences.audioSource == AudioSource.piped)
PopupMenuButton(
icon: const Icon(SpotubeIcons.filter, size: 18),
onSelected: (SearchMode mode) {
diff --git a/lib/components/shared/dialogs/track_details_dialog.dart b/lib/components/shared/dialogs/track_details_dialog.dart
index 9e29c32d..8634776f 100644
--- a/lib/components/shared/dialogs/track_details_dialog.dart
+++ b/lib/components/shared/dialogs/track_details_dialog.dart
@@ -6,8 +6,7 @@ import 'package:spotube/components/shared/links/hyper_link.dart';
import 'package:spotube/components/shared/links/link_text.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
-import 'package:spotube/models/spotube_track.dart';
-import 'package:spotube/utils/primitive_utils.dart';
+import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:spotube/extensions/duration.dart';
@@ -37,8 +36,8 @@ class TrackDetailsDialog extends HookWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.blue),
),
- context.l10n.duration: (track is SpotubeTrack
- ? (track as SpotubeTrack).ytTrack.duration
+ context.l10n.duration: (track is SourcedTrack
+ ? (track as SourcedTrack).sourceInfo.duration
: track.duration!)
.toHumanReadableString(),
if (track.album!.releaseDate != null)
@@ -46,33 +45,27 @@ class TrackDetailsDialog extends HookWidget {
context.l10n.popularity: track.popularity?.toString() ?? "0",
};
- final ytTrack =
- track is SpotubeTrack ? (track as SpotubeTrack).ytTrack : null;
+ final sourceInfo =
+ track is SourcedTrack ? (track as SourcedTrack).sourceInfo : null;
- final ytTracksDetailsMap = ytTrack == null
+ final ytTracksDetailsMap = sourceInfo == null
? {}
: {
context.l10n.youtube: Hyperlink(
- "https://piped.video/watch?v=${ytTrack.id}",
- "https://piped.video/watch?v=${ytTrack.id}",
+ "https://piped.video/watch?v=${sourceInfo.id}",
+ "https://piped.video/watch?v=${sourceInfo.id}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
context.l10n.channel: Hyperlink(
- ytTrack.channelName,
- "https://youtube.com${ytTrack.channelName}",
+ sourceInfo.artist,
+ sourceInfo.artistUrl,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
- context.l10n.likes:
- PrimitiveUtils.toReadableNumber(ytTrack.likes.toDouble()),
- context.l10n.dislikes:
- PrimitiveUtils.toReadableNumber(ytTrack.dislikes.toDouble()),
- context.l10n.views:
- PrimitiveUtils.toReadableNumber(ytTrack.views.toDouble()),
context.l10n.streamUrl: Hyperlink(
- (track as SpotubeTrack).ytUri,
- (track as SpotubeTrack).ytUri,
+ (track as SourcedTrack).url,
+ (track as SourcedTrack).url,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
diff --git a/lib/components/shared/track_table/track_options.dart b/lib/components/shared/track_table/track_options.dart
index 96bd8b60..b0633d34 100644
--- a/lib/components/shared/track_table/track_options.dart
+++ b/lib/components/shared/track_table/track_options.dart
@@ -110,7 +110,7 @@ class TrackOptions extends HookConsumerWidget {
]);
final progressNotifier = useMemoized(() {
- final spotubeTrack = downloadManager.mapToSpotubeTrack(track);
+ final spotubeTrack = downloadManager.mapToSourcedTrack(track);
if (spotubeTrack == null) return null;
return downloadManager.getProgressNotifier(spotubeTrack);
});
diff --git a/lib/components/shared/track_table/tracks_table_view.dart b/lib/components/shared/track_table/tracks_table_view.dart
index 14a4f1a9..003662f5 100644
--- a/lib/components/shared/track_table/tracks_table_view.dart
+++ b/lib/components/shared/track_table/tracks_table_view.dart
@@ -60,7 +60,7 @@ class TracksTableView extends HookConsumerWidget {
ref.watch(downloadManagerProvider);
final downloader = ref.watch(downloadManagerProvider.notifier);
final apiType =
- ref.watch(userPreferencesProvider.select((s) => s.youtubeApiType));
+ ref.watch(userPreferencesProvider.select((s) => s.audioSource));
const tableHeadStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
final selected = useState>([]);
@@ -195,7 +195,7 @@ class TracksTableView extends HookConsumerWidget {
switch (action) {
case "download":
{
- final confirmed = apiType == YoutubeApiType.piped ||
+ final confirmed = apiType == AudioSource.piped ||
await showDialog(
context: context,
builder: (context) {
diff --git a/lib/main.dart b/lib/main.dart
index f46f02c1..5d7ae2a7 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -18,8 +18,8 @@ import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.da
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/models/logger.dart';
-import 'package:spotube/models/matched_track.dart';
import 'package:spotube/models/skip_segment.dart';
+import 'package:spotube/models/source_match.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
@@ -71,16 +71,18 @@ Future main(List rawArgs) async {
cacheDir: hiveCacheDir,
connectivity: FlQueryInternetConnectionCheckerAdapter(),
);
- Hive.registerAdapter(MatchedTrackAdapter());
+
Hive.registerAdapter(SkipSegmentAdapter());
- Hive.registerAdapter(SearchModeAdapter());
+
+ Hive.registerAdapter(SourceMatchAdapter());
+ Hive.registerAdapter(SourceTypeAdapter());
// Cache versioning entities with Adapter
- MatchedTrack.version = 'v1';
+ SourceMatch.version = 'v1';
SkipSegment.version = 'v1';
- await Hive.openLazyBox(
- MatchedTrack.boxName,
+ await Hive.openLazyBox(
+ SourceMatch.boxName,
path: hiveCacheDir,
);
await Hive.openLazyBox(
diff --git a/lib/models/current_playlist.dart b/lib/models/current_playlist.dart
index 1c3f8e16..53ea2799 100644
--- a/lib/models/current_playlist.dart
+++ b/lib/models/current_playlist.dart
@@ -1,6 +1,7 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart';
-import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/extensions/track.dart';
+import 'package:spotube/services/sourced_track/sourced_track.dart';
class CurrentPlaylist {
List