mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: add lyrics provider
This commit is contained in:
parent
54b32459a8
commit
1d580e8b6a
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -2,6 +2,7 @@
|
|||||||
"cmake.configureOnOpen": false,
|
"cmake.configureOnOpen": false,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"acousticness",
|
"acousticness",
|
||||||
|
"Buildless",
|
||||||
"danceability",
|
"danceability",
|
||||||
"instrumentalness",
|
"instrumentalness",
|
||||||
"Mpris",
|
"Mpris",
|
||||||
|
|||||||
@ -25,6 +25,7 @@ linter:
|
|||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
file_names: false
|
file_names: false
|
||||||
|
avoid_renaming_method_parameters: false
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|||||||
56
lib/provider/spotify/lyrics/synced.dart
Normal file
56
lib/provider/spotify/lyrics/synced.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
part of '../spotify.dart';
|
||||||
|
|
||||||
|
class SyncedLyricsNotifier extends FamilyAsyncNotifier<SubtitleSimple, Track?>
|
||||||
|
with Persistence<SubtitleSimple> {
|
||||||
|
SyncedLyricsNotifier() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<SubtitleSimple> build(track) async {
|
||||||
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
if (track == null) {
|
||||||
|
throw "No track currently";
|
||||||
|
}
|
||||||
|
final token = await spotify.getCredentials();
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://spclient.wg.spotify.com/color-lyrics/v2/track/${track.id}?format=json&market=from_token",
|
||||||
|
),
|
||||||
|
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.accessToken}"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw Exception("Unable to find lyrics");
|
||||||
|
}
|
||||||
|
final linesRaw = Map.castFrom<dynamic, dynamic, String, dynamic>(
|
||||||
|
jsonDecode(res.body),
|
||||||
|
)["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.request!.url,
|
||||||
|
rating: 100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<SubtitleSimple> fromJson(Map<String, dynamic> json) =>
|
||||||
|
SubtitleSimple.fromJson(json.castKeyDeep<String>());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson(SubtitleSimple data) => data.toJson();
|
||||||
|
}
|
||||||
@ -1,14 +1,22 @@
|
|||||||
library spotify;
|
library spotify;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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
|
||||||
import 'package:riverpod/src/async_notifier.dart';
|
import 'package:riverpod/src/async_notifier.dart';
|
||||||
|
import 'package:spotube/extensions/map.dart';
|
||||||
|
import 'package:spotube/models/lyrics.dart';
|
||||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'album/favorite.dart';
|
part 'album/favorite.dart';
|
||||||
part 'album/tracks.dart';
|
part 'album/tracks.dart';
|
||||||
@ -25,6 +33,9 @@ part 'category/genres.dart';
|
|||||||
part 'category/categories.dart';
|
part 'category/categories.dart';
|
||||||
part 'category/playlists.dart';
|
part 'category/playlists.dart';
|
||||||
|
|
||||||
|
part 'lyrics/synced.dart';
|
||||||
|
|
||||||
part 'utils/mixin.dart';
|
part 'utils/mixin.dart';
|
||||||
part 'utils/state.dart';
|
part 'utils/state.dart';
|
||||||
part 'utils/provider.dart';
|
part 'utils/provider.dart';
|
||||||
|
part 'utils/persistence.dart';
|
||||||
|
|||||||
40
lib/provider/spotify/utils/persistence.dart
Normal file
40
lib/provider/spotify/utils/persistence.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
part of '../spotify.dart';
|
||||||
|
|
||||||
|
// ignore: invalid_use_of_internal_member
|
||||||
|
mixin Persistence<T> on BuildlessAsyncNotifier<T> {
|
||||||
|
LazyBox get store => Hive.lazyBox("spotube_cache");
|
||||||
|
|
||||||
|
FutureOr<T> fromJson(Map<String, dynamic> json);
|
||||||
|
Map<String, dynamic> toJson(T data);
|
||||||
|
|
||||||
|
FutureOr<void> onInit() {}
|
||||||
|
|
||||||
|
Future<void> load() async {
|
||||||
|
final json = await store.get(runtimeType.toString());
|
||||||
|
if (json != null ||
|
||||||
|
(json is Map && json.entries.isNotEmpty) ||
|
||||||
|
(json is List && json.isNotEmpty)) {
|
||||||
|
state = AsyncData(
|
||||||
|
await fromJson(
|
||||||
|
PersistedStateNotifier.castNestedJson(json),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
await store.put(
|
||||||
|
runtimeType.toString(),
|
||||||
|
state.value == null ? null : toJson(state.value as T),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set state(AsyncValue<T> value) {
|
||||||
|
if (state == value) return;
|
||||||
|
super.state = value;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -126,7 +126,7 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> castNestedJson(Map map) {
|
static Map<String, dynamic> castNestedJson(Map map) {
|
||||||
return Map.castFrom<dynamic, dynamic, String, dynamic>(
|
return Map.castFrom<dynamic, dynamic, String, dynamic>(
|
||||||
map.map((key, value) {
|
map.map((key, value) {
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user