mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-16 17:05:17 +00:00
200 lines
5.8 KiB
Dart
200 lines
5.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:drift/drift.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:lrc/lrc.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:spotube/models/database/database.dart';
|
|
import 'package:spotube/models/lyrics.dart';
|
|
import 'package:spotube/models/metadata/metadata.dart';
|
|
import 'package:spotube/provider/database/database.dart';
|
|
import 'package:spotube/services/dio/dio.dart';
|
|
import 'package:spotube/services/logger/logger.dart';
|
|
|
|
class SyncedLyricsNotifier
|
|
extends FamilyAsyncNotifier<SubtitleSimple, SpotubeTrackObject?> {
|
|
SpotubeTrackObject get _track => arg!;
|
|
|
|
// Future<SubtitleSimple> getSpotifyLyrics(String? token) async {
|
|
// final res = await globalDio.getUri(
|
|
// Uri.parse(
|
|
// "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token",
|
|
// ),
|
|
// options: Options(
|
|
// headers: {
|
|
// "User-Agent":
|
|
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
|
|
// "App-platform": "WebPlayer",
|
|
// "authorization": "Bearer $token"
|
|
// },
|
|
// responseType: ResponseType.json,
|
|
// validateStatus: (status) => true,
|
|
// ),
|
|
// );
|
|
|
|
// if (res.statusCode != 200) {
|
|
// return SubtitleSimple(
|
|
// lyrics: [],
|
|
// name: _track.name!,
|
|
// uri: res.realUri,
|
|
// rating: 0,
|
|
// provider: "Spotify",
|
|
// );
|
|
// }
|
|
// final linesRaw =
|
|
// Map.castFrom<dynamic, dynamic, String, dynamic>(res.data)["lyrics"]
|
|
// ?["lines"] as List?;
|
|
|
|
// final lines = linesRaw?.map((line) {
|
|
// return LyricSlice(
|
|
// time: Duration(milliseconds: int.parse(line["startTimeMs"])),
|
|
// text: line["words"] as String,
|
|
// );
|
|
// }).toList() ??
|
|
// [];
|
|
|
|
// return SubtitleSimple(
|
|
// lyrics: lines,
|
|
// name: _track.name!,
|
|
// uri: res.realUri,
|
|
// 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 globalDio.getUri(
|
|
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.durationMs / 1000).toInt().toString(),
|
|
},
|
|
),
|
|
options: Options(
|
|
headers: {
|
|
"User-Agent":
|
|
"Spotube v${packageInfo.version} (https://github.com/KRTirtho/spotube)"
|
|
},
|
|
responseType: ResponseType.json,
|
|
),
|
|
);
|
|
|
|
if (res.statusCode != 200) {
|
|
return SubtitleSimple(
|
|
lyrics: [],
|
|
name: _track.name,
|
|
uri: res.realUri,
|
|
rating: 0,
|
|
provider: "LRCLib",
|
|
);
|
|
}
|
|
|
|
final json = res.data 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.realUri,
|
|
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.realUri,
|
|
rating: 0,
|
|
provider: "LRCLib",
|
|
);
|
|
}
|
|
|
|
@override
|
|
FutureOr<SubtitleSimple> build(track) async {
|
|
try {
|
|
final database = ref.watch(databaseProvider);
|
|
|
|
if (track == null) {
|
|
throw "No track currently";
|
|
}
|
|
|
|
final cachedLyrics = await (database.select(database.lyricsTable)
|
|
..where((tbl) => tbl.trackId.equals(track.id)))
|
|
.map((row) => row.data)
|
|
.getSingleOrNull();
|
|
|
|
SubtitleSimple? lyrics = cachedLyrics;
|
|
|
|
if (lyrics == null ||
|
|
lyrics.lyrics.isEmpty ||
|
|
lyrics.lyrics.length <= 5) {
|
|
lyrics = await getLRCLibLyrics();
|
|
}
|
|
|
|
if (lyrics.lyrics.isEmpty) {
|
|
throw Exception("Unable to find lyrics");
|
|
}
|
|
|
|
if (cachedLyrics == null || cachedLyrics.lyrics.isEmpty) {
|
|
await database.into(database.lyricsTable).insert(
|
|
LyricsTableCompanion.insert(
|
|
trackId: track.id,
|
|
data: lyrics,
|
|
),
|
|
mode: InsertMode.replace,
|
|
);
|
|
}
|
|
|
|
return lyrics;
|
|
} catch (e, stackTrace) {
|
|
AppLogger.reportError(e, stackTrace);
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
final syncedLyricsDelayProvider = StateProvider<int>((ref) => 0);
|
|
|
|
final syncedLyricsProvider = AsyncNotifierProviderFamily<SyncedLyricsNotifier,
|
|
SubtitleSimple, SpotubeTrackObject?>(
|
|
() => SyncedLyricsNotifier(),
|
|
);
|
|
|
|
final syncedLyricsMapProvider =
|
|
FutureProvider.family((ref, SpotubeTrackObject? track) async {
|
|
final syncedLyrics = await ref.watch(syncedLyricsProvider(track).future);
|
|
|
|
final isStaticLyrics =
|
|
syncedLyrics.lyrics.every((l) => l.time == Duration.zero);
|
|
|
|
final lyricsMap = syncedLyrics.lyrics
|
|
.map((lyric) => {lyric.time.inSeconds: lyric.text})
|
|
.reduce((accumulator, lyricSlice) => {...accumulator, ...lyricSlice});
|
|
|
|
return (static: isStaticLyrics, lyricsMap: lyricsMap);
|
|
});
|