diff --git a/lib/logic/lrc_providers.dart b/lib/logic/lrc_providers.dart index 7701073..e91c58b 100644 --- a/lib/logic/lrc_providers.dart +++ b/lib/logic/lrc_providers.dart @@ -226,6 +226,47 @@ class NetEaseProvider extends LrcProvider { } } +/// Lrclib provider +class LrclibProvider extends LrcProvider { + static const String rootUrl = "https://lrclib.net"; + static const String apiEndpoint = "$rootUrl/api"; + static const String searchEndpoint = "$apiEndpoint/search"; + static const String lrcEndpoint = "$apiEndpoint/get/"; + + @override + String get name => 'Lrclib'; + + Future getLrcById(String trackId) async { + final url = lrcEndpoint + trackId; + final response = await session.get(url); + if (response.statusCode != 200) return null; + final track = response.data; + final synced = track['syncedLyrics']; + final plain = track['plainLyrics']; + return Lyrics(synced: synced, plain: plain); + } + + @override + Future getLrc(String searchTerm) async { + final response = await session.get( + searchEndpoint, + queryParameters: {'q': searchTerm}, + ); + if (response.statusCode != 200) return null; + final tracks = response.data as List; + if (tracks.isEmpty) return null; + // Find first track with syncedLyrics not empty + for (final track in tracks) { + final synced = track['syncedLyrics']; + if (synced != null && synced.trim().isNotEmpty) { + final id = track['id'].toString(); + return await getLrcById(id); + } + } + return null; + } +} + // Utility function String formatTime(dynamic time) { final seconds = time.toInt(); diff --git a/lib/providers/lrc_fetcher_provider.dart b/lib/providers/lrc_fetcher_provider.dart index 3ed8812..34c93bc 100644 --- a/lib/providers/lrc_fetcher_provider.dart +++ b/lib/providers/lrc_fetcher_provider.dart @@ -120,3 +120,4 @@ class LyricsFetcherState { // Providers for each LRC provider final musixmatchProvider = Provider((ref) => MusixmatchProvider()); final neteaseProvider = Provider((ref) => NetEaseProvider()); +final lrclibProvider = Provider((ref) => LrclibProvider()); diff --git a/lib/ui/screens/player_screen.dart b/lib/ui/screens/player_screen.dart index 0fa84d8..25476bc 100644 --- a/lib/ui/screens/player_screen.dart +++ b/lib/ui/screens/player_screen.dart @@ -516,6 +516,7 @@ class _PlayerLyrics extends HookConsumerWidget { final lyricsFetcher = ref.watch(lyricsFetcherProvider); final musixmatchProviderInstance = ref.watch(musixmatchProvider); final neteaseProviderInstance = ref.watch(neteaseProvider); + final lrclibProviderInstance = ref.watch(lrclibProvider); // Simulate async behavior for compatibility if (currentTrack == null) { @@ -543,6 +544,7 @@ class _PlayerLyrics extends HookConsumerWidget { lyricsFetcher, musixmatchProviderInstance, neteaseProviderInstance, + lrclibProviderInstance, context, ); } @@ -555,6 +557,7 @@ class _PlayerLyrics extends HookConsumerWidget { dynamic metadataObj, musixmatchProvider, neteaseProvider, + lrclibProvider, ) { showDialog( context: context, @@ -565,6 +568,7 @@ class _PlayerLyrics extends HookConsumerWidget { ref: ref, musixmatchProvider: musixmatchProvider, neteaseProvider: neteaseProvider, + lrclibProvider: lrclibProvider, ), ); } @@ -576,6 +580,7 @@ class _PlayerLyrics extends HookConsumerWidget { dynamic lyricsFetcher, dynamic musixmatchProviderInstance, dynamic neteaseProviderInstance, + dynamic lrclibProviderInstance, BuildContext context, ) { // Get lyrics mode setting @@ -614,6 +619,7 @@ class _PlayerLyrics extends HookConsumerWidget { metadataAsync.value, musixmatchProviderInstance, neteaseProviderInstance, + lrclibProviderInstance, ), ), ], @@ -701,6 +707,7 @@ class _FetchLyricsDialog extends StatelessWidget { final WidgetRef ref; final LrcProvider musixmatchProvider; final LrcProvider neteaseProvider; + final LrcProvider lrclibProvider; const _FetchLyricsDialog({ required this.track, @@ -709,6 +716,7 @@ class _FetchLyricsDialog extends StatelessWidget { required this.ref, required this.musixmatchProvider, required this.neteaseProvider, + required this.lrclibProvider, }); @override @@ -780,6 +788,25 @@ class _FetchLyricsDialog extends StatelessWidget { ); }, ), + ListTile( + dense: true, + leading: const Icon(Symbols.library_books), + title: const Text('Lrclib'), + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + onTap: () async { + Navigator.of(context).pop(); + await ref + .read(lyricsFetcherProvider.notifier) + .fetchLyricsForTrack( + trackId: track.id, + searchTerm: searchTerm, + provider: lrclibProvider, + trackPath: trackPath, + ); + }, + ), ListTile( dense: true, leading: const Icon(Symbols.file_upload), @@ -857,6 +884,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { ); final musixmatchProviderInstance = ref.watch(musixmatchProvider); final neteaseProviderInstance = ref.watch(neteaseProvider); + final lrclibProviderInstance = ref.watch(lrclibProvider); // Don't show the button if there's no current track if (currentTrack == null) { @@ -874,6 +902,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { metadataAsync, musixmatchProviderInstance, neteaseProviderInstance, + lrclibProviderInstance, ), padding: EdgeInsets.zero, ); @@ -887,6 +916,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { dynamic metadataObj, musixmatchProvider, neteaseProvider, + lrclibProvider, ) { showDialog( context: context, @@ -897,6 +927,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { ref: ref, musixmatchProvider: musixmatchProvider, neteaseProvider: neteaseProvider, + lrclibProvider: lrclibProvider, ), ); } @@ -908,6 +939,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { AsyncValue metadataAsync, musixmatchProvider, neteaseProvider, + lrclibProvider, ) { // Convert CurrentTrackData to db.Track for compatibility final track = db.Track( @@ -952,6 +984,7 @@ class _LyricsAdjustButton extends HookConsumerWidget { metadata, musixmatchProvider, neteaseProvider, + lrclibProvider, ); }, ), diff --git a/lib/ui/shell.dart b/lib/ui/shell.dart index e14857c..7bd416e 100644 --- a/lib/ui/shell.dart +++ b/lib/ui/shell.dart @@ -83,7 +83,9 @@ class Shell extends HookConsumerWidget { void saveWindowSize() { windowManager.getBounds().then((bounds) { final settingsNotifier = ref.read(settingsProvider.notifier); - settingsNotifier.setWindowSize(bounds.size); + Future(() { + settingsNotifier.setWindowSize(bounds.size); + }); }); }