diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index 9754409b..99699d73 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -67,7 +67,7 @@ class PlayerView extends HookConsumerWidget { ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), - child: Container( + child: Material( color: paletteColor.color.withOpacity(.5), child: SafeArea( child: Column( diff --git a/lib/components/Search/Search.dart b/lib/components/Search/Search.dart index 6aac8872..bf8c1876 100644 --- a/lib/components/Search/Search.dart +++ b/lib/components/Search/Search.dart @@ -37,8 +37,8 @@ class Search extends HookConsumerWidget { } final searchSnapshot = ref.watch(searchQuery(searchTerm)); - return SafeArea( - child: Expanded( + return Expanded( + child: SafeArea( child: Container( color: Theme.of(context).backgroundColor, child: Column( diff --git a/lib/components/Shared/TrackTile.dart b/lib/components/Shared/TrackTile.dart index 3fc7e5b1..03660f2b 100644 --- a/lib/components/Shared/TrackTile.dart +++ b/lib/components/Shared/TrackTile.dart @@ -177,151 +177,155 @@ class TrackTile extends HookConsumerWidget { : Colors.transparent, borderRadius: BorderRadius.circular(isActive ? 10 : 0), ), - child: Row( - children: [ - SizedBox( - height: 20, - width: 25, - child: Text( - (track.key + 1).toString(), - textAlign: TextAlign.center, - ), - ), - if (thumbnailUrl != null) - Padding( - padding: EdgeInsets.symmetric( - horizontal: breakpoint.isMoreThan(Breakpoints.md) ? 8.0 : 0, - vertical: 8.0, + child: Material( + type: MaterialType.transparency, + child: Row( + children: [ + SizedBox( + height: 20, + width: 25, + child: Text( + (track.key + 1).toString(), + textAlign: TextAlign.center, ), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(5)), - child: CachedNetworkImage( - placeholder: (context, url) { - return Container( - height: 40, - width: 40, - color: Theme.of(context).primaryColor, - ); - }, - imageUrl: thumbnailUrl!, - maxHeightDiskCache: 40, - maxWidthDiskCache: 40, + ), + if (thumbnailUrl != null) + Padding( + padding: EdgeInsets.symmetric( + horizontal: breakpoint.isMoreThan(Breakpoints.md) ? 8.0 : 0, + vertical: 8.0, + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(5)), + child: CachedNetworkImage( + placeholder: (context, url) { + return Container( + height: 40, + width: 40, + color: Theme.of(context).primaryColor, + ); + }, + imageUrl: thumbnailUrl!, + maxHeightDiskCache: 40, + maxWidthDiskCache: 40, + ), ), ), + IconButton( + icon: Icon( + playback.track?.id != null && + playback.track?.id == track.value.id + ? Icons.pause_circle_rounded + : Icons.play_circle_rounded, + color: Theme.of(context).primaryColor, + ), + onPressed: () => onTrackPlayButtonPressed?.call( + track.value, + ), ), - IconButton( - icon: Icon( - playback.track?.id != null && playback.track?.id == track.value.id - ? Icons.pause_circle_rounded - : Icons.play_circle_rounded, - color: Theme.of(context).primaryColor, - ), - onPressed: () => onTrackPlayButtonPressed?.call( - track.value, - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - track.value.name ?? "", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: breakpoint.isSm ? 14 : 17, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + track.value.name ?? "", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: breakpoint.isSm ? 14 : 17, + ), + overflow: TextOverflow.ellipsis, ), + artistsToClickableArtists(track.value.artists ?? [], + textStyle: TextStyle( + fontSize: + breakpoint.isLessThan(Breakpoints.lg) ? 12 : 14)), + ], + ), + ), + if (breakpoint.isMoreThan(Breakpoints.md) && showAlbum) + Expanded( + child: LinkText( + track.value.album!.name!, + "/album/${track.value.album?.id}", + extra: track.value.album, overflow: TextOverflow.ellipsis, ), - artistsToClickableArtists(track.value.artists ?? [], - textStyle: TextStyle( - fontSize: - breakpoint.isLessThan(Breakpoints.lg) ? 12 : 14)), - ], - ), - ), - if (breakpoint.isMoreThan(Breakpoints.md) && showAlbum) - Expanded( - child: LinkText( - track.value.album!.name!, - "/album/${track.value.album?.id}", - extra: track.value.album, - overflow: TextOverflow.ellipsis, ), - ), - if (!breakpoint.isSm) ...[ + if (!breakpoint.isSm) ...[ + const SizedBox(width: 10), + Text(duration), + ], const SizedBox(width: 10), - Text(duration), + PopupMenuButton( + icon: const Icon(Icons.more_horiz_rounded), + itemBuilder: (context) { + return [ + if (auth.isLoggedIn) + PopupMenuItem( + child: Row( + children: const [ + Icon(Icons.add_box_rounded), + SizedBox(width: 10), + Text("Add to Playlist"), + ], + ), + value: "add-playlist", + ), + if (userPlaylist && auth.isLoggedIn) + PopupMenuItem( + child: Row( + children: const [ + Icon(Icons.remove_circle_outline_rounded), + SizedBox(width: 10), + Text("Remove from Playlist"), + ], + ), + value: "remove-playlist", + ), + if (auth.isLoggedIn) + PopupMenuItem( + child: Row( + children: [ + Icon(isSaved + ? Icons.favorite_rounded + : Icons.favorite_border_rounded), + const SizedBox(width: 10), + const Text("Favorite") + ], + ), + value: "favorite", + ), + PopupMenuItem( + child: Row( + children: const [ + Icon(Icons.share_rounded), + SizedBox(width: 10), + Text("Share") + ], + ), + value: "share", + ) + ]; + }, + onSelected: (value) { + switch (value) { + case "favorite": + actionFavorite(isSaved); + break; + case "add-playlist": + actionAddToPlaylist(); + break; + case "remove-playlist": + actionRemoveFromPlaylist(); + break; + case "share": + actionShare(track.value); + break; + } + }, + ), ], - const SizedBox(width: 10), - PopupMenuButton( - icon: const Icon(Icons.more_horiz_rounded), - itemBuilder: (context) { - return [ - if (auth.isLoggedIn) - PopupMenuItem( - child: Row( - children: const [ - Icon(Icons.add_box_rounded), - SizedBox(width: 10), - Text("Add to Playlist"), - ], - ), - value: "add-playlist", - ), - if (userPlaylist && auth.isLoggedIn) - PopupMenuItem( - child: Row( - children: const [ - Icon(Icons.remove_circle_outline_rounded), - SizedBox(width: 10), - Text("Remove from Playlist"), - ], - ), - value: "remove-playlist", - ), - if (auth.isLoggedIn) - PopupMenuItem( - child: Row( - children: [ - Icon(isSaved - ? Icons.favorite_rounded - : Icons.favorite_border_rounded), - const SizedBox(width: 10), - const Text("Favorite") - ], - ), - value: "favorite", - ), - PopupMenuItem( - child: Row( - children: const [ - Icon(Icons.share_rounded), - SizedBox(width: 10), - Text("Share") - ], - ), - value: "share", - ) - ]; - }, - onSelected: (value) { - switch (value) { - case "favorite": - actionFavorite(isSaved); - break; - case "add-playlist": - actionAddToPlaylist(); - break; - case "remove-playlist": - actionRemoveFromPlaylist(); - break; - case "share": - actionShare(track.value); - break; - } - }, - ), - ], + ), ), ); } diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index 6abf60c9..f2d72c33 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:async/async.dart'; import 'package:audio_service/audio_service.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -227,6 +228,27 @@ class Playback extends PersistedChangeNotifier { player.dispose(); } + Future retryingOperation( + Future Function() inner, { + Duration? timeout, + }) async { + T result; + try { + final operation = CancelableOperation.fromFuture(inner()); + result = await operation.value; + await Future.delayed(timeout ?? const Duration(seconds: 5), () async { + if (!operation.isCompleted) { + operation.cancel(); + result = await inner(); + } + }); + return result; + } catch (e) { + result = await inner(); + return result; + } + } + // playlist & track list methods Future toSpotubeTrack(Track track) async { final format = preferences.ytSearchFormat; @@ -260,7 +282,8 @@ class Playback extends PersistedChangeNotifier { ); ytVideo = VideoFromCacheTrackExtension.fromCacheTrack(cachedTrack); } else { - VideoSearchList videos = await youtube.search.search(queryString); + VideoSearchList videos = + await retryingOperation(() => youtube.search.search(queryString)); if (matchAlgorithm != SpotubeTrackMatchAlgorithm.youtube) { List ratedRankedVideos = videos .map((video) { @@ -310,7 +333,9 @@ class Playback extends PersistedChangeNotifier { } } - final trackManifest = await youtube.videos.streams.getManifest(ytVideo.id); + StreamManifest trackManifest = await retryingOperation( + () => youtube.videos.streams.getManifest(ytVideo.id), + ); _logger.v( "[YouTube Matched Track] ${ytVideo.title} | ${ytVideo.author} - ${ytVideo.url}", diff --git a/pubspec.lock b/pubspec.lock index af68e4b4..4374a9f0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,7 +37,7 @@ packages: source: hosted version: "2.3.1" async: - dependency: transitive + dependency: "direct main" description: name: async url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index ac400e50..3f84bb13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: dbus: ^0.7.3 audioplayers: ^1.0.1 resizable_widget: ^1.0.5 + async: ^2.8.2 dev_dependencies: flutter_test: