mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
stop streaming iterables
add gonic to test servers setup gather artist image URLs on allArtists to remove weird Future<Uri> interface for artist images move source options around
This commit is contained in:
parent
3408a3988e
commit
c900c9750a
21
compose.yaml
21
compose.yaml
@ -8,7 +8,7 @@ services:
|
||||
navidrome:
|
||||
image: deluan/navidrome:latest
|
||||
ports:
|
||||
- "4533:4533"
|
||||
- 4533:4533
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ND_LOGLEVEL: debug
|
||||
@ -16,7 +16,26 @@ services:
|
||||
- navidrome-data:/data
|
||||
- music:/music:ro
|
||||
|
||||
gonic:
|
||||
image: sentriz/gonic:latest
|
||||
environment:
|
||||
- TZ
|
||||
- GONIC_SCAN_AT_START_ENABLED=true
|
||||
- GONIC_SCAN_WATCHER_ENABLED=true
|
||||
ports:
|
||||
- 4747:80
|
||||
volumes:
|
||||
- gonic-data:/data
|
||||
- music:/music:ro
|
||||
- gonic-podcasts:/podcasts
|
||||
- gonic-playlists:/playlists
|
||||
- gonic-cache:/cache
|
||||
|
||||
volumes:
|
||||
deno-dir:
|
||||
music:
|
||||
navidrome-data:
|
||||
gonic-data:
|
||||
gonic-podcasts:
|
||||
gonic-playlists:
|
||||
gonic-cache:
|
||||
|
||||
@ -19,6 +19,8 @@ abstract class SourceItem with _$SourceItem {
|
||||
required String id,
|
||||
required String name,
|
||||
DateTime? starred,
|
||||
Uri? smallImage,
|
||||
Uri? largeImage,
|
||||
}) = SourceArtist;
|
||||
|
||||
@With<Starred>()
|
||||
|
||||
@ -159,10 +159,10 @@ return song(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String id, String name, DateTime? starred)? artist,TResult Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank)? album,TResult Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public)? playlist,TResult Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt)? song,required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String id, String name, DateTime? starred, Uri? smallImage, Uri? largeImage)? artist,TResult Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank)? album,TResult Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public)? playlist,TResult Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt)? song,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SourceArtist() when artist != null:
|
||||
return artist(_that.id,_that.name,_that.starred);case SourceAlbum() when album != null:
|
||||
return artist(_that.id,_that.name,_that.starred,_that.smallImage,_that.largeImage);case SourceAlbum() when album != null:
|
||||
return album(_that.id,_that.artistId,_that.name,_that.albumArtist,_that.created,_that.coverArt,_that.year,_that.starred,_that.genre,_that.frequentRank,_that.recentRank);case SourcePlaylist() when playlist != null:
|
||||
return playlist(_that.id,_that.name,_that.comment,_that.created,_that.changed,_that.coverArt,_that.owner,_that.public);case SourceSong() when song != null:
|
||||
return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that.album,_that.duration,_that.track,_that.disc,_that.starred,_that.genre,_that.coverArt);case _:
|
||||
@ -183,10 +183,10 @@ return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String id, String name, DateTime? starred) artist,required TResult Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank) album,required TResult Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public) playlist,required TResult Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt) song,}) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String id, String name, DateTime? starred, Uri? smallImage, Uri? largeImage) artist,required TResult Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank) album,required TResult Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public) playlist,required TResult Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt) song,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SourceArtist():
|
||||
return artist(_that.id,_that.name,_that.starred);case SourceAlbum():
|
||||
return artist(_that.id,_that.name,_that.starred,_that.smallImage,_that.largeImage);case SourceAlbum():
|
||||
return album(_that.id,_that.artistId,_that.name,_that.albumArtist,_that.created,_that.coverArt,_that.year,_that.starred,_that.genre,_that.frequentRank,_that.recentRank);case SourcePlaylist():
|
||||
return playlist(_that.id,_that.name,_that.comment,_that.created,_that.changed,_that.coverArt,_that.owner,_that.public);case SourceSong():
|
||||
return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that.album,_that.duration,_that.track,_that.disc,_that.starred,_that.genre,_that.coverArt);case _:
|
||||
@ -206,10 +206,10 @@ return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String id, String name, DateTime? starred)? artist,TResult? Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank)? album,TResult? Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public)? playlist,TResult? Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt)? song,}) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String id, String name, DateTime? starred, Uri? smallImage, Uri? largeImage)? artist,TResult? Function( String id, String? artistId, String name, String? albumArtist, DateTime created, String? coverArt, int? year, DateTime? starred, String? genre, int? frequentRank, int? recentRank)? album,TResult? Function( String id, String name, String? comment, DateTime created, DateTime changed, String? coverArt, String? owner, bool? public)? playlist,TResult? Function( String id, String? albumId, String? artistId, String title, String? artist, String? album, Duration? duration, int? track, int? disc, DateTime? starred, String? genre, String? coverArt)? song,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SourceArtist() when artist != null:
|
||||
return artist(_that.id,_that.name,_that.starred);case SourceAlbum() when album != null:
|
||||
return artist(_that.id,_that.name,_that.starred,_that.smallImage,_that.largeImage);case SourceAlbum() when album != null:
|
||||
return album(_that.id,_that.artistId,_that.name,_that.albumArtist,_that.created,_that.coverArt,_that.year,_that.starred,_that.genre,_that.frequentRank,_that.recentRank);case SourcePlaylist() when playlist != null:
|
||||
return playlist(_that.id,_that.name,_that.comment,_that.created,_that.changed,_that.coverArt,_that.owner,_that.public);case SourceSong() when song != null:
|
||||
return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that.album,_that.duration,_that.track,_that.disc,_that.starred,_that.genre,_that.coverArt);case _:
|
||||
@ -224,12 +224,14 @@ return song(_that.id,_that.albumId,_that.artistId,_that.title,_that.artist,_that
|
||||
|
||||
|
||||
class SourceArtist with Starred implements SourceItem {
|
||||
const SourceArtist({required this.id, required this.name, this.starred});
|
||||
const SourceArtist({required this.id, required this.name, this.starred, this.smallImage, this.largeImage});
|
||||
|
||||
|
||||
@override final String id;
|
||||
final String name;
|
||||
final DateTime? starred;
|
||||
final Uri? smallImage;
|
||||
final Uri? largeImage;
|
||||
|
||||
/// Create a copy of SourceItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -241,16 +243,16 @@ $SourceArtistCopyWith<SourceArtist> get copyWith => _$SourceArtistCopyWithImpl<S
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SourceArtist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SourceArtist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,starred);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,starred,smallImage,largeImage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SourceItem.artist(id: $id, name: $name, starred: $starred)';
|
||||
return 'SourceItem.artist(id: $id, name: $name, starred: $starred, smallImage: $smallImage, largeImage: $largeImage)';
|
||||
}
|
||||
|
||||
|
||||
@ -261,7 +263,7 @@ abstract mixin class $SourceArtistCopyWith<$Res> implements $SourceItemCopyWith<
|
||||
factory $SourceArtistCopyWith(SourceArtist value, $Res Function(SourceArtist) _then) = _$SourceArtistCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String name, DateTime? starred
|
||||
String id, String name, DateTime? starred, Uri? smallImage, Uri? largeImage
|
||||
});
|
||||
|
||||
|
||||
@ -278,12 +280,14 @@ class _$SourceArtistCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SourceItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? starred = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? starred = freezed,Object? smallImage = freezed,Object? largeImage = freezed,}) {
|
||||
return _then(SourceArtist(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,starred: freezed == starred ? _self.starred : starred // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
as DateTime?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable
|
||||
as Uri?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable
|
||||
as Uri?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -3,15 +3,14 @@ import 'models.dart';
|
||||
abstract class MusicSource {
|
||||
Future<void> ping();
|
||||
|
||||
Stream<Iterable<SourceAlbum>> allAlbums();
|
||||
Stream<Iterable<SourceArtist>> allArtists();
|
||||
Stream<Iterable<SourcePlaylist>> allPlaylists();
|
||||
Stream<Iterable<SourceSong>> allSongs();
|
||||
Stream<Iterable<SourcePlaylistSong>> allPlaylistSongs();
|
||||
Stream<SourceAlbum> allAlbums();
|
||||
Stream<SourceArtist> allArtists();
|
||||
Stream<SourcePlaylist> allPlaylists();
|
||||
Stream<SourceSong> allSongs();
|
||||
Stream<SourcePlaylistSong> allPlaylistSongs();
|
||||
|
||||
Uri streamUri(String songId);
|
||||
Uri downloadUri(String songId);
|
||||
|
||||
Uri coverArtUri(String coverArtId, {bool thumbnail = true});
|
||||
Future<Uri?> artistArtUri(String artistId, {bool thumbnail = true});
|
||||
}
|
||||
|
||||
@ -2,10 +2,12 @@ import 'package:xml/xml.dart';
|
||||
|
||||
import '../models.dart';
|
||||
|
||||
SourceArtist mapArtist(XmlElement e) => SourceArtist(
|
||||
SourceArtist mapArtist(XmlElement e, XmlElement? info) => SourceArtist(
|
||||
id: e.getAttribute('id')!,
|
||||
name: e.getAttribute('name')!,
|
||||
starred: DateTime.tryParse(e.getAttribute('starred').toString()),
|
||||
smallImage: Uri.tryParse(info?.getElement('smallImageUrl')?.innerText ?? ''),
|
||||
largeImage: Uri.tryParse(info?.getElement('largeImageUrl')?.innerText ?? ''),
|
||||
);
|
||||
|
||||
SourceAlbum mapAlbum(
|
||||
|
||||
@ -9,17 +9,17 @@ import 'client.dart';
|
||||
import 'mapping.dart';
|
||||
|
||||
class SubsonicSource implements MusicSource {
|
||||
SubsonicSource({
|
||||
required this.client,
|
||||
required this.maxBitrate,
|
||||
this.streamFormat,
|
||||
SubsonicSource(
|
||||
this.client, {
|
||||
this.maxConnections = 10,
|
||||
this.connectionPoolTimeout = const Duration(seconds: 60),
|
||||
});
|
||||
|
||||
final SubsonicClient client;
|
||||
final int maxBitrate;
|
||||
final String? streamFormat;
|
||||
final int maxConnections;
|
||||
final Duration connectionPoolTimeout;
|
||||
|
||||
final _pool = Pool(10, timeout: const Duration(seconds: 60));
|
||||
late final _pool = Pool(maxConnections, timeout: connectionPoolTimeout);
|
||||
|
||||
bool? _featureEmptyQuerySearch;
|
||||
Future<bool> get supportsFastSongSync async {
|
||||
@ -41,23 +41,31 @@ class SubsonicSource implements MusicSource {
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Iterable<SourceArtist>> allArtists() async* {
|
||||
final res = await client.get('getArtists');
|
||||
Stream<SourceArtist> allArtists() async* {
|
||||
final getArtistsRes = await _pool.withResource(
|
||||
() => client.get('getArtists'),
|
||||
);
|
||||
|
||||
for (var artists in res.xml.findAllElements('artist').slices(200)) {
|
||||
yield artists.map(mapArtist);
|
||||
}
|
||||
yield* _pool.forEach(getArtistsRes.xml.findAllElements('artist'), (
|
||||
artist,
|
||||
) async {
|
||||
final res = await client.get('getArtistInfo2', {
|
||||
'id': artist.getAttribute('id')!,
|
||||
});
|
||||
|
||||
return mapArtist(artist, res.xml.getElement('artistInfo2'));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Iterable<SourceAlbum>> allAlbums() async* {
|
||||
Stream<SourceAlbum> allAlbums() async* {
|
||||
final extras = await Future.wait([
|
||||
_albumList(
|
||||
'frequent',
|
||||
).flatten().map((element) => element.getAttribute('id')!).toList(),
|
||||
).flatten().map((e) => e.getAttribute('id')!).toList(),
|
||||
_albumList(
|
||||
'recent',
|
||||
).flatten().map((element) => element.getAttribute('id')!).toList(),
|
||||
).flatten().map((e) => e.getAttribute('id')!).toList(),
|
||||
]);
|
||||
|
||||
final frequentlyPlayed = {
|
||||
@ -67,65 +75,73 @@ class SubsonicSource implements MusicSource {
|
||||
for (var i = 0; i < extras[1].length; i++) extras[1][i]: i,
|
||||
};
|
||||
|
||||
await for (var albums in _albumList('newest')) {
|
||||
yield albums.map(
|
||||
(e) => mapAlbum(
|
||||
e,
|
||||
frequentRank: frequentlyPlayed[e.getAttribute('id')!],
|
||||
recentRank: recentlyPlayed[e.getAttribute('id')!],
|
||||
await for (final albums in _albumList('newest')) {
|
||||
yield* Stream.fromIterable(
|
||||
albums.map(
|
||||
(album) => mapAlbum(
|
||||
album,
|
||||
frequentRank: frequentlyPlayed[album.getAttribute('id')!],
|
||||
recentRank: recentlyPlayed[album.getAttribute('id')!],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Iterable<SourcePlaylist>> allPlaylists() async* {
|
||||
final res = await client.get('getPlaylists');
|
||||
Stream<SourcePlaylist> allPlaylists() async* {
|
||||
final res = await _pool.withResource(() => client.get('getPlaylists'));
|
||||
|
||||
for (var playlists in res.xml.findAllElements('playlist').slices(200)) {
|
||||
yield playlists.map(mapPlaylist);
|
||||
}
|
||||
yield* Stream.fromIterable(
|
||||
res.xml.findAllElements('playlist').map(mapPlaylist),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Iterable<SourcePlaylistSong>> allPlaylistSongs() async* {
|
||||
final allPlaylists = await client.get('getPlaylists');
|
||||
Stream<SourcePlaylistSong> allPlaylistSongs() async* {
|
||||
final allPlaylists = await _pool.withResource(
|
||||
() => client.get('getPlaylists'),
|
||||
);
|
||||
|
||||
yield* _pool.forEach(allPlaylists.xml.findAllElements('playlist'), (
|
||||
playlist,
|
||||
) async {
|
||||
final id = playlist.getAttribute('id')!;
|
||||
final res = await client.get('getPlaylist', {'id': id});
|
||||
yield* _pool
|
||||
.forEach(allPlaylists.xml.findAllElements('playlist'), (
|
||||
playlist,
|
||||
) async {
|
||||
final id = playlist.getAttribute('id')!;
|
||||
final res = await client.get('getPlaylist', {'id': id});
|
||||
|
||||
return res.xml.findAllElements('entry').mapIndexed(mapPlaylistSong);
|
||||
});
|
||||
return res.xml.findAllElements('entry').mapIndexed(mapPlaylistSong);
|
||||
})
|
||||
.expand((a) => a);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Iterable<SourceSong>> allSongs() async* {
|
||||
Stream<SourceSong> allSongs() async* {
|
||||
if (await supportsFastSongSync) {
|
||||
await for (var songs in _songSearch()) {
|
||||
yield songs.map(mapSong);
|
||||
yield* Stream.fromIterable(songs.map(mapSong));
|
||||
}
|
||||
} else {
|
||||
await for (var albumsList in _albumList('alphabeticalByName')) {
|
||||
yield* _pool.forEach(albumsList, (album) async {
|
||||
final albums = await client.get('getAlbum', {
|
||||
'id': album.getAttribute('id')!,
|
||||
});
|
||||
return albums.xml.findAllElements('song').map(mapSong);
|
||||
});
|
||||
yield* _pool
|
||||
.forEach(albumsList, (album) async {
|
||||
final albums = await client.get('getAlbum', {
|
||||
'id': album.getAttribute('id')!,
|
||||
});
|
||||
return albums.xml.findAllElements('song').map(mapSong);
|
||||
})
|
||||
.expand((a) => a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Uri streamUri(String songId) {
|
||||
Uri streamUri(String songId, {int? maxBitrate, String? format}) {
|
||||
return client.uri('stream', {
|
||||
'id': songId,
|
||||
'estimateContentLength': true.toString(),
|
||||
'maxBitRate': maxBitrate.toString(),
|
||||
'format': streamFormat?.toString(),
|
||||
'format': format.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -143,28 +159,18 @@ class SubsonicSource implements MusicSource {
|
||||
return client.uri('getCoverArt', opts);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uri?> artistArtUri(String artistId, {bool thumbnail = true}) async {
|
||||
final res = await client.get('getArtistInfo2', {'id': artistId});
|
||||
return Uri.tryParse(
|
||||
res.xml
|
||||
.getElement('artistInfo2')
|
||||
?.getElement(thumbnail ? 'smallImageUrl' : 'largeImageUrl')
|
||||
?.text ??
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
Stream<Iterable<XmlElement>> _albumList(String type) async* {
|
||||
const size = 500;
|
||||
var offset = 0;
|
||||
|
||||
while (true) {
|
||||
final res = await client.get('getAlbumList2', {
|
||||
'type': type,
|
||||
'size': size.toString(),
|
||||
'offset': offset.toString(),
|
||||
});
|
||||
final res = await _pool.withResource(
|
||||
() => client.get('getAlbumList2', {
|
||||
'type': type,
|
||||
'size': size.toString(),
|
||||
'offset': offset.toString(),
|
||||
}),
|
||||
);
|
||||
|
||||
final albums = res.xml.findAllElements('album');
|
||||
offset += albums.length;
|
||||
@ -182,13 +188,15 @@ class SubsonicSource implements MusicSource {
|
||||
var offset = 0;
|
||||
|
||||
while (true) {
|
||||
final res = await client.get('search3', {
|
||||
'query': '""',
|
||||
'songCount': size.toString(),
|
||||
'songOffset': offset.toString(),
|
||||
'artistCount': '0',
|
||||
'albumCount': '0',
|
||||
});
|
||||
final res = await _pool.withResource(
|
||||
() => client.get('search3', {
|
||||
'query': '""',
|
||||
'songCount': size.toString(),
|
||||
'songOffset': offset.toString(),
|
||||
'artistCount': '0',
|
||||
'albumCount': '0',
|
||||
}),
|
||||
);
|
||||
|
||||
final songs = res.xml.findAllElements('song');
|
||||
offset += songs.length;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:subtracks/sources/subsonic/client.dart';
|
||||
import 'package:subtracks/sources/subsonic/source.dart';
|
||||
@ -32,12 +31,21 @@ void main() {
|
||||
password: 'password',
|
||||
),
|
||||
),
|
||||
Server(
|
||||
name: 'gonic',
|
||||
client: SubsonicClient(
|
||||
http: TestHttpClient(),
|
||||
address: Uri.parse('http://localhost:4747/'),
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (final Server(:name, :client) in clients) {
|
||||
group(name, () {
|
||||
setUp(() async {
|
||||
source = SubsonicSource(client: client, maxBitrate: 196);
|
||||
source = SubsonicSource(client);
|
||||
});
|
||||
|
||||
test('ping', () async {
|
||||
@ -45,22 +53,34 @@ void main() {
|
||||
});
|
||||
|
||||
test('allAlbums', () async {
|
||||
final items = (await source.allAlbums().toList()).flattened.toList();
|
||||
final items = await source.allAlbums().toList();
|
||||
|
||||
expect(items.length, equals(3));
|
||||
});
|
||||
|
||||
test('allArtists', () async {
|
||||
final items = (await source.allArtists().toList()).flattened.toList();
|
||||
final items = await source.allArtists().toList();
|
||||
|
||||
expect(items.length, equals(2));
|
||||
});
|
||||
|
||||
test('allSongs', () async {
|
||||
final items = (await source.allSongs().toList()).flattened.toList();
|
||||
final items = await source.allSongs().toList();
|
||||
|
||||
expect(items.length, equals(20));
|
||||
});
|
||||
|
||||
test('allPlaylists', () async {
|
||||
final items = await source.allPlaylists().toList();
|
||||
|
||||
expect(items.length, equals(0));
|
||||
});
|
||||
|
||||
test('allPlaylistSongs', () async {
|
||||
final items = await source.allPlaylistSongs().toList();
|
||||
|
||||
expect(items.length, equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user