mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
feat: add download tab on library
Addition of download badge in sidebar and navbar library icon Fix SpotubeMarqueeText behavior using auto_size_text
This commit is contained in:
parent
a23ce61446
commit
8d77b6900a
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart' hide Page;
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
@ -8,6 +9,7 @@ import 'package:spotube/hooks/useBreakpointValue.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/models/sideBarTiles.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/Downloader.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
@ -40,6 +42,9 @@ class Sidebar extends HookConsumerWidget {
|
||||
final extended = useState(false);
|
||||
final meSnapshot = ref.watch(currentUserQuery);
|
||||
final auth = ref.watch(authProvider);
|
||||
final downloadCount = ref.watch(
|
||||
downloaderProvider.select((s) => s.currentlyRunning),
|
||||
);
|
||||
|
||||
final int titleBarDragMaxWidth = useBreakpointValue(
|
||||
md: 80,
|
||||
@ -90,20 +95,34 @@ class Sidebar extends HookConsumerWidget {
|
||||
),
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
destinations: sidebarTileList
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: Icon(e.icon),
|
||||
label: Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
destinations: sidebarTileList.map(
|
||||
(e) {
|
||||
final icon = Icon(e.icon);
|
||||
return NavigationRailDestination(
|
||||
icon: e.title == "Library" && downloadCount > 0
|
||||
? Badge(
|
||||
badgeColor: Colors.red[100]!,
|
||||
badgeContent: Text(
|
||||
downloadCount.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
animationType: BadgeAnimationType.fade,
|
||||
child: icon,
|
||||
)
|
||||
: icon,
|
||||
label: Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onSelectedIndexChanged,
|
||||
extended: extended.value,
|
||||
|
@ -1,10 +1,13 @@
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/components/Home/Sidebar.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/models/sideBarTiles.dart';
|
||||
import 'package:spotube/provider/Downloader.dart';
|
||||
|
||||
class SpotubeNavigationBar extends HookWidget {
|
||||
class SpotubeNavigationBar extends HookConsumerWidget {
|
||||
final int selectedIndex;
|
||||
final void Function(int) onSelectedIndexChanged;
|
||||
|
||||
@ -15,14 +18,36 @@ class SpotubeNavigationBar extends HookWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final downloadCount = ref.watch(
|
||||
downloaderProvider.select((s) => s.currentlyRunning),
|
||||
);
|
||||
final breakpoint = useBreakpoints();
|
||||
|
||||
if (breakpoint.isMoreThan(Breakpoints.sm)) return Container();
|
||||
return NavigationBar(
|
||||
destinations: [
|
||||
...sidebarTileList.map(
|
||||
(e) => NavigationDestination(icon: Icon(e.icon), label: e.title),
|
||||
(e) {
|
||||
final icon = Icon(e.icon);
|
||||
return NavigationDestination(
|
||||
icon: e.title == "Library" && downloadCount > 0
|
||||
? Badge(
|
||||
badgeColor: Colors.red[100]!,
|
||||
badgeContent: Text(
|
||||
downloadCount.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
animationType: BadgeAnimationType.fade,
|
||||
child: icon,
|
||||
)
|
||||
: icon,
|
||||
label: e.title,
|
||||
);
|
||||
},
|
||||
),
|
||||
const NavigationDestination(
|
||||
icon: Icon(Icons.settings_rounded),
|
||||
|
83
lib/components/Library/UserDownloads.dart
Normal file
83
lib/components/Library/UserDownloads.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/Downloader.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
class UserDownloads extends HookConsumerWidget {
|
||||
const UserDownloads({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final downloader = ref.watch(downloaderProvider);
|
||||
|
||||
final inQueue = downloader.inQueue.toList();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoSizeText(
|
||||
"Currently downloading (${downloader.currentlyRunning})",
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.red[50],
|
||||
onPrimary: Colors.red[400],
|
||||
),
|
||||
child: const Text("Cancel All"),
|
||||
onPressed: downloader.currentlyRunning > 0
|
||||
? downloader.cancelAll
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
itemCount: inQueue.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final track = inQueue[index];
|
||||
return ListTile(
|
||||
title: Text(track.name!),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
height: 40,
|
||||
width: 40,
|
||||
imageUrl: TypeConversionUtils.image_X_UrlString(
|
||||
track.album?.images,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: const SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
horizontalTitleGap: 5,
|
||||
subtitle: Text(
|
||||
TypeConversionUtils.artists_X_String<Artist>(
|
||||
track.artists ?? [],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart' hide Image;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/components/Library/UserAlbums.dart';
|
||||
import 'package:spotube/components/Library/UserArtists.dart';
|
||||
import 'package:spotube/components/Library/UserDownloads.dart';
|
||||
import 'package:spotube/components/Library/UserPlaylists.dart';
|
||||
import 'package:spotube/components/Shared/AnonymousFallback.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
@ -14,7 +15,7 @@ class UserLibrary extends ConsumerWidget {
|
||||
|
||||
return Expanded(
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
length: 4,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: TabBar(
|
||||
@ -26,6 +27,7 @@ class UserLibrary extends ConsumerWidget {
|
||||
Tab(text: "Playlist"),
|
||||
Tab(text: "Artists"),
|
||||
Tab(text: "Album"),
|
||||
Tab(text: "Downloads"),
|
||||
],
|
||||
),
|
||||
body: auth.isLoggedIn
|
||||
@ -33,6 +35,7 @@ class UserLibrary extends ConsumerWidget {
|
||||
const UserPlaylists(),
|
||||
UserArtists(),
|
||||
const UserAlbums(),
|
||||
const UserDownloads(),
|
||||
])
|
||||
: const AnonymousFallback(),
|
||||
),
|
||||
|
@ -106,7 +106,7 @@ class PlaybuttonCard extends StatelessWidget {
|
||||
text: title,
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold),
|
||||
minStartLength: 25,
|
||||
minStartLength: 20,
|
||||
isHovering: isHovering,
|
||||
),
|
||||
),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class SpotubeMarqueeText extends HookWidget {
|
||||
final int? minStartLength;
|
||||
@ -18,46 +18,32 @@ class SpotubeMarqueeText extends HookWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hovering = useState(false);
|
||||
final isInitial = useState(true);
|
||||
final uKey = useState(UniqueKey());
|
||||
|
||||
useEffect(() {
|
||||
if (isHovering != null && isHovering != hovering.value) {
|
||||
hovering.value = isHovering!;
|
||||
}
|
||||
return null;
|
||||
uKey.value = UniqueKey();
|
||||
return;
|
||||
}, [isHovering]);
|
||||
|
||||
if ((!isInitial.value && !hovering.value && kIsDesktop) ||
|
||||
minStartLength != null && text.length <= minStartLength!) {
|
||||
return Text(
|
||||
text,
|
||||
style: style,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
|
||||
return Marquee(
|
||||
text: text,
|
||||
return AutoSizeText(
|
||||
text,
|
||||
minFontSize: 13,
|
||||
style: style,
|
||||
scrollAxis: Axis.horizontal,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
blankSpace: 40.0,
|
||||
velocity: 30.0,
|
||||
accelerationDuration: const Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration: const Duration(milliseconds: 500),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
fadingEdgeStartFraction: 0.15,
|
||||
fadingEdgeEndFraction: 0.15,
|
||||
showFadingOnlyWhenScrolling: true,
|
||||
onDone: () {
|
||||
if (isInitial.value) {
|
||||
isInitial.value = false;
|
||||
hovering.value = false;
|
||||
}
|
||||
},
|
||||
numberOfRounds: hovering.value ? null : 1,
|
||||
overflowReplacement: Marquee(
|
||||
key: uKey.value,
|
||||
text: text,
|
||||
style: style,
|
||||
scrollAxis: Axis.horizontal,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
blankSpace: 40.0,
|
||||
velocity: 30.0,
|
||||
accelerationDuration: const Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration: const Duration(milliseconds: 500),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
showFadingOnlyWhenScrolling: true,
|
||||
numberOfRounds: isHovering == true ? null : 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -193,15 +193,15 @@ class TrackTile extends HookConsumerWidget {
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (s) => onCheckChange?.call(s),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 25,
|
||||
child: Center(
|
||||
child: Text((track.key + 1).toString()),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 15,
|
||||
child: Text(
|
||||
(track.key + 1).toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (thumbnailUrl != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
|
@ -132,23 +132,11 @@ class TracksTableView extends HookConsumerWidget {
|
||||
return const DownloadConfirmationDialog();
|
||||
});
|
||||
if (isConfirmed != true) return;
|
||||
final queue = Queue(
|
||||
delay: const Duration(seconds: 5),
|
||||
);
|
||||
for (final selectedTrack in selectedTracks) {
|
||||
queue.add(() async {
|
||||
downloader.addToQueue(
|
||||
await playback.toSpotubeTrack(
|
||||
selectedTrack,
|
||||
noSponsorBlock: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
downloader.addToQueue(selectedTrack);
|
||||
}
|
||||
|
||||
selected.value = [];
|
||||
showCheck.value = false;
|
||||
await queue.onComplete;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -171,7 +159,15 @@ class TracksTableView extends HookConsumerWidget {
|
||||
},
|
||||
onTap: () {
|
||||
if (showCheck.value) {
|
||||
selected.value = [...selected.value, track.value.id!];
|
||||
final alreadyChecked =
|
||||
selected.value.contains(track.value.id);
|
||||
if (alreadyChecked) {
|
||||
selected.value = selected.value
|
||||
.where((id) => id != track.value.id)
|
||||
.toList();
|
||||
} else {
|
||||
selected.value = [...selected.value, track.value.id!];
|
||||
}
|
||||
} else {
|
||||
onTrackPlayButtonPressed?.call(track.value);
|
||||
}
|
||||
|
110
lib/main.dart
110
lib/main.dart
@ -10,11 +10,13 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotube/components/Shared/DownloadTrackButton.dart';
|
||||
import 'package:spotube/entities/CacheTrack.dart';
|
||||
import 'package:spotube/models/GoRouteDeclarations.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/provider/AudioPlayer.dart';
|
||||
import 'package:spotube/provider/Downloader.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/provider/YouTube.dart';
|
||||
@ -53,41 +55,85 @@ void main() async {
|
||||
);
|
||||
}
|
||||
MobileAudioService? audioServiceHandler;
|
||||
runApp(ProviderScope(
|
||||
child: const Spotube(),
|
||||
overrides: [
|
||||
playbackProvider.overrideWithProvider(ChangeNotifierProvider(
|
||||
(ref) {
|
||||
final youtube = ref.watch(youtubeProvider);
|
||||
final player = ref.watch(audioPlayerProvider);
|
||||
runApp(
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ProviderScope(
|
||||
child: const Spotube(),
|
||||
overrides: [
|
||||
playbackProvider.overrideWithProvider(
|
||||
ChangeNotifierProvider(
|
||||
(ref) {
|
||||
final youtube = ref.watch(youtubeProvider);
|
||||
final player = ref.watch(audioPlayerProvider);
|
||||
|
||||
final playback = Playback(
|
||||
player: player,
|
||||
youtube: youtube,
|
||||
ref: ref,
|
||||
);
|
||||
final playback = Playback(
|
||||
player: player,
|
||||
youtube: youtube,
|
||||
ref: ref,
|
||||
);
|
||||
|
||||
if (audioServiceHandler == null) {
|
||||
AudioService.init(
|
||||
builder: () => MobileAudioService(playback),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||
androidNotificationChannelName: 'Spotube',
|
||||
androidNotificationOngoing: true,
|
||||
if (audioServiceHandler == null) {
|
||||
AudioService.init(
|
||||
builder: () => MobileAudioService(playback),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||
androidNotificationChannelName: 'Spotube',
|
||||
androidNotificationOngoing: true,
|
||||
),
|
||||
).then(
|
||||
(value) {
|
||||
playback.mobileAudioService = value;
|
||||
audioServiceHandler = value;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return playback;
|
||||
},
|
||||
),
|
||||
).then(
|
||||
(value) {
|
||||
playback.mobileAudioService = value;
|
||||
audioServiceHandler = value;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return playback;
|
||||
},
|
||||
))
|
||||
],
|
||||
));
|
||||
),
|
||||
downloaderProvider.overrideWithProvider(
|
||||
ChangeNotifierProvider(
|
||||
(ref) {
|
||||
return Downloader(
|
||||
ref,
|
||||
queueInstance,
|
||||
yt: ref.watch(youtubeProvider),
|
||||
downloadPath: ref.watch(
|
||||
userPreferencesProvider.select(
|
||||
(s) => s.downloadLocation,
|
||||
),
|
||||
),
|
||||
onFileExists: (track) {
|
||||
final logger = getLogger(Downloader);
|
||||
try {
|
||||
logger.v(
|
||||
"[onFileExists] download confirmation for ${track.name}",
|
||||
);
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) =>
|
||||
ReplaceDownloadedFileDialog(track: track),
|
||||
).then((s) => s ?? false);
|
||||
} catch (e, stack) {
|
||||
logger.e(
|
||||
"onFileExists",
|
||||
e,
|
||||
stack,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Spotube extends StatefulHookConsumerWidget {
|
||||
|
@ -6,20 +6,26 @@ import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:queue/queue.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/provider/YouTube.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
Queue _queueInstance = Queue(delay: const Duration(seconds: 5));
|
||||
Queue queueInstance = Queue(delay: const Duration(seconds: 5));
|
||||
Queue grabberQueue = Queue(delay: const Duration(seconds: 5));
|
||||
|
||||
class Downloader with ChangeNotifier {
|
||||
Ref ref;
|
||||
Queue _queue;
|
||||
YoutubeExplode yt;
|
||||
String downloadPath;
|
||||
FutureOr<bool> Function(SpotubeTrack track)? onFileExists;
|
||||
Downloader(
|
||||
this.ref,
|
||||
this._queue, {
|
||||
required this.downloadPath,
|
||||
required this.yt,
|
||||
@ -27,73 +33,115 @@ class Downloader with ChangeNotifier {
|
||||
});
|
||||
|
||||
int currentlyRunning = 0;
|
||||
Set<String> inQueue = {};
|
||||
// ignore: prefer_collection_literals
|
||||
Set<Track> inQueue = Set();
|
||||
|
||||
void addToQueue(SpotubeTrack track) async {
|
||||
final logger = getLogger(Downloader);
|
||||
|
||||
void addToQueue(Track baseTrack) async {
|
||||
if (inQueue.any((t) => t.id == baseTrack.id!)) return;
|
||||
inQueue.add(baseTrack);
|
||||
currentlyRunning++;
|
||||
inQueue.add(track.id!);
|
||||
notifyListeners();
|
||||
final filename = '${track.ytTrack.title}.mp3';
|
||||
if (kIsMobile) {
|
||||
final url =
|
||||
((await yt.videos.streamsClient.getManifest(track.ytTrack.url)))
|
||||
.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate()
|
||||
.url;
|
||||
await FlutterDownloader.enqueue(
|
||||
savedDir: downloadPath,
|
||||
url: url.toString(),
|
||||
fileName: filename,
|
||||
openFileFromNotification: true,
|
||||
showNotification: true,
|
||||
);
|
||||
} else {
|
||||
if (inQueue.contains(track.id!)) return;
|
||||
_queue.add(() async {
|
||||
try {
|
||||
final file = File(path.join(downloadPath, filename));
|
||||
if (file.existsSync() && await onFileExists?.call(track) != true) {
|
||||
return;
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
StreamManifest manifest =
|
||||
await yt.videos.streamsClient.getManifest(track.ytTrack.url);
|
||||
final audioStream = yt.videos.streamsClient
|
||||
.get(
|
||||
manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate(),
|
||||
)
|
||||
.asBroadcastStream();
|
||||
grabberQueue.add(() async {
|
||||
final track = await ref.read(playbackProvider).toSpotubeTrack(
|
||||
baseTrack,
|
||||
noSponsorBlock: true,
|
||||
);
|
||||
|
||||
IOSink outputFileStream = file.openWrite();
|
||||
await audioStream.pipe(outputFileStream);
|
||||
await outputFileStream.flush();
|
||||
} finally {
|
||||
currentlyRunning--;
|
||||
inQueue.remove(track.id);
|
||||
notifyListeners();
|
||||
}
|
||||
final filename = '${track.ytTrack.title}.mp3';
|
||||
|
||||
final url =
|
||||
((await yt.videos.streamsClient.getManifest(track.ytTrack.url)))
|
||||
.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate()
|
||||
.url;
|
||||
await FlutterDownloader.enqueue(
|
||||
savedDir: downloadPath,
|
||||
url: url.toString(),
|
||||
fileName: filename,
|
||||
openFileFromNotification: true,
|
||||
showNotification: true,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
grabberQueue.add(() async {
|
||||
final track = await ref.read(playbackProvider).toSpotubeTrack(
|
||||
baseTrack,
|
||||
noSponsorBlock: true,
|
||||
);
|
||||
_queue.add(() async {
|
||||
final filename = '${track.ytTrack.title}.mp3';
|
||||
final file = File(path.join(downloadPath, filename));
|
||||
try {
|
||||
logger.v("[addToQueue] Download starting for ${file.path}");
|
||||
if (file.existsSync() && await onFileExists?.call(track) != true) {
|
||||
return;
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
StreamManifest manifest =
|
||||
await yt.videos.streamsClient.getManifest(track.ytTrack.url);
|
||||
logger.v(
|
||||
"[addToQueue] Getting download information for ${file.path}",
|
||||
);
|
||||
final audioStream = yt.videos.streamsClient
|
||||
.get(
|
||||
manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate(),
|
||||
)
|
||||
.asBroadcastStream();
|
||||
|
||||
logger.v(
|
||||
"[addToQueue] ${file.path} download started",
|
||||
);
|
||||
|
||||
IOSink outputFileStream = file.openWrite();
|
||||
await audioStream.pipe(outputFileStream);
|
||||
await outputFileStream.flush();
|
||||
logger.v(
|
||||
"[addToQueue] Download of ${file.path} is done successfully",
|
||||
);
|
||||
} catch (e, stack) {
|
||||
logger.e(
|
||||
"[addToQueue] Failed download of ${file.path}",
|
||||
e,
|
||||
stack,
|
||||
);
|
||||
rethrow;
|
||||
} finally {
|
||||
currentlyRunning--;
|
||||
inQueue.removeWhere((t) => t.id == track.id);
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll() {
|
||||
grabberQueue.cancel();
|
||||
grabberQueue = Queue();
|
||||
inQueue.clear();
|
||||
currentlyRunning = 0;
|
||||
if (kIsMobile) {
|
||||
FlutterDownloader.cancelAll();
|
||||
} else {
|
||||
_queue.cancel();
|
||||
_queueInstance = Queue();
|
||||
_queue = _queueInstance;
|
||||
queueInstance = Queue();
|
||||
_queue = queueInstance;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
final downloaderProvider = ChangeNotifierProvider(
|
||||
(ref) {
|
||||
return Downloader(
|
||||
_queueInstance,
|
||||
ref,
|
||||
queueInstance,
|
||||
yt: ref.watch(youtubeProvider),
|
||||
downloadPath: ref.watch(
|
||||
userPreferencesProvider.select(
|
||||
|
14
pubspec.lock
14
pubspec.lock
@ -218,6 +218,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
auto_size_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: auto_size_text
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
badges:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: badges
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -69,6 +69,8 @@ dependencies:
|
||||
popover: ^0.2.6+3
|
||||
queue: ^3.1.0+1
|
||||
flutter_downloader: ^1.8.1
|
||||
auto_size_text: ^3.0.0
|
||||
badges: ^2.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user