Cover-Farbe, Teilen aus YouTube, Android Auto, geschärfte Typografie
P3-Liste aus dem UX-Review: Player nimmt Cover-Farbe auf, "Teilen" aus YouTube trägt die Adresse in den Downloader ein, Android Auto blättert durch die Bibliothek, Laufweite der Typografie geschärft, Überschriften vereinheitlicht. 533 Tests grün, flutter analyze ohne Befund.
This commit is contained in:
@@ -4,9 +4,12 @@ import 'package:audio_service/audio_service.dart';
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import '../library/database.dart';
|
||||
import '../library/song_grouping.dart';
|
||||
import '../library/song_media.dart';
|
||||
import '../services/cache_manager.dart';
|
||||
import '../services/download_speicher.dart';
|
||||
import '../services/navidrome_service.dart';
|
||||
import 'auto_browse.dart';
|
||||
import 'headphone_autoplay.dart';
|
||||
import 'replay_gain.dart';
|
||||
import 'sleep_timer.dart';
|
||||
@@ -455,6 +458,102 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
await _player.moveAudioSource(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
// === Blättern im Auto und beim Sprachassistenten ===
|
||||
//
|
||||
// Der MediaBrowserService war im Manifest zwar angemeldet, lieferte aber
|
||||
// nie Inhalte: ohne diese Methoden blieb die Bibliothek dort leer.
|
||||
|
||||
@override
|
||||
Future<List<MediaItem>> getChildren(String parentMediaId,
|
||||
[Map<String, dynamic>? options]) async {
|
||||
if (parentMediaId == AudioService.browsableRootId) {
|
||||
return [for (final art in wurzelArten) _ordner(art)];
|
||||
}
|
||||
final knoten = knotenAus(parentMediaId);
|
||||
if (knoten == null) return const [];
|
||||
switch (knoten.art) {
|
||||
case AutoArt.favoriten:
|
||||
return _alsMedien(await db.watchFavorites().first);
|
||||
case AutoArt.zuletzt:
|
||||
return _alsMedien(await db.watchRecent(limit: 100).first);
|
||||
case AutoArt.kategorien:
|
||||
final namen = await db.allCategoryNames();
|
||||
return [
|
||||
for (final name in namen) _ordner(AutoArt.kategorie, name),
|
||||
];
|
||||
case AutoArt.kuenstlerListe:
|
||||
final songs = await db.watchSongs().first;
|
||||
return [
|
||||
for (final name in groupByArtist(songs).keys)
|
||||
_ordner(AutoArt.kuenstler, name),
|
||||
];
|
||||
case AutoArt.kategorie:
|
||||
return _alsMedien(await _titelDerKategorie(knoten.name));
|
||||
case AutoArt.kuenstler:
|
||||
final songs = await db.watchSongs().first;
|
||||
return _alsMedien(groupByArtist(songs)[knoten.name] ?? const []);
|
||||
case AutoArt.titel:
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MediaItem?> getMediaItem(String mediaId) async {
|
||||
final knoten = knotenAus(mediaId);
|
||||
if (knoten?.art != AutoArt.titel || knoten?.name == null) return null;
|
||||
final song = await db.songById(knoten!.name!);
|
||||
return song == null ? null : songToMediaItem(song);
|
||||
}
|
||||
|
||||
/// Wird aufgerufen, wenn im Auto ein Titel angetippt wird. Es läuft nicht
|
||||
/// nur dieser eine, sondern der Ordner ab dieser Stelle — sonst hörte die
|
||||
/// Musik nach einem Lied auf.
|
||||
@override
|
||||
Future<void> playFromMediaId(String mediaId,
|
||||
[Map<String, dynamic>? extras]) async {
|
||||
final knoten = knotenAus(mediaId);
|
||||
if (knoten == null) return;
|
||||
if (knoten.art == AutoArt.titel && knoten.name != null) {
|
||||
final ordner = extras?['ordner'] as String?;
|
||||
final geschwister =
|
||||
ordner == null ? const <MediaItem>[] : await getChildren(ordner);
|
||||
final index =
|
||||
geschwister.indexWhere((m) => songIdOf(m) == knoten.name);
|
||||
if (index != -1) {
|
||||
await loadPlaylist(geschwister, startIndex: index);
|
||||
return;
|
||||
}
|
||||
final song = await db.songById(knoten.name!);
|
||||
if (song != null) await loadPlaylist([songToMediaItem(song)]);
|
||||
return;
|
||||
}
|
||||
// Ein Ordner: alles darin von vorn.
|
||||
final inhalt = await getChildren(mediaId);
|
||||
final titel = [for (final m in inhalt) if (songIdOf(m) != null) m];
|
||||
if (titel.isNotEmpty) await loadPlaylist(titel);
|
||||
}
|
||||
|
||||
Future<List<Song>> _titelDerKategorie(String? name) async {
|
||||
if (name == null) return const [];
|
||||
final songs = await db.watchSongs().first;
|
||||
final zuordnung = await db.watchCategoriesBySong().first;
|
||||
return [
|
||||
for (final song in songs)
|
||||
if (zuordnung[song.id]?.contains(name) ?? false) song,
|
||||
];
|
||||
}
|
||||
|
||||
MediaItem _ordner(AutoArt art, [String? name]) => MediaItem(
|
||||
id: knotenId(art, name),
|
||||
title: name ?? art.anzeige,
|
||||
playable: false,
|
||||
);
|
||||
|
||||
List<MediaItem> _alsMedien(List<Song> songs) => [
|
||||
for (final song in songs)
|
||||
songToMediaItem(song).copyWith(id: knotenId(AutoArt.titel, song.id)),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> play() => _player.play();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user