0.6.2 - Spotify albums/tracks, album art gradient, languages, minor fixes

This commit is contained in:
exttex 2020-11-09 22:05:47 +01:00
parent f877aa9d7b
commit e9d97986b5
17 changed files with 497 additions and 221 deletions

View file

@ -1,4 +1,5 @@
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/spotify.dart';
import 'package:freezer/settings.dart';
import 'package:http/http.dart' as http;
@ -114,6 +115,23 @@ class DeezerAPI {
String newUrl = response.headers['location'];
return parseLink(newUrl);
}
//Spotify
if (uri.host == 'open.spotify.com') {
if (uri.pathSegments.length < 2) return null;
String spotifyUri = 'spotify:' + uri.pathSegments.sublist(0, 2).join(':');
try {
//Tracks
if (uri.pathSegments[0] == 'track') {
String id = await spotify.convertTrack(spotifyUri);
return DeezerLinkResponse(type: DeezerLinkType.TRACK, id: id);
}
//Albums
if (uri.pathSegments[0] == 'album') {
String id = await spotify.convertAlbum(spotifyUri);
return DeezerLinkResponse(type: DeezerLinkType.ALBUM, id: id);
}
} catch (e) {}
}
}
//Search

View file

@ -49,8 +49,8 @@ Map<String, dynamic> _$TrackToJson(Track instance) => <String, dynamic>{
'favorite': instance.favorite,
'diskNumber': instance.diskNumber,
'explicit': instance.explicit,
'playbackDetails': instance.playbackDetails,
'favoriteDate': instance.favoriteDate,
'playbackDetails': instance.playbackDetails,
};
Album _$AlbumFromJson(Map<String, dynamic> json) {
@ -73,7 +73,7 @@ Album _$AlbumFromJson(Map<String, dynamic> json) {
library: json['library'] as bool,
type: _$enumDecodeNullable(_$AlbumTypeEnumMap, json['type']),
releaseDate: json['releaseDate'] as String,
favoriteDate: json['favoriteDate'] as String
favoriteDate: json['favoriteDate'] as String,
);
}
@ -88,7 +88,7 @@ Map<String, dynamic> _$AlbumToJson(Album instance) => <String, dynamic>{
'library': instance.library,
'type': _$AlbumTypeEnumMap[instance.type],
'releaseDate': instance.releaseDate,
'favoriteDate': instance.favoriteDate
'favoriteDate': instance.favoriteDate,
};
T _$enumDecode<T>(
@ -149,6 +149,7 @@ Artist _$ArtistFromJson(Map<String, dynamic> json) {
offline: json['offline'] as bool,
library: json['library'] as bool,
radio: json['radio'] as bool,
favoriteDate: json['favoriteDate'] as String,
);
}
@ -163,6 +164,7 @@ Map<String, dynamic> _$ArtistToJson(Artist instance) => <String, dynamic>{
'offline': instance.offline,
'library': instance.library,
'radio': instance.radio,
'favoriteDate': instance.favoriteDate,
};
Playlist _$PlaylistFromJson(Map<String, dynamic> json) {

View file

@ -1,5 +1,3 @@
import 'dart:math';
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:fluttertoast/fluttertoast.dart';
@ -11,6 +9,7 @@ import 'package:connectivity/connectivity.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:scrobblenaut/scrobblenaut.dart';
import 'definitions.dart';
import '../settings.dart';
@ -28,6 +27,8 @@ class PlayerHelper {
StreamSubscription _playbackStateStreamSubscription;
QueueSource queueSource;
LoopMode repeatType = LoopMode.off;
Timer _timer;
Scrobblenaut scrobblenaut;
//Find queue index by id
int get queueIndex => AudioService.queue == null ? 0 : AudioService.queue.indexWhere((mi) => mi.id == AudioService.currentMediaItem?.id??'Random string so it returns -1');
@ -63,18 +64,6 @@ class PlayerHelper {
await androidAuto.playItem(event['id']);
}
});
_playbackStateStreamSubscription = AudioService.playbackStateStream.listen((event) {
//Log song (if allowed)
if (event == null) return;
if (event.processingState == AudioProcessingState.ready && event.playing) {
if (settings.logListen) {
//Check if duplicate
if (cache.loggedTrackId == AudioService.currentMediaItem.id) return;
cache.loggedTrackId = AudioService.currentMediaItem.id;
deezerAPI.logListen(AudioService.currentMediaItem.id);
}
}
});
_mediaItemSubscription = AudioService.currentMediaItemStream.listen((event) {
if (event == null) return;
//Save queue
@ -86,6 +75,31 @@ class PlayerHelper {
cache.save();
});
//Logging listen timer
_timer = Timer.periodic(Duration(seconds: 2), (timer) async {
if (AudioService.currentMediaItem == null || !AudioService.playbackState.playing) return;
if (AudioService.playbackState.currentPosition.inSeconds > (AudioService.currentMediaItem.duration.inSeconds * 0.75)) {
if (cache.loggedTrackId == AudioService.currentMediaItem.id) return;
cache.loggedTrackId = AudioService.currentMediaItem.id;
await cache.save();
//Log to Deezer
if (settings.logListen) {
deezerAPI.logListen(AudioService.currentMediaItem.id);
}
//LastFM
if (scrobblenaut != null) {
await scrobblenaut.track.scrobble(
track: AudioService.currentMediaItem.title,
artist: AudioService.currentMediaItem.artist,
album: AudioService.currentMediaItem.album,
);
}
}
});
//Start audio_service
await startService();
}
@ -108,6 +122,19 @@ class PlayerHelper {
);
}
Future authorizeLastFM() async {
if (settings.lastFMUsername == null || settings.lastFMPassword == null) return;
try {
LastFM lastFM = await LastFM.authenticateWithPasswordHash(
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: settings.lastFMUsername,
passwordHash: settings.lastFMPassword
);
scrobblenaut = Scrobblenaut(lastFM: lastFM);
} catch (e) {}
}
Future toggleShuffle() async {
await AudioService.customAction('shuffle');
}

View file

@ -38,7 +38,13 @@ class SpotifyAPI {
//Parse
dom.Document document = parse(response.body);
dom.Element element = document.getElementById('resource');
return jsonDecode(element.innerHtml);
//Some are URL encoded
try {
return jsonDecode(element.innerHtml);
} catch (e) {
return jsonDecode(Uri.decodeComponent(element.innerHtml));
}
}
Future<SpotifyPlaylist> playlist(String uri) async {
@ -50,6 +56,21 @@ class SpotifyAPI {
return playlist;
}
//Get Deezer track ID from Spotify URI
Future<String> convertTrack(String uri) async {
Map data = await getEmbedData(getEmbedUrl(uri));
SpotifyTrack track = SpotifyTrack.fromJson(data);
Map deezer = await deezerAPI.callPublicApi('track/isrc:' + track.isrc);
return deezer['id'].toString();
}
//Get Deezer album ID by UPC
Future<String> convertAlbum(String uri) async {
Map data = await getEmbedData(getEmbedUrl(uri));
SpotifyAlbum album = SpotifyAlbum.fromJson(data);
Map deezer = await deezerAPI.callPublicApi('album/upc:' + album.upc);
return deezer['id'].toString();
}
Future convertPlaylist(SpotifyPlaylist playlist, {bool downloadOnly = false, BuildContext context, AudioQuality quality}) async {
doneImporting = false;
@ -132,6 +153,17 @@ class SpotifyPlaylist {
);
}
class SpotifyAlbum {
String upc;
SpotifyAlbum({this.upc});
//JSON
factory SpotifyAlbum.fromJson(Map json) => SpotifyAlbum(
upc: json['external_ids']['upc']
);
}
enum TrackImportState {
NONE,
ERROR,