feat: lyrics provider extensions, configurable lyrics cascade, and iOS method channel parity

Add lyrics_provider as a new extension type alongside metadata_provider and
download_provider. Extensions implementing fetchLyrics() are called before
built-in providers, giving user-installed extensions highest priority.

Built-in lyrics cascade is now configurable from Download Settings:
  - Reorderable provider list (LRCLIB, Musixmatch, Netease, Apple Music, QQ Music)
  - Per-provider options: Netease translation/romanization, Apple/QQ multi-person
    word-by-word speaker tags, Musixmatch language code
  - Provider order and options synced to Go backend on app start and on change

Go backend changes:
  - New lyrics_provider manifest type with validation (extension_manifest.go)
  - ExtensionProviderWrapper.FetchLyrics() with Goja JS bridge (extension_providers.go)
  - Configurable SetLyricsProviderOrder/GetLyricsProviderOrder cascade (lyrics.go)
  - LyricsFetchOptions struct for per-provider settings (lyrics.go)
  - Extracted tryLRCLIB() helper, randomized LRCLIB User-Agent (lyrics.go)
  - Refactored msToLRCTimestamp to separate msToLRCTimestampInline (lyrics.go)
  - New provider source files: lyrics_apple.go, lyrics_musixmatch.go,
    lyrics_netease.go, lyrics_qqmusic.go
  - JSON export functions for lyrics settings (exports.go)
  - hasLyricsProvider field in extension manager JSON output

Platform channels:
  - Android (MainActivity.kt): setLyricsProviders, getLyricsProviders,
    getAvailableLyricsProviders, setLyricsFetchOptions, getLyricsFetchOptions
  - iOS (AppDelegate.swift): same 5 method channel handlers for iOS parity

Flutter side:
  - Extension model: hasLyricsProvider field + Lyrics Provider capability badge
  - Settings model: lyricsProviders, lyricsIncludeTranslationNetease,
    lyricsIncludeRomanizationNetease, lyricsMultiPersonWordByWord,
    musixmatchLanguage fields with generated serialization
  - Settings provider: setters + _syncLyricsSettingsToBackend()
  - Download settings UI: provider picker, toggle switches, language picker
  - Platform bridge: lyrics provider/options methods

Docs: lyrics provider extension documentation in site/docs.html
CHANGELOG: updated with lyrics provider and search feature entries
This commit is contained in:
zarzet committed 2026-02-14 01:42:18 +07:00
1 parent 9b89625660
commit 30973a8e78
20 files changed
+2484 -92

No files matched your search

+38
View File
@@ -332,6 +332,44 @@ class PlatformBridge {
return jsonDecode(result as String) as Map<String, dynamic>;
}
// ==================== LYRICS PROVIDER SETTINGS ====================
/// Sets the lyrics provider order. Providers not in the list are disabled.
static Future<void> setLyricsProviders(List<String> providers) async {
final providersJSON = jsonEncode(providers);
await _channel.invokeMethod('setLyricsProviders', {
'providers_json': providersJSON,
});
}
/// Returns the current lyrics provider order.
static Future<List<String>> getLyricsProviders() async {
final result = await _channel.invokeMethod('getLyricsProviders');
final List<dynamic> decoded = jsonDecode(result as String) as List<dynamic>;
return decoded.cast<String>();
}
/// Returns metadata about all available lyrics providers.
static Future<List<Map<String, dynamic>>> getAvailableLyricsProviders() async {
final result = await _channel.invokeMethod('getAvailableLyricsProviders');
final List<dynamic> decoded = jsonDecode(result as String) as List<dynamic>;
return decoded.cast<Map<String, dynamic>>();
}
/// Sets advanced lyrics fetch options used by provider-specific integrations.
static Future<void> setLyricsFetchOptions(Map<String, dynamic> options) async {
final optionsJSON = jsonEncode(options);
await _channel.invokeMethod('setLyricsFetchOptions', {
'options_json': optionsJSON,
});
}
/// Returns current advanced lyrics fetch options.
static Future<Map<String, dynamic>> getLyricsFetchOptions() async {
final result = await _channel.invokeMethod('getLyricsFetchOptions');
return jsonDecode(result as String) as Map<String, dynamic>;
}
static Future<Map<String, dynamic>> reEnrichFile(
Map<String, dynamic> request,
) async {