✨ Lyrics for remote tracks
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_media_metadata/flutter_media_metadata.dart';
|
||||
import 'package:groovybox/data/db.dart';
|
||||
import 'package:groovybox/providers/audio_provider.dart';
|
||||
import 'package:groovybox/providers/db_provider.dart';
|
||||
import 'package:groovybox/providers/settings_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -218,6 +219,15 @@ class TrackRepository extends _$TrackRepository {
|
||||
await (db.update(db.tracks)..where((t) => t.id.equals(trackId))).write(
|
||||
TracksCompanion(lyrics: Value(lyricsJson)),
|
||||
);
|
||||
|
||||
// Update current track provider if this is the current track
|
||||
final currentTrackNotifier = ref.read(currentTrackProvider.notifier);
|
||||
final currentTrack = currentTrackNotifier.state;
|
||||
if (currentTrack != null && currentTrack.id == trackId) {
|
||||
final updatedTrack = currentTrack.copyWith(lyrics: lyricsJson);
|
||||
currentTrackNotifier.setTrack(updatedTrack);
|
||||
debugPrint('Updated current track provider with imported lyrics');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a single track by ID.
|
||||
|
||||
@@ -33,7 +33,7 @@ final class TrackRepositoryProvider
|
||||
TrackRepository create() => TrackRepository();
|
||||
}
|
||||
|
||||
String _$trackRepositoryHash() => r'655c231192698ef0c31920af846de47def7da81d';
|
||||
String _$trackRepositoryHash() => r'606c68068cb2811a0982c950ba0f12d77cdf9d44';
|
||||
|
||||
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:audio_service/audio_service.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:media_kit/media_kit.dart' as media_kit;
|
||||
import 'package:groovybox/data/db.dart';
|
||||
import 'package:groovybox/data/db.dart' as db;
|
||||
import 'package:groovybox/logic/metadata_service.dart';
|
||||
import 'package:groovybox/providers/audio_provider.dart';
|
||||
import 'package:groovybox/providers/theme_provider.dart';
|
||||
@@ -51,18 +51,19 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
_container = container;
|
||||
}
|
||||
|
||||
// Update theme color based on current track's album art and set current metadata
|
||||
// Update theme color based on current track's album art and set current metadata and track
|
||||
void _updateThemeFromCurrentTrack(MediaItem mediaItem) async {
|
||||
if (_container == null) return;
|
||||
|
||||
try {
|
||||
TrackMetadata? metadata;
|
||||
db.Track? track;
|
||||
|
||||
// For remote tracks, get metadata from database
|
||||
final urlResolver = _container!.read(remoteUrlResolverProvider);
|
||||
if (urlResolver.isProtocolUrl(mediaItem.id)) {
|
||||
final database = _container!.read(databaseProvider);
|
||||
final track = await (database.select(
|
||||
track = await (database.select(
|
||||
database.tracks,
|
||||
)..where((t) => t.path.equals(mediaItem.id))).getSingleOrNull();
|
||||
|
||||
@@ -94,7 +95,13 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
seedColorNotifier.updateFromAlbumArtBytes(artBytes);
|
||||
}
|
||||
} else {
|
||||
// For local tracks, use metadata service
|
||||
// For local tracks, get from database and use metadata service
|
||||
final database = _container!.read(databaseProvider);
|
||||
track = await (database.select(
|
||||
database.tracks,
|
||||
)..where((t) => t.path.equals(mediaItem.id))).getSingleOrNull();
|
||||
|
||||
// Use metadata service for local tracks
|
||||
final metadataService = MetadataService();
|
||||
metadata = await metadataService.getMetadata(mediaItem.id);
|
||||
|
||||
@@ -103,18 +110,31 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
seedColorNotifier.updateFromAlbumArtBytes(metadata.artBytes);
|
||||
}
|
||||
|
||||
// Set current track
|
||||
final trackNotifier = _container!.read(currentTrackProvider.notifier);
|
||||
if (track != null) {
|
||||
trackNotifier.setTrack(CurrentTrackData.fromTrack(track));
|
||||
} else {
|
||||
trackNotifier.clear();
|
||||
}
|
||||
|
||||
// Set current track metadata
|
||||
final metadataNotifier = _container!.read(
|
||||
currentTrackMetadataProvider.notifier,
|
||||
);
|
||||
if (metadata != null) {
|
||||
final metadataNotifier = _container!.read(
|
||||
currentTrackMetadataProvider.notifier,
|
||||
);
|
||||
metadataNotifier.setMetadata(metadata);
|
||||
} else {
|
||||
metadataNotifier.clear();
|
||||
}
|
||||
} catch (e) {
|
||||
// If metadata retrieval fails, reset to default color and clear metadata
|
||||
// If metadata retrieval fails, reset to default color and clear metadata/track
|
||||
final seedColorNotifier = _container!.read(seedColorProvider.notifier);
|
||||
seedColorNotifier.resetToDefault();
|
||||
|
||||
final trackNotifier = _container!.read(currentTrackProvider.notifier);
|
||||
trackNotifier.clear();
|
||||
|
||||
final metadataNotifier = _container!.read(
|
||||
currentTrackMetadataProvider.notifier,
|
||||
);
|
||||
@@ -256,18 +276,18 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
}
|
||||
|
||||
// New methods that accept Track objects with proper metadata
|
||||
Future<void> playTrack(Track track) async {
|
||||
Future<void> playTrack(db.Track track) async {
|
||||
final mediaItem = _trackToMediaItem(track);
|
||||
await updateQueue([mediaItem]);
|
||||
}
|
||||
|
||||
Future<void> playTracks(List<Track> tracks, {int initialIndex = 0}) async {
|
||||
Future<void> playTracks(List<db.Track> tracks, {int initialIndex = 0}) async {
|
||||
final mediaItems = tracks.map(_trackToMediaItem).toList();
|
||||
_queueIndex = initialIndex;
|
||||
await updateQueue(mediaItems);
|
||||
}
|
||||
|
||||
MediaItem _trackToMediaItem(Track track) {
|
||||
MediaItem _trackToMediaItem(db.Track track) {
|
||||
return MediaItem(
|
||||
id: track.path,
|
||||
album: track.album,
|
||||
|
||||
@@ -1,9 +1,63 @@
|
||||
import 'package:groovybox/logic/audio_handler.dart';
|
||||
import 'package:groovybox/logic/metadata_service.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:groovybox/data/db.dart' as db;
|
||||
|
||||
part 'audio_provider.g.dart';
|
||||
|
||||
// Simple data class for current track to avoid drift type issues
|
||||
class CurrentTrackData {
|
||||
final int id;
|
||||
final String title;
|
||||
final String? artist;
|
||||
final String? album;
|
||||
final String path;
|
||||
final String? lyrics;
|
||||
final int lyricsOffset;
|
||||
|
||||
CurrentTrackData({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.artist,
|
||||
this.album,
|
||||
required this.path,
|
||||
this.lyrics,
|
||||
required this.lyricsOffset,
|
||||
});
|
||||
|
||||
factory CurrentTrackData.fromTrack(db.Track track) {
|
||||
return CurrentTrackData(
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
artist: track.artist,
|
||||
album: track.album,
|
||||
path: track.path,
|
||||
lyrics: track.lyrics,
|
||||
lyricsOffset: track.lyricsOffset,
|
||||
);
|
||||
}
|
||||
|
||||
CurrentTrackData copyWith({
|
||||
int? id,
|
||||
String? title,
|
||||
String? artist,
|
||||
String? album,
|
||||
String? path,
|
||||
String? lyrics,
|
||||
int? lyricsOffset,
|
||||
}) {
|
||||
return CurrentTrackData(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
artist: artist ?? this.artist,
|
||||
album: album ?? this.album,
|
||||
path: path ?? this.path,
|
||||
lyrics: lyrics ?? this.lyrics,
|
||||
lyricsOffset: lyricsOffset ?? this.lyricsOffset,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This should be set after AudioService.init in main.dart
|
||||
late AudioHandler _audioHandler;
|
||||
|
||||
@@ -17,6 +71,22 @@ void setAudioHandler(AudioHandler handler) {
|
||||
_audioHandler = handler;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CurrentTrackNotifier extends _$CurrentTrackNotifier {
|
||||
@override
|
||||
CurrentTrackData? build() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void setTrack(CurrentTrackData? track) {
|
||||
state = track;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CurrentTrackMetadataNotifier extends _$CurrentTrackMetadataNotifier {
|
||||
@override
|
||||
|
||||
@@ -50,6 +50,60 @@ final class AudioHandlerProvider
|
||||
|
||||
String _$audioHandlerHash() => r'65fbd92e049fe4f3a0763516f1e68e1614f7630f';
|
||||
|
||||
@ProviderFor(CurrentTrackNotifier)
|
||||
const currentTrackProvider = CurrentTrackNotifierProvider._();
|
||||
|
||||
final class CurrentTrackNotifierProvider
|
||||
extends $NotifierProvider<CurrentTrackNotifier, CurrentTrackData?> {
|
||||
const CurrentTrackNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'currentTrackProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$currentTrackNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
CurrentTrackNotifier create() => CurrentTrackNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(CurrentTrackData? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<CurrentTrackData?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$currentTrackNotifierHash() =>
|
||||
r'faa718574ece8c3c4b8f19b70d79d142b4b7f3e9';
|
||||
|
||||
abstract class _$CurrentTrackNotifier extends $Notifier<CurrentTrackData?> {
|
||||
CurrentTrackData? build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<CurrentTrackData?, CurrentTrackData?>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<CurrentTrackData?, CurrentTrackData?>,
|
||||
CurrentTrackData?,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(CurrentTrackMetadataNotifier)
|
||||
const currentTrackMetadataProvider = CurrentTrackMetadataNotifierProvider._();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:groovybox/data/db.dart' as db;
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:groovybox/logic/lrc_providers.dart';
|
||||
import 'package:groovybox/logic/lyrics_parser.dart';
|
||||
import 'package:groovybox/providers/audio_provider.dart';
|
||||
import 'package:groovybox/providers/db_provider.dart';
|
||||
import 'package:groovybox/ui/screens/player_screen.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -60,6 +61,16 @@ class LyricsFetcher extends _$LyricsFetcher {
|
||||
|
||||
debugPrint('Updated database with lyrics for track $trackId');
|
||||
|
||||
// Update the current track provider if this is the current track
|
||||
final currentTrackNotifier = ref.read(currentTrackProvider.notifier);
|
||||
final currentTrack = currentTrackNotifier.state;
|
||||
if (currentTrack != null && currentTrack.id == trackId) {
|
||||
// Update the current track with new lyrics
|
||||
final updatedTrack = currentTrack.copyWith(lyrics: lyricsJson);
|
||||
currentTrackNotifier.setTrack(updatedTrack);
|
||||
debugPrint('Updated current track provider with new lyrics');
|
||||
}
|
||||
|
||||
// Invalidate the track provider to refresh the UI
|
||||
ref.invalidate(trackByPathProvider(trackPath));
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ class RemoteUrlResolver {
|
||||
try {
|
||||
// Create Jellyfin client and authenticate
|
||||
final client = JellyfinDart(basePathOverride: provider.serverUrl);
|
||||
client.setDeviceId('groovybox-${providerId}');
|
||||
client.setDeviceId('groovybox-$providerId');
|
||||
client.setVersion('1.0.0');
|
||||
|
||||
final userApi = client.getUserApi();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user