feat(jellyfin): songs and their lyrics, read as subtitles are

A music library's artists and albums open as shows and seasons do, and
can be tied to a studyset. A song plays as sound alone (`/Audio/{id}/
stream`, as stored) with its lyrics as Jellyfin keeps them (`/Audio/{id}/
Lyrics`: an LRC file beside it, or its tags), cut into the dictionary's
words as a video's subtitles are, with no translation. Timed lyrics
follow the song: the line sung large and bright among the others faded,
scrolled to as it comes, a line tapped played from its start; untimed
ones show whole. A song without lyrics shows greyed and doesn't open
(`HasLyrics`), as there is nothing in it to read. Its cover stands where
the picture would, glowing as a video does, and its words are tagged
with the artist, shown, and "artist · title", hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-13 19:46:54 +02:00
commit 22f5a4fcd1
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -137,6 +137,34 @@ void main() {
expect(store.kept?.account?.token, 'token-2');
});
testWidgets('a music library lists its albums and songs; a song without '
'lyrics is greyed and opens nothing, one with lyrics plays', (
tester,
) async {
jellyfin = sampleMusic();
store = signedInJellyfin();
await show(
tester,
RoutePaths.jellyfin(
'japanese',
parentId: 'album-1',
name: 'Sample Album',
),
);
expect(find.text('Sample Song'), findsOneWidget);
expect(find.textContaining('No lyrics'), findsOneWidget);
await tester.tap(find.text('Sample Song (Instrumental)'));
await tester.pumpAndSettle();
expect(find.text('Sample Song (Instrumental)'), findsOneWidget);
await tester.tap(find.text('Sample Song'));
for (var frame = 0; frame < 6; frame++) {
await tester.pump(const Duration(milliseconds: 500));
}
expect(find.text('Words in this song'), findsOneWidget);
});
testWidgets('a show tied to the studyset comes first next time; an '
'episode opens to be watched', (tester) async {
store = signedInJellyfin();