[new] Play playlist starting from any track

[new] skip to another track in the currently playing playlist
[improved] Download track now with infinite progressbar & completion indicator
This commit is contained in:
Kingkor Roy Tirtho 2022-01-21 20:00:58 +06:00
parent 0801d6170b
commit b75256b481
2 changed files with 127 additions and 56 deletions

View File

@ -15,8 +15,11 @@ class DownloadTrackButton extends StatefulWidget {
_DownloadTrackButtonState createState() => _DownloadTrackButtonState();
}
enum TrackStatus { downloading, idle, done }
class _DownloadTrackButtonState extends State<DownloadTrackButton> {
late YoutubeExplode yt;
TrackStatus status = TrackStatus.idle;
@override
void initState() {
@ -30,33 +33,89 @@ class _DownloadTrackButtonState extends State<DownloadTrackButton> {
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,
);
}

View File

@ -15,7 +15,25 @@ class PlaylistView extends StatefulWidget {
}
class _PlaylistViewState extends State<PlaylistView> {
List<TableRow> trackToTableRow(List<Track> tracks) {
playPlaylist(Playback playback, List<Track> 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<TableRow> trackToTableRow(Playback playback, List<Track> 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<PlaylistView> {
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<PlaylistView> {
@override
Widget build(BuildContext context) {
Playback playback = context.watch<Playback>();
var isPlaylistPlaying =
playback.currentPlaylist?.id == this.widget.playlist.id;
return Consumer<SpotifyDI>(builder: (_, data, __) {
return Scaffold(
body: FutureBuilder<Iterable<Track>>(
@ -132,37 +165,16 @@ class _PlaylistViewState extends State<PlaylistView> {
onPressed: () {},
),
// play playlist
Consumer<Playback>(
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<PlaylistView> {
)),
],
),
...trackToTableRow(tracks),
...trackToTableRow(playback, tracks),
],
),
),