diff --git a/lib/components/DownloadTrackButton.dart b/lib/components/DownloadTrackButton.dart index bcafc6fd..6d66da8a 100644 --- a/lib/components/DownloadTrackButton.dart +++ b/lib/components/DownloadTrackButton.dart @@ -15,8 +15,11 @@ class DownloadTrackButton extends StatefulWidget { _DownloadTrackButtonState createState() => _DownloadTrackButtonState(); } +enum TrackStatus { downloading, idle, done } + class _DownloadTrackButtonState extends State { late YoutubeExplode yt; + TrackStatus status = TrackStatus.idle; @override void initState() { @@ -30,33 +33,89 @@ class _DownloadTrackButtonState extends State { super.dispose(); } + _downloadTrack() async { + if (widget.track == null) return; + StreamManifest manifest = + await yt.videos.streamsClient.getManifest(widget.track?.href); + + var audioStream = yt.videos.streamsClient + .get(manifest.audioOnly.withHighestBitrate()) + .asBroadcastStream(); + + var statusCb = audioStream.listen( + (event) { + if (status != TrackStatus.downloading) { + setState(() { + status = TrackStatus.downloading; + }); + } + }, + onDone: () async { + setState(() { + status = TrackStatus.done; + }); + await Future.delayed( + const Duration(seconds: 3), + () { + if (status == TrackStatus.done) { + setState(() { + status = TrackStatus.idle; + }); + } + }, + ); + }, + ); + + 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().then((value) async { + if (status == TrackStatus.downloading) { + setState(() { + status = TrackStatus.done; + }); + await Future.delayed( + const Duration(seconds: 3), + () { + if (status == TrackStatus.done) { + setState(() { + status = TrackStatus.idle; + }); + } + }, + ); + } + return statusCb.cancel(); + }); + } + } + @override Widget build(BuildContext context) { + if (status == TrackStatus.downloading) { + return const SizedBox( + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + height: 20, + width: 20, + ); + } else if (status == TrackStatus.done) { + return const Icon(Icons.download_done_rounded); + } 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(); - } - } + onPressed: widget.track != null && + !(widget.track!.href ?? "").startsWith("https://api.spotify.com") + ? _downloadTrack : null, ); } diff --git a/lib/components/PlaylistView.dart b/lib/components/PlaylistView.dart index 47d32844..3fdd2e33 100644 --- a/lib/components/PlaylistView.dart +++ b/lib/components/PlaylistView.dart @@ -15,7 +15,25 @@ class PlaylistView extends StatefulWidget { } class _PlaylistViewState extends State { - List trackToTableRow(List tracks) { + playPlaylist(Playback playback, List tracks, {Track? currentTrack}) { + currentTrack ??= tracks.first; + var isPlaylistPlaying = playback.currentPlaylist?.id == widget.playlist.id; + if (!isPlaylistPlaying) { + playback.setCurrentPlaylist = CurrentPlaylist( + tracks: tracks, + id: widget.playlist.id!, + name: widget.playlist.name!, + thumbnail: widget.playlist.images![0].url!, + ); + playback.setCurrentTrack = currentTrack; + } else if (isPlaylistPlaying && + currentTrack.id != null && + currentTrack.id != playback.currentTrack?.id) { + playback.setCurrentTrack = currentTrack; + } + } + + List trackToTableRow(Playback playback, List tracks) { return tracks.asMap().entries.map((track) { String? thumbnailUrl = (track.value.album?.images?.isNotEmpty ?? false) ? track.value.album?.images?.last.url @@ -35,6 +53,18 @@ class _PlaylistViewState extends State { TableCell( child: Row( children: [ + IconButton( + icon: Icon( + playback.currentTrack?.id != null && + playback.currentTrack?.id == track.value.id + ? Icons.pause_circle_rounded + : Icons.play_circle_rounded, + color: Theme.of(context).primaryColor, + ), + onPressed: () { + playPlaylist(playback, tracks, currentTrack: track.value); + }, + ), if (thumbnailUrl != null) Padding( padding: const EdgeInsets.all(8.0), @@ -105,6 +135,9 @@ class _PlaylistViewState extends State { @override Widget build(BuildContext context) { + Playback playback = context.watch(); + var isPlaylistPlaying = + playback.currentPlaylist?.id == this.widget.playlist.id; return Consumer(builder: (_, data, __) { return Scaffold( body: FutureBuilder>( @@ -132,37 +165,16 @@ class _PlaylistViewState extends State { onPressed: () {}, ), // play playlist - Consumer( - builder: (context, playback, widget) { - var isPlaylistPlaying = - playback.currentPlaylist?.id == - this.widget.playlist.id; - return IconButton( - icon: Icon( - isPlaylistPlaying - ? Icons.stop_rounded - : Icons.play_arrow_rounded, - ), - onPressed: snapshot.hasData - ? () { - if (!isPlaylistPlaying) { - playback.setCurrentPlaylist = - CurrentPlaylist( - tracks: tracks, - id: this.widget.playlist.id!, - name: this.widget.playlist.name!, - thumbnail: this - .widget - .playlist - .images![0] - .url!, - ); - playback.setCurrentTrack = tracks.first; - } - } - : null, - ); - }), + IconButton( + icon: Icon( + isPlaylistPlaying + ? Icons.stop_rounded + : Icons.play_arrow_rounded, + ), + onPressed: snapshot.hasData + ? () => playPlaylist(playback, tracks) + : null, + ) ], ), ), @@ -216,7 +228,7 @@ class _PlaylistViewState extends State { )), ], ), - ...trackToTableRow(tracks), + ...trackToTableRow(playback, tracks), ], ), ),