mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat(lyrics): add LRCLIB lyrics provider as fallback
This commit is contained in:
parent
c8dd8025ec
commit
5afe823abd
@ -1,13 +1,18 @@
|
|||||||
|
import 'package:lrc/lrc.dart';
|
||||||
|
|
||||||
class SubtitleSimple {
|
class SubtitleSimple {
|
||||||
Uri uri;
|
Uri uri;
|
||||||
String name;
|
String name;
|
||||||
List<LyricSlice> lyrics;
|
List<LyricSlice> lyrics;
|
||||||
int rating;
|
int rating;
|
||||||
|
String provider;
|
||||||
|
|
||||||
SubtitleSimple({
|
SubtitleSimple({
|
||||||
required this.uri,
|
required this.uri,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.lyrics,
|
required this.lyrics,
|
||||||
required this.rating,
|
required this.rating,
|
||||||
|
required this.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory SubtitleSimple.fromJson(Map<String, dynamic> json) {
|
factory SubtitleSimple.fromJson(Map<String, dynamic> json) {
|
||||||
@ -18,6 +23,7 @@ class SubtitleSimple {
|
|||||||
.map((e) => LyricSlice.fromJson(e as Map<String, dynamic>))
|
.map((e) => LyricSlice.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
rating: json["rating"] as int,
|
rating: json["rating"] as int,
|
||||||
|
provider: json["provider"] as String? ?? "unknown",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +33,7 @@ class SubtitleSimple {
|
|||||||
"name": name,
|
"name": name,
|
||||||
"lyrics": lyrics.map((e) => e.toJson()).toList(),
|
"lyrics": lyrics.map((e) => e.toJson()).toList(),
|
||||||
"rating": rating,
|
"rating": rating,
|
||||||
|
"provider": provider,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,6 +44,13 @@ class LyricSlice {
|
|||||||
|
|
||||||
LyricSlice({required this.time, required this.text});
|
LyricSlice({required this.time, required this.text});
|
||||||
|
|
||||||
|
factory LyricSlice.fromLrcLine(LrcLine line) {
|
||||||
|
return LyricSlice(
|
||||||
|
time: line.timestamp,
|
||||||
|
text: line.lyrics.trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory LyricSlice.fromJson(Map<String, dynamic> json) {
|
factory LyricSlice.fromJson(Map<String, dynamic> json) {
|
||||||
return LyricSlice(
|
return LyricSlice(
|
||||||
time: Duration(milliseconds: json["time"]),
|
time: Duration(milliseconds: json["time"]),
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -19,6 +20,7 @@ import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
|||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class LyricsPage extends HookConsumerWidget {
|
class LyricsPage extends HookConsumerWidget {
|
||||||
final bool isModal;
|
final bool isModal;
|
||||||
@ -43,13 +45,41 @@ class LyricsPage extends HookConsumerWidget {
|
|||||||
noSetBGColor: true,
|
noSetBGColor: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final tabbar = ThemedButtonsTabBar(
|
PreferredSizeWidget tabbar = ThemedButtonsTabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: " ${context.l10n.synced} "),
|
Tab(text: " ${context.l10n.synced} "),
|
||||||
Tab(text: " ${context.l10n.plain} "),
|
Tab(text: " ${context.l10n.plain} "),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tabbar = PreferredSize(
|
||||||
|
preferredSize: tabbar.preferredSize,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
tabbar,
|
||||||
|
const Spacer(),
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final playback = ref.watch(ProxyPlaylistNotifier.provider);
|
||||||
|
final lyric =
|
||||||
|
ref.watch(syncedLyricsProvider(playback.activeTrack));
|
||||||
|
final providerName = lyric.asData?.value.provider;
|
||||||
|
|
||||||
|
if (providerName == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: Text("Powered by $providerName"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(5),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
|
@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/lyrics/zoom_controls.dart';
|
import 'package:spotube/components/lyrics/zoom_controls.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
||||||
@ -120,6 +119,7 @@ class PlainLyrics extends HookConsumerWidget {
|
|||||||
lyrics == null && playlist.activeTrack == null
|
lyrics == null && playlist.activeTrack == null
|
||||||
? "No Track being played currently"
|
? "No Track being played currently"
|
||||||
: lyrics ?? "",
|
: lyrics ?? "",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -71,125 +75,128 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
CustomScrollView(
|
||||||
children: [
|
controller: controller,
|
||||||
|
slivers: [
|
||||||
if (isModal != true)
|
if (isModal != true)
|
||||||
Center(
|
SliverAppBar(
|
||||||
child: Text(
|
automaticallyImplyLeading: false,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
playlist.activeTrack?.name ?? "Not Playing",
|
playlist.activeTrack?.name ?? "Not Playing",
|
||||||
style: headlineTextStyle,
|
style: headlineTextStyle,
|
||||||
),
|
),
|
||||||
),
|
bottom: PreferredSize(
|
||||||
if (isModal != true)
|
preferredSize: const Size.fromHeight(40),
|
||||||
Center(
|
child: Text(
|
||||||
child: Text(
|
playlist.activeTrack?.artists?.asString() ?? "",
|
||||||
playlist.activeTrack?.artists?.asString() ?? "",
|
style: mediaQuery.mdAndUp
|
||||||
style: mediaQuery.mdAndUp
|
? textTheme.headlineSmall
|
||||||
? textTheme.headlineSmall
|
: textTheme.titleLarge,
|
||||||
: textTheme.titleLarge,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (lyricValue != null &&
|
if (lyricValue != null &&
|
||||||
lyricValue.lyrics.isNotEmpty &&
|
lyricValue.lyrics.isNotEmpty &&
|
||||||
lyricsState.asData?.value.static != true)
|
lyricsState.asData?.value.static != true)
|
||||||
Expanded(
|
SliverList.builder(
|
||||||
child: ListView.builder(
|
itemCount: lyricValue.lyrics.length,
|
||||||
controller: controller,
|
itemBuilder: (context, index) {
|
||||||
itemCount: lyricValue.lyrics.length,
|
final lyricSlice = lyricValue.lyrics[index];
|
||||||
itemBuilder: (context, index) {
|
final isActive = lyricSlice.time.inSeconds == currentTime;
|
||||||
final lyricSlice = lyricValue.lyrics[index];
|
|
||||||
final isActive = lyricSlice.time.inSeconds == currentTime;
|
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
controller.scrollToIndex(
|
controller.scrollToIndex(
|
||||||
index,
|
index,
|
||||||
preferPosition: AutoScrollPosition.middle,
|
preferPosition: AutoScrollPosition.middle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return AutoScrollTag(
|
return AutoScrollTag(
|
||||||
key: ValueKey(index),
|
key: ValueKey(index),
|
||||||
index: index,
|
index: index,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: lyricSlice.text.isEmpty
|
child: lyricSlice.text.isEmpty
|
||||||
? Container(
|
? Container(
|
||||||
|
padding: index == lyricValue.lyrics.length - 1
|
||||||
|
? EdgeInsets.only(
|
||||||
|
bottom: mediaQuery.size.height / 2,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
)
|
||||||
|
: Center(
|
||||||
|
child: Padding(
|
||||||
padding: index == lyricValue.lyrics.length - 1
|
padding: index == lyricValue.lyrics.length - 1
|
||||||
? EdgeInsets.only(
|
? const EdgeInsets.all(8.0).copyWith(
|
||||||
bottom: mediaQuery.size.height / 2,
|
bottom: 100,
|
||||||
)
|
)
|
||||||
: null,
|
: const EdgeInsets.all(8.0),
|
||||||
)
|
child: AnimatedDefaultTextStyle(
|
||||||
: Center(
|
duration: const Duration(milliseconds: 250),
|
||||||
child: Padding(
|
style: TextStyle(
|
||||||
padding: index == lyricValue.lyrics.length - 1
|
fontWeight: isActive
|
||||||
? const EdgeInsets.all(8.0).copyWith(
|
? FontWeight.w500
|
||||||
bottom: 100,
|
: FontWeight.normal,
|
||||||
)
|
fontSize: (isActive ? 28 : 26) *
|
||||||
: const EdgeInsets.all(8.0),
|
(textZoomLevel.value / 100),
|
||||||
child: AnimatedDefaultTextStyle(
|
),
|
||||||
duration: const Duration(milliseconds: 250),
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
child: InkWell(
|
||||||
fontWeight: isActive
|
onTap: () async {
|
||||||
? FontWeight.w500
|
final duration =
|
||||||
: FontWeight.normal,
|
await audioPlayer.duration ??
|
||||||
fontSize: (isActive ? 28 : 26) *
|
Duration.zero;
|
||||||
(textZoomLevel.value / 100),
|
final time = Duration(
|
||||||
),
|
seconds:
|
||||||
textAlign: TextAlign.center,
|
lyricSlice.time.inSeconds - delay,
|
||||||
child: InkWell(
|
);
|
||||||
onTap: () async {
|
if (time > duration || time.isNegative) {
|
||||||
final duration =
|
return;
|
||||||
await audioPlayer.duration ??
|
}
|
||||||
Duration.zero;
|
audioPlayer.seek(time);
|
||||||
final time = Duration(
|
},
|
||||||
seconds:
|
child: Builder(builder: (context) {
|
||||||
lyricSlice.time.inSeconds - delay,
|
return StrokeText(
|
||||||
);
|
text: lyricSlice.text,
|
||||||
if (time > duration || time.isNegative) {
|
textStyle:
|
||||||
return;
|
DefaultTextStyle.of(context).style,
|
||||||
}
|
textColor: isActive
|
||||||
audioPlayer.seek(time);
|
? Colors.white
|
||||||
},
|
: palette.bodyTextColor,
|
||||||
child: Builder(builder: (context) {
|
strokeColor: isActive
|
||||||
return StrokeText(
|
? Colors.black
|
||||||
text: lyricSlice.text,
|
: Colors.transparent,
|
||||||
textStyle:
|
);
|
||||||
DefaultTextStyle.of(context).style,
|
}),
|
||||||
textColor: isActive
|
|
||||||
? Colors.white
|
|
||||||
: palette.bodyTextColor,
|
|
||||||
strokeColor: isActive
|
|
||||||
? Colors.black
|
|
||||||
: Colors.transparent,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
if (playlist.activeTrack != null &&
|
if (playlist.activeTrack != null &&
|
||||||
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
|
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
|
||||||
const Expanded(
|
const SliverToBoxAdapter(child: ShimmerLyrics())
|
||||||
child: ShimmerLyrics(),
|
|
||||||
)
|
|
||||||
else if (playlist.activeTrack != null &&
|
else if (playlist.activeTrack != null &&
|
||||||
(timedLyricsQuery.hasError)) ...[
|
(timedLyricsQuery.hasError)) ...[
|
||||||
Container(
|
SliverToBoxAdapter(
|
||||||
alignment: Alignment.center,
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16),
|
alignment: Alignment.center,
|
||||||
child: Text(
|
padding: const EdgeInsets.all(16),
|
||||||
context.l10n.no_lyrics_available,
|
child: Text(
|
||||||
style: bodyTextTheme,
|
context.l10n.no_lyrics_available,
|
||||||
textAlign: TextAlign.center,
|
style: bodyTextTheme,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(26),
|
const SliverGap(26),
|
||||||
const Icon(SpotubeIcons.noLyrics, size: 60),
|
const SliverToBoxAdapter(
|
||||||
|
child: Icon(SpotubeIcons.noLyrics, size: 60),
|
||||||
|
),
|
||||||
] else if (lyricsState.asData?.value.static == true)
|
] else if (lyricsState.asData?.value.static == true)
|
||||||
Expanded(
|
SliverFillRemaining(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: RichText(
|
child: RichText(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
@ -6,26 +6,28 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier<SubtitleSimple, Track?>
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Track get _track => arg!;
|
||||||
FutureOr<SubtitleSimple> build(track) async {
|
|
||||||
final spotify = ref.watch(spotifyProvider);
|
Future<SubtitleSimple> getSpotifyLyrics(String? token) async {
|
||||||
if (track == null) {
|
|
||||||
throw "No track currently";
|
|
||||||
}
|
|
||||||
final token = await spotify.getCredentials();
|
|
||||||
final res = await http.get(
|
final res = await http.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://spclient.wg.spotify.com/color-lyrics/v2/track/${track.id}?format=json&market=from_token",
|
"https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token",
|
||||||
),
|
),
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent":
|
"User-Agent":
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
|
||||||
"App-platform": "WebPlayer",
|
"App-platform": "WebPlayer",
|
||||||
"authorization": "Bearer ${token.accessToken}"
|
"authorization": "Bearer $token"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw Exception("Unable to find lyrics");
|
return SubtitleSimple(
|
||||||
|
lyrics: [],
|
||||||
|
name: _track.name!,
|
||||||
|
uri: res.request!.url,
|
||||||
|
rating: 0,
|
||||||
|
provider: "Spotify",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final linesRaw = Map.castFrom<dynamic, dynamic, String, dynamic>(
|
final linesRaw = Map.castFrom<dynamic, dynamic, String, dynamic>(
|
||||||
jsonDecode(res.body),
|
jsonDecode(res.body),
|
||||||
@ -41,12 +43,105 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier<SubtitleSimple, Track?>
|
|||||||
|
|
||||||
return SubtitleSimple(
|
return SubtitleSimple(
|
||||||
lyrics: lines,
|
lyrics: lines,
|
||||||
name: track.name!,
|
name: _track.name!,
|
||||||
uri: res.request!.url,
|
uri: res.request!.url,
|
||||||
rating: 100,
|
rating: 100,
|
||||||
|
provider: "Spotify",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lyrics credits: [lrclib.net](https://lrclib.net) and their contributors
|
||||||
|
/// Thanks for their generous public API
|
||||||
|
Future<SubtitleSimple> getLRCLibLyrics() async {
|
||||||
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
|
final res = await http.get(
|
||||||
|
Uri(
|
||||||
|
scheme: "https",
|
||||||
|
host: "lrclib.net",
|
||||||
|
path: "/api/get",
|
||||||
|
queryParameters: {
|
||||||
|
"artist_name": _track.artists?.first.name,
|
||||||
|
"track_name": _track.name,
|
||||||
|
"album_name": _track.album?.name,
|
||||||
|
"duration": _track.duration?.inSeconds.toString(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Spotube v${packageInfo.version} (https://github.com/KRTirtho/spotube)"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
return SubtitleSimple(
|
||||||
|
lyrics: [],
|
||||||
|
name: _track.name!,
|
||||||
|
uri: res.request!.url,
|
||||||
|
rating: 0,
|
||||||
|
provider: "LRCLib",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final json = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
final syncedLyricsRaw = json["syncedLyrics"] as String?;
|
||||||
|
final syncedLyrics = syncedLyricsRaw?.isNotEmpty == true
|
||||||
|
? Lrc.parse(syncedLyricsRaw!)
|
||||||
|
.lyrics
|
||||||
|
.map(LyricSlice.fromLrcLine)
|
||||||
|
.toList()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (syncedLyrics?.isNotEmpty == true) {
|
||||||
|
return SubtitleSimple(
|
||||||
|
lyrics: syncedLyrics!,
|
||||||
|
name: _track.name!,
|
||||||
|
uri: res.request!.url,
|
||||||
|
rating: 100,
|
||||||
|
provider: "LRCLib",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final plainLyrics = (json["plainLyrics"] as String)
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => LyricSlice(text: line, time: Duration.zero))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return SubtitleSimple(
|
||||||
|
lyrics: plainLyrics,
|
||||||
|
name: _track.name!,
|
||||||
|
uri: res.request!.url,
|
||||||
|
rating: 0,
|
||||||
|
provider: "LRCLib",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<SubtitleSimple> build(track) async {
|
||||||
|
try {
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
if (track == null) {
|
||||||
|
throw "No track currently";
|
||||||
|
}
|
||||||
|
final token = await spotify.getCredentials();
|
||||||
|
SubtitleSimple lyrics = await getSpotifyLyrics(token.accessToken);
|
||||||
|
|
||||||
|
if (lyrics.lyrics.isEmpty) {
|
||||||
|
lyrics = await getLRCLibLyrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lyrics.lyrics.isEmpty) {
|
||||||
|
throw Exception("Unable to find lyrics");
|
||||||
|
}
|
||||||
|
|
||||||
|
return lyrics;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
Catcher2.reportCheckedError(e, stackTrace);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<SubtitleSimple> fromJson(Map<String, dynamic> json) =>
|
FutureOr<SubtitleSimple> fromJson(Map<String, dynamic> json) =>
|
||||||
SubtitleSimple.fromJson(json.castKeyDeep<String>());
|
SubtitleSimple.fromJson(json.castKeyDeep<String>());
|
||||||
|
@ -8,6 +8,8 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:lrc/lrc.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
// ignore: depend_on_referenced_packages, implementation_imports
|
// ignore: depend_on_referenced_packages, implementation_imports
|
||||||
|
@ -251,6 +251,7 @@ abstract class ServiceUtils {
|
|||||||
uri: subtitleUri,
|
uri: subtitleUri,
|
||||||
lyrics: lrcList,
|
lyrics: lrcList,
|
||||||
rating: rateSortedResults.first["points"] as int,
|
rating: rateSortedResults.first["points"] as int,
|
||||||
|
provider: "Rent An Adviser",
|
||||||
);
|
);
|
||||||
|
|
||||||
return subtitle;
|
return subtitle;
|
||||||
@ -307,7 +308,9 @@ abstract class ServiceUtils {
|
|||||||
case SortBy.duration:
|
case SortBy.duration:
|
||||||
return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0;
|
return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0;
|
||||||
case SortBy.artist:
|
case SortBy.artist:
|
||||||
return a.artists?.first.name?.compareTo(b.artists?.first.name ?? "") ?? 0;
|
return a.artists?.first.name
|
||||||
|
?.compareTo(b.artists?.first.name ?? "") ??
|
||||||
|
0;
|
||||||
case SortBy.album:
|
case SortBy.album:
|
||||||
return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0;
|
return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0;
|
||||||
default:
|
default:
|
||||||
|
@ -1455,6 +1455,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
lrc:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: lrc
|
||||||
|
sha256: "5100362b5c8e97f4d3f03ff87efeb40e73a6dd780eca2cbde9312e0d44b8e5ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
mailer:
|
mailer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -128,6 +128,7 @@ dependencies:
|
|||||||
shelf_router: ^1.1.4
|
shelf_router: ^1.1.4
|
||||||
shelf_web_socket: ^1.0.4
|
shelf_web_socket: ^1.0.4
|
||||||
web_socket_channel: ^2.4.4
|
web_socket_channel: ^2.4.4
|
||||||
|
lrc: ^1.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.3.2
|
build_runner: ^2.3.2
|
||||||
|
Loading…
Reference in New Issue
Block a user