spotube/lib/provider/scrobbler_provider.dart

130 lines
3.3 KiB
Dart

import 'dart:async';
import 'package:spotube/services/logger/logger.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scrobblenaut/scrobblenaut.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/utils/persisted_state_notifier.dart';
class ScrobblerState {
final String username;
final String passwordHash;
final Scrobblenaut scrobblenaut;
ScrobblerState({
required this.username,
required this.passwordHash,
required this.scrobblenaut,
});
Map<String, dynamic> toJson() {
return {
'username': username,
'passwordHash': passwordHash,
};
}
}
class ScrobblerNotifier extends PersistedStateNotifier<ScrobblerState?> {
final Scrobblenaut? scrobblenaut;
/// Directly scrobbling in set state of [ProxyPlaylistNotifier]
/// brings extra latency in playback
final StreamController<Track> _scrobbleController =
StreamController<Track>.broadcast();
ScrobblerNotifier()
: scrobblenaut = null,
super(null, "scrobbler", encrypted: true) {
_scrobbleController.stream.listen((track) async {
try {
await state?.scrobblenaut.track.scrobble(
artist: track.artists!.first.name!,
track: track.name!,
album: track.album!.name!,
chosenByUser: true,
duration: track.duration,
timestamp: DateTime.now().toUtc(),
trackNumber: track.trackNumber,
);
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
}
});
}
Future<void> login(
String username,
String password,
) async {
final lastFm = await LastFM.authenticate(
apiKey: Env.lastFmApiKey,
apiSecret: Env.lastFmApiSecret,
username: username,
password: password,
);
if (!lastFm.isAuth) throw Exception("Invalid credentials");
state = ScrobblerState(
username: username,
passwordHash: lastFm.passwordHash!,
scrobblenaut: Scrobblenaut(lastFM: lastFm),
);
}
Future<void> logout() async {
state = null;
}
void scrobble(Track track) {
_scrobbleController.add(track);
}
Future<void> love(Track track) async {
await state?.scrobblenaut.track.love(
artist: track.artists!.asString(),
track: track.name!,
);
}
Future<void> unlove(Track track) async {
await state?.scrobblenaut.track.unLove(
artist: track.artists!.asString(),
track: track.name!,
);
}
@override
FutureOr<ScrobblerState?> fromJson(Map<String, dynamic> json) async {
if (json.isEmpty) {
return null;
}
return ScrobblerState(
username: json['username'],
passwordHash: json['passwordHash'],
scrobblenaut: Scrobblenaut(
lastFM: await LastFM.authenticateWithPasswordHash(
apiKey: Env.lastFmApiKey,
apiSecret: Env.lastFmApiSecret,
username: json["username"],
passwordHash: json["passwordHash"],
),
),
);
}
@override
Map<String, dynamic> toJson() {
return state?.toJson() ?? {};
}
}
final scrobblerProvider =
StateNotifierProvider<ScrobblerNotifier, ScrobblerState?>(
(ref) => ScrobblerNotifier(),
);