From aebe6d332f61315bb253919185eabeb6d7b9bdcf Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 22 Apr 2022 22:13:31 +0600 Subject: [PATCH] better searching result matching for timed-lyrics SyncedLyrics not found fallback with static genius lyrics marquee text in PlayerView & Lyric page --- lib/components/Artist/ArtistCard.dart | 2 +- lib/components/Lyrics/SyncedLyrics.dart | 51 +++++++++++++++---- lib/components/Player/PlayerView.dart | 25 ++++++--- lib/components/Shared/PageWindowTitleBar.dart | 11 ++-- lib/components/Shared/PlaybuttonCard.dart | 4 +- lib/components/Shared/SpotubeMarqueeText.dart | 7 ++- lib/helpers/timed-lyrics.dart | 48 ++++++++++------- 7 files changed, 104 insertions(+), 44 deletions(-) diff --git a/lib/components/Artist/ArtistCard.dart b/lib/components/Artist/ArtistCard.dart index 7f43d3cb..d85c238b 100644 --- a/lib/components/Artist/ArtistCard.dart +++ b/lib/components/Artist/ArtistCard.dart @@ -48,7 +48,7 @@ class ArtistCard extends StatelessWidget { child: artist.name!.length > 15 ? SpotubeMarqueeText( text: artist.name!, - textStyle: Theme.of(context).textTheme.headline5!, + style: Theme.of(context).textTheme.headline5!, ) : Text( artist.name!, diff --git a/lib/components/Lyrics/SyncedLyrics.dart b/lib/components/Lyrics/SyncedLyrics.dart index a35cc5f2..057c73e0 100644 --- a/lib/components/Lyrics/SyncedLyrics.dart +++ b/lib/components/Lyrics/SyncedLyrics.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Lyrics.dart'; +import 'package:spotube/components/Shared/SpotubeMarqueeText.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/timed-lyrics.dart'; import 'package:spotube/hooks/useAutoScrollController.dart'; @@ -19,10 +21,20 @@ class SyncedLyrics extends HookConsumerWidget { Playback playback = ref.watch(playbackProvider); final breakpoint = useBreakpoints(); final controller = useAutoScrollController(); - final timedLyrics = useMemoized(() { + final failed = useState(false); + final timedLyrics = useMemoized(() async { if (playback.currentTrack == null || playback.currentTrack is! SpotubeTrack) return null; - return getTimedLyrics(playback.currentTrack as SpotubeTrack); + try { + final lyrics = + await getTimedLyrics(playback.currentTrack as SpotubeTrack); + if (failed.value) failed.value = false; + return lyrics; + } catch (e) { + if (e == "Subtitle lookup failed") { + failed.value = true; + } + } }, [playback.currentTrack]); final lyricsSnapshot = useFuture(timedLyrics); final lyricsMap = useMemoized( @@ -39,17 +51,37 @@ class SyncedLyrics extends HookConsumerWidget { final textTheme = Theme.of(context).textTheme; + useEffect(() { + controller.scrollToIndex( + 0, + preferPosition: AutoScrollPosition.middle, + ); + return null; + }, [playback.currentTrack]); + + // when synced lyrics not found, fallback to GeniusLyrics + if (failed.value) return const Lyrics(); + + final headlineTextStyle = breakpoint >= Breakpoints.md + ? textTheme.headline3 + : textTheme.headline4?.copyWith(fontSize: 25); return Expanded( child: Column( children: [ Center( - child: Text( - playback.currentTrack?.name ?? "", - style: breakpoint >= Breakpoints.md - ? textTheme.headline3 - : textTheme.headline4?.copyWith(fontSize: 25), - ), - ), + child: SizedBox( + height: breakpoint >= Breakpoints.md ? 50 : 30, + child: playback.currentTrack?.name != null && + playback.currentTrack!.name!.length > 29 + ? SpotubeMarqueeText( + text: playback.currentTrack?.name ?? "Not Playing", + style: headlineTextStyle, + ) + : Text( + playback.currentTrack?.name ?? "Not Playing", + style: headlineTextStyle, + ), + )), Center( child: Text( artistsToString(playback.currentTrack?.artists ?? []), @@ -86,6 +118,7 @@ class SyncedLyrics extends HookConsumerWidget { fontWeight: isActive ? FontWeight.bold : null, fontSize: 30, ), + textAlign: TextAlign.center, ), ), ), diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index b910f329..3ffd7060 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -7,6 +7,7 @@ import 'package:palette_generator/palette_generator.dart'; import 'package:spotube/components/Player/PlayerActions.dart'; import 'package:spotube/components/Player/PlayerControls.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; +import 'package:spotube/components/Shared/SpotubeMarqueeText.dart'; import 'package:spotube/helpers/artists-to-clickable-artists.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; @@ -48,6 +49,7 @@ class PlayerView extends HookConsumerWidget { child: Scaffold( appBar: const PageWindowTitleBar( leading: BackButton(), + transparent: true, ), backgroundColor: paletteColor.color, body: Column( @@ -57,13 +59,22 @@ class PlayerView extends HookConsumerWidget { padding: const EdgeInsets.all(10), child: Column( children: [ - Text( - currentTrack?.name ?? "Not playing", - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.headline5?.copyWith( - fontWeight: FontWeight.bold, - color: paletteColor.titleTextColor, - ), + SizedBox( + height: 30, + child: currentTrack?.name != null && + currentTrack!.name!.length > 29 + ? SpotubeMarqueeText( + text: currentTrack.name ?? "Not playing", + style: + Theme.of(context).textTheme.headline5?.copyWith( + fontWeight: FontWeight.bold, + color: paletteColor.titleTextColor, + ), + ) + : Text( + currentTrack?.name ?? "Not Playing", + style: Theme.of(context).textTheme.headline5, + ), ), artistsToClickableArtists( currentTrack?.artists ?? [], diff --git a/lib/components/Shared/PageWindowTitleBar.dart b/lib/components/Shared/PageWindowTitleBar.dart index f773309e..4de0faf8 100644 --- a/lib/components/Shared/PageWindowTitleBar.dart +++ b/lib/components/Shared/PageWindowTitleBar.dart @@ -49,8 +49,13 @@ class PageWindowTitleBar extends StatelessWidget implements PreferredSizeWidget { final Widget? leading; final Widget? center; - const PageWindowTitleBar({Key? key, this.leading, this.center}) - : super(key: key); + final bool transparent; + const PageWindowTitleBar({ + Key? key, + this.leading, + this.center, + this.transparent = false, + }) : super(key: key); @override Size get preferredSize => Size.fromHeight( !Platform.isIOS && !Platform.isAndroid ? appWindow.titleBarHeight : 35, @@ -71,7 +76,7 @@ class PageWindowTitleBar extends StatelessWidget } return WindowTitleBarBox( child: Container( - color: Theme.of(context).scaffoldBackgroundColor, + color: !transparent ? Theme.of(context).scaffoldBackgroundColor : null, child: Row( children: [ if (Platform.isMacOS) diff --git a/lib/components/Shared/PlaybuttonCard.dart b/lib/components/Shared/PlaybuttonCard.dart index 120f8e02..06a4e26f 100644 --- a/lib/components/Shared/PlaybuttonCard.dart +++ b/lib/components/Shared/PlaybuttonCard.dart @@ -93,7 +93,7 @@ class PlaybuttonCard extends StatelessWidget { child: title.length > 25 ? SpotubeMarqueeText( text: title, - textStyle: const TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold), ) : Text( @@ -110,7 +110,7 @@ class PlaybuttonCard extends StatelessWidget { child: description!.length > 30 ? SpotubeMarqueeText( text: description!, - textStyle: TextStyle( + style: TextStyle( fontSize: 13, color: Theme.of(context) .textTheme diff --git a/lib/components/Shared/SpotubeMarqueeText.dart b/lib/components/Shared/SpotubeMarqueeText.dart index fe9d11c2..9a7762cd 100644 --- a/lib/components/Shared/SpotubeMarqueeText.dart +++ b/lib/components/Shared/SpotubeMarqueeText.dart @@ -2,17 +2,16 @@ import 'package:flutter/material.dart'; import 'package:marquee/marquee.dart'; class SpotubeMarqueeText extends StatelessWidget { - const SpotubeMarqueeText( - {Key? key, required this.text, required this.textStyle}) + const SpotubeMarqueeText({Key? key, required this.text, this.style}) : super(key: key); - final TextStyle textStyle; + final TextStyle? style; final String text; @override Widget build(BuildContext context) { return Marquee( text: text, - style: textStyle, + style: style, scrollAxis: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.start, blankSpace: 60.0, diff --git a/lib/helpers/timed-lyrics.dart b/lib/helpers/timed-lyrics.dart index 0db44bc3..f90dd772 100644 --- a/lib/helpers/timed-lyrics.dart +++ b/lib/helpers/timed-lyrics.dart @@ -3,8 +3,11 @@ import 'package:http/http.dart' as http; import 'package:html/parser.dart'; import 'package:collection/collection.dart'; import 'package:spotube/helpers/getLyrics.dart'; +import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/SpotubeTrack.dart'; +final logger = getLogger("getTimedLyrics"); + class SubtitleSimple { Uri uri; String name; @@ -37,6 +40,9 @@ Future getTimedLyrics(SpotubeTrack track) async { track.name!, artists: artistNames, ); + + logger.v("[Searching Subtitle] $query"); + final searchUri = Uri.parse("$baseUri/subtitles4songs.aspx").replace( queryParameters: {"q": query}, ); @@ -46,28 +52,34 @@ Future getTimedLyrics(SpotubeTrack track) async { final results = document.querySelectorAll("#tablecontainer table tbody tr td a"); - final topResult = results - .map((result) { - final title = result.text.trim().toLowerCase(); - int points = 0; - final hasAllArtists = track.artists - ?.map((artist) => artist.name!) - .every((artist) => title.contains(artist.toLowerCase())) ?? - false; - final hasTrackName = title.contains(track.name!.toLowerCase()); - final exactYtMatch = title == track.ytTrack.title.toLowerCase(); - if (exactYtMatch) points = 8; - for (final criteria in [hasTrackName, hasAllArtists]) { - if (criteria) points++; - } - return {"result": result, "points": points}; - }) - .sorted((a, b) => (b["points"] as int).compareTo(a["points"] as int)) - .first["result"] as Element; + final rateSortedResults = results.map((result) { + final title = result.text.trim().toLowerCase(); + int points = 0; + final hasAllArtists = track.artists + ?.map((artist) => artist.name!) + .every((artist) => title.contains(artist.toLowerCase())) ?? + false; + final hasTrackName = title.contains(track.name!.toLowerCase()); + final exactYtMatch = title == track.ytTrack.title.toLowerCase(); + if (exactYtMatch) points = 8; + for (final criteria in [hasTrackName, hasAllArtists]) { + if (criteria) points++; + } + return {"result": result, "points": points}; + }).sorted((a, b) => (b["points"] as int).compareTo(a["points"] as int)); + // not result was found at all + if (rateSortedResults.first["points"] == 0) { + logger.e("[Subtitle not found] ${track.name}"); + return Future.error("Subtitle lookup failed", StackTrace.current); + } + + final topResult = rateSortedResults.first["result"] as Element; final subtitleUri = Uri.parse("$baseUri/${topResult.attributes["href"]}&type=lrc"); + logger.v("[Selected subtitle] ${topResult.text} | $subtitleUri"); + final lrcDocument = parse((await http.get(subtitleUri)).body); final lrcList = lrcDocument .querySelector("#ctl00_ContentPlaceHolder1_lbllyrics")