Lyrics support added

This commit is contained in:
Kingkor Roy Tirtho 2022-01-06 18:37:47 +06:00
parent 6ea222c5b0
commit 50b835e122
6 changed files with 197 additions and 5 deletions

View File

@ -7,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotify/spotify.dart' hide Image;
import 'package:spotube/components/CategoryCard.dart';
import 'package:spotube/components/Login.dart';
import 'package:spotube/components/Lyrics.dart';
import 'package:spotube/components/PageWindowTitleBar.dart';
import 'package:spotube/components/Player.dart' as player;
import 'package:spotube/components/Settings.dart';
@ -239,7 +240,7 @@ class _HomeState extends State<Home> {
),
),
if (_selectedIndex == 2) const UserLibrary(),
// player itself
if (_selectedIndex == 3) const Lyrics(),
],
),
),

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/helpers/getLyrics.dart';
import 'package:spotube/provider/Playback.dart';
class Lyrics extends StatefulWidget {
const Lyrics({Key? key}) : super(key: key);
@override
State<Lyrics> createState() => _LyricsState();
}
class _LyricsState extends State<Lyrics> {
Map<String, String>? _lyrics;
@override
Widget build(BuildContext context) {
Playback playback = context.watch<Playback>();
if (playback.currentTrack != null &&
playback.currentTrack!.id != _lyrics?["id"]) {
getLyrics(
playback.currentTrack!.name!,
artistsToString(playback.currentTrack!.artists ?? []),
apiKey:
"O6K9JcMNsVD36lRJM6wvl0YsfjrtHFFfAwYHZqxxTNg2xBuMxcaJXrYbpR6kVipN",
optimizeQuery: true,
).then((lyrics) {
if (lyrics != null) {
setState(() {
_lyrics = {"lyrics": lyrics, "id": playback.currentTrack!.id!};
});
}
});
}
if (_lyrics == null && playback.currentTrack != null) {
return const Expanded(
child: Center(
child: CircularProgressIndicator.adaptive(),
),
);
}
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
playback.currentTrack!.name!,
style: Theme.of(context).textTheme.headline3,
),
),
Center(
child: Text(
artistsToString(playback.currentTrack!.artists ?? []),
style: Theme.of(context).textTheme.headline5,
),
),
Expanded(
child: SingleChildScrollView(
child: Center(
child: Text(
_lyrics == null && playback.currentTrack == null
? "No Track being played currently"
: _lyrics!["lyrics"]!,
style: Theme.of(context).textTheme.headline6,
),
),
),
),
const Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Powered by genius.com"),
),
)
],
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:spotube/components/PlayerControls.dart';
import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/provider/Playback.dart';
import 'package:flutter/material.dart';
import 'package:mpv_dart/mpv_dart.dart';
@ -128,10 +129,6 @@ class _PlayerState extends State<Player> {
}
}
String artistsToString(List<Artist> artists) {
return artists.map((e) => e.name?.replaceAll(",", " ")).join(", ");
}
@override
Widget build(BuildContext context) {
MPVPlayer player = context.watch<PlayerDI>().player;

View File

@ -0,0 +1,5 @@
import 'package:spotify/spotify.dart';
String artistsToString(List<Artist> artists) {
return artists.map((e) => e.name?.replaceAll(",", " ")).join(", ");
}

104
lib/helpers/getLyrics.dart Normal file
View File

@ -0,0 +1,104 @@
import 'dart:convert';
import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart';
import 'package:http/http.dart' as http;
String getTitle(String title, String artist) {
return "$title $artist"
.toLowerCase()
.replaceAll(RegExp(" *\\([^)]*\\) *"), '')
.replaceAll(RegExp(" *\\[[^\\]]*]"), '')
.replaceAll(RegExp("feat.|ft."), '')
.replaceAll(RegExp("\\s+"), ' ')
.trim();
}
Future<String?> extractLyrics(Uri url) async {
try {
var response = await http.get(url);
Document document = parser.parse(response.body);
var lyrics = document.querySelector('div.lyrics')?.text.trim();
if (lyrics == null) {
lyrics = "";
document
.querySelectorAll("div[class^=\"Lyrics__Container\"]")
.forEach((element) {
if (element.text.trim().isNotEmpty) {
var snippet = element.innerHtml.replaceAll("<br>", "\n").replaceAll(
RegExp("<(?!\\s*br\\s*\\/?)[^>]+>", caseSensitive: false),
"",
);
var el = document.createElement("textarea");
el.innerHtml = snippet;
lyrics = "$lyrics${el.text.trim()}\n\n";
}
});
}
return lyrics;
} catch (e, stack) {
print("[extractLyrics] $e");
print(stack);
rethrow;
}
}
Future<List?> searchSong(
String title,
String artist, {
String apiKey = "",
bool optimizeQuery = false,
bool authHeader = false,
}) async {
try {
const searchUrl = 'https://api.genius.com/search?q=';
String song = optimizeQuery ? getTitle(title, artist) : "$title $artist";
String reqUrl = "$searchUrl${Uri.encodeComponent(song)}";
Map<String, String> headers = {"Authorization": 'Bearer ' + apiKey};
var response = await http.get(
Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"),
headers: authHeader ? headers : null,
);
Map data = jsonDecode(response.body)["response"];
if (data["hits"]?.length == 0) return null;
List results = data["hits"]?.map((val) {
return <String, dynamic>{
"id": val["result"]["id"],
"full_title": val["result"]["full_title"],
"albumArt": val["result"]["song_art_image_url"],
"url": val["result"]["url"]
};
}).toList();
return results;
} catch (e, stack) {
print("[searchSong] $e");
print(stack);
rethrow;
}
}
Future<String?> getLyrics(
String title,
String artist, {
String apiKey = "",
bool optimizeQuery = false,
bool authHeader = false,
}) async {
try {
var results = await searchSong(
title,
artist,
apiKey: apiKey,
optimizeQuery: optimizeQuery,
authHeader: authHeader,
);
if (results == null) return null;
String? lyrics = await extractLyrics(Uri.parse(results.first["url"]));
return lyrics;
} catch (e, stack) {
print("[getLyrics] $e");
print(stack);
}
}

View File

@ -10,4 +10,5 @@ List<SideBarTiles> sidebarTileList = [
SideBarTiles(icon: Icons.home_rounded, title: "Browse"),
SideBarTiles(icon: Icons.search_rounded, title: "Search"),
SideBarTiles(icon: Icons.library_books_rounded, title: "Library"),
SideBarTiles(icon: Icons.music_note_rounded, title: "Lyrics")
];