diff --git a/lib/components/DownloadTrackButton.dart b/lib/components/DownloadTrackButton.dart new file mode 100644 index 00000000..bcafc6fd --- /dev/null +++ b/lib/components/DownloadTrackButton.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/helpers/artist-to-string.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:path/path.dart' as path; + +class DownloadTrackButton extends StatefulWidget { + final Track? track; + const DownloadTrackButton({Key? key, this.track}) : super(key: key); + + @override + _DownloadTrackButtonState createState() => _DownloadTrackButtonState(); +} + +class _DownloadTrackButtonState extends State { + late YoutubeExplode yt; + + @override + void initState() { + yt = YoutubeExplode(); + super.initState(); + } + + @override + void dispose() { + yt.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return IconButton( + icon: const Icon(Icons.download_rounded), + onPressed: widget.track != null + ? () async { + if (widget.track == null) return; + StreamManifest manifest = await yt.videos.streamsClient + .getManifest(widget.track?.href!.split("watch?v=").last); + + var audioStream = yt.videos.streamsClient + .get(manifest.audioOnly.withHighestBitrate()); + + String downloadFolder = path.join( + (await path_provider.getDownloadsDirectory())!.path, + "Spotube"); + String fileName = + "${widget.track?.name} - ${artistsToString(widget.track?.artists ?? [])}.mp3"; + File outputFile = File(path.join(downloadFolder, fileName)); + if (!outputFile.existsSync()) { + outputFile.createSync(recursive: true); + IOSink outputFileStream = outputFile.openWrite(); + await audioStream.pipe(outputFileStream); + await outputFileStream.flush(); + await outputFileStream.close(); + } + } + : null, + ); + } +} diff --git a/lib/components/Player.dart b/lib/components/Player.dart index 783214f0..0c5caf8a 100644 --- a/lib/components/Player.dart +++ b/lib/components/Player.dart @@ -5,6 +5,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:just_audio/just_audio.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/DownloadTrackButton.dart'; import 'package:spotube/components/PlayerControls.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/search-youtube.dart'; @@ -354,6 +355,9 @@ class _PlayerState extends State with WidgetsBindingObserver { }, ), ), + DownloadTrackButton( + track: playback.currentTrack, + ), // add to saved tracks Expanded( flex: 1, diff --git a/lib/helpers/search-youtube.dart b/lib/helpers/search-youtube.dart index f659e68d..74f4351d 100644 --- a/lib/helpers/search-youtube.dart +++ b/lib/helpers/search-youtube.dart @@ -19,6 +19,7 @@ Future toYoutubeTrack(Track track) async { var trackManifest = await youtube.videos.streams.getManifest(ytVideo.id); - track.uri = trackManifest.audioOnly.first.url.toString(); + track.uri = trackManifest.audioOnly.withHighestBitrate().url.toString(); + track.href = ytVideo.url; return track; } diff --git a/pubspec.lock b/pubspec.lock index 79f52987..45c709eb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -367,14 +367,14 @@ packages: source: hosted version: "2.0.2" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" source: hosted version: "1.8.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 27b1b87d..8b4b1f34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,8 @@ dependencies: hotkey_manager: ^0.1.6 just_audio: ^0.9.18 just_audio_libwinmedia: ^0.0.4 + path: ^1.8.0 + path_provider: ^2.0.8 dev_dependencies: flutter_test: