New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨Lyrics Support #693
✨Lyrics Support #693
Conversation
- lyrics are loaded from the server (if available) and can be show on the lyrics screen - auto-scrolling and highlighting working for synchronized lyrics - unsynchronized lyrics not yet tested - lyrics/metadata not yet prefetched/cached/downloaded
Regarding precaching, I think all you need to do is swap out all the ref.read calls on the metadataProvider future in current_track_metadata_provider to calls to ref.watch. The provider initialized in the prefetch is seeing that no-one is watching and disposing itself before we get to that track. If you use watch, it should stay alive and the value should be available instantly when the track switch occurs. |
Hmm, it seems like the types are not compatible for some reason. It won't let me I've pushed the changes in c1c78ca, maybe you can take a quick look? |
I also forgot to add one TODO yesterday, we should probably also download the lyric data along with the tracks and images for offline lyrics. What would be your approach to do this? You're already downloading track metadata somewhere after all (to show the grayed-out tracks), so maybe we can sneak the Or instead of downloading the regular |
We want metadataProvider to autoDispose because otherwise the lyrics will never be moved out of memory. In general, I don't think having a non-autoDispose family provider is good. I think the best option is something like this: /// Provider to handle pre-fetching metadata for upcoming tracks
final currentTrackMetadataProvider =
AutoDisposeFutureProvider<MetadataProvider?>((ref) async {
final List<FinampQueueItem> precacheItems =
GetIt.instance<QueueService>().peekQueue(next: 3, previous: 1);
for (final itemToPrecache in precacheItems) {
BaseItemDto? base = itemToPrecache.baseItem;
if (base != null) {
final request = MetadataRequest(item: base, includeLyrics: true);
unawaited(ref.watch(metadataProvider(request).future));
}
}
final currentTrack = ref.watch(currentSongProvider).value?.baseItem;
if (currentTrack != null) {
final request = MetadataRequest(
item: currentTrack,
includeLyrics: true,
);
var out = ref.watch(metadataProvider(request)).valueOrNull;
if (out != null) {
ref.keepAlive();
}
return out;
}
return null;
}); Regarding downloading lyrics, I'm not completely sure what the best way to go about it is. We don't want to download for all synced songs, only ones that are actually downloaded, both to reduce stored data and to reduce unneeded server requests. I don't see any way to batch lyrics or attach them to the BaseItemDto or anything, so that means an individual server request for every downloaded song with lyrics. The nastiest problem is that none of the lyrics endpoints have any sort of ID or version, so the only way to tell if the lyrics have changed is to re-fetch them for every song. I guess we re-fetch on full sync, skip on quick-sync, and do the actual downloading inside the sync step. I'm thinking the best way to actually store this info is probably in a separate Isar database, but using the same isarId as the parent song. |
Thanks for the suggestions, they seem to work. I think I was missing replacing the We could create an issue for adding the needed info in the server repo. It probably isn't a big change. I'm also not a fan that lyrics are now split up, but it does makes sense to have them as media streams, just like subtitles. What would be the benefit of a separate database for lyrics? More flexibility for handling |
Regarding fetching the lyrics in the background, I believe just adding some sort of global watcher is the way to go about it. Maybe just put it into the now playing bar to match the album image, just try not to accidentally cause unneeded rebuilds on metadata change. This all somewhat conflicts with the other idea you had earlier of wanting to re-fetch failed requests when closing and re-opening the player screen. Putting the lyrics in a separate database is just a bit more flexible, and helps separate any lyric related logic from the core DownloadItem syncing/updating logic, which is already complex enough as-is. It allows managing the lyrics in a way closer to how we manage downloaded files than how we manage the core metadata, which matches better with the behavior we want. |
Okay, I'll see what I can do about it. I wasn't really aiming to re-fetch lyrics when re-opening the player screen, I was just trying to figure out the downsides a non-auto-disposed provider would have. I'll try to figure out how the download system works and how to add the lyrics downloading. I'd be happy about any pointers or guidance you could give me! :) |
Yeah, without autoDispose the provider will never get disposed, ever, unless one of it's dependencies refreshes. The cached value will just be stored forever. This makes sense for things with a single provider, like currentAlbumImageProvider, unless they are very large, and even makes sense for things with a small family like playerScreenThemeProvider, but is pretty bad for something with a large family like metadataProvider that creates a new provider per song. For the downloads, you want to turn the LyricDto into an Isar collection and register it in main. Then inside _downloadSong() you fetch the lyrics, if needed, outside the isar write transaction. Then you actually write it to the new collection inside the same final write transaction in that function, using the same isarId as the parent DownloadItem. Isar transactions cover all collections at once, so this means that it we can never get desync where a notDownloaded song has lyrics downloaded. Finally, add a delete on the lyrics collection using the song's isarId to the transaction in deleteDownload() |
@Komodo5197 I've taken a shot at implementing lyrics downloading now in bd3ec69. I'm sure there's something I did wrong or more complicated than necessary, especially w.r.t. the Edit: I'm also not sure if this update will migrate properly. I'm guessing people will need to re-download all tracks to get the lyrics? |
…teError for missing MediaStreams
Okay, that should be all of it. Main features should be fully working now:
I think I'll leave this as it is now for the 10.9 update, and then use the initial feedback for gauging which configuration options are wanted by users. |
…y/pause, added haptic feedback
…nt queue items - settings can change between adding different queue items (transcoding, downloading, etc.), so we include the ID of the FinampQueueItem in the MetadataRequest hash if available
While looking at the volume PR, I noticed that there’s another spot in Downloditem.copyWith you should add some mediastream stuff to prevent accidentally removing it when updating metadata. |
Was the thing I just added in the latest commit what you were referring to? I'm honestly not sure what the point of that assert was there (just to make sure the list of MediaSources is not empty?), but tried to add similar checks for the MediaStreams. Would that work, or do I have to do something else? |
Yeah, that’s what I was referring to. Looking at the assert, I’m honestly not entirely sure why it was put in either. Regardless, it’s probably best to update it to make sure both are null or non-empty, vs the current logic which checks if either is. |
Okay, that should be it then. I'll merge this tomorrow morning, unless something else comes up... |
Just noticed I forgot to update the the assert. I'll throw that in tomorrow |
This all looks good to me :) |
🎉 |
Can we have a build? |
@barolo it was just released ^^ But the feature will only work with a Jellyfin 10.9 server! |
Great!. I know, I'm already on 10.9 |
It's kind of weird that it's a separate screen [instead appearing in and expanding from within the current one], could we have an option to set it as default? Or auto-trigger when lyrics are available? |
Yeah, I agree that the current "layered" behavior isn't great, especially because it doesn't allow quickly collapsing the player screen and e.g. searching for a track. I can try making it so that it actually switches between the screen instead of layering them, and that it remembers the default. |
This adds support for lyrics, which the Jellyfin server supports starting with Jellyfin 10.9.
Since 10.9 is getting close to a release now (planned for end of April), this PR aims to get lyrics support ready for the redesign beta before that release.
The fetching of lyrics, rendering, UI, and things like auto-scrolling are already mostly working. What's still missing is:
TODO
JellyfinApiHelper.getPlaybackInfo()
in the metadata provider, that might require everything we need for now and is better suited for the taskAllow the user to disable auto-scrollUse the player's sizing controller to reduce the height of the control section