Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7378e7a25 | ||
|
|
9e045b1bd4 | ||
|
|
7049128f01 | ||
|
|
e5dc9cbf89 |
@@ -87,6 +87,8 @@ Ensemble supports multiple authentication methods:
|
||||
- Network connectivity to your Music Assistant server
|
||||
- Android device (Android 5.0+)
|
||||
|
||||
> **Note:** Local device playback (streaming to your phone) is not currently supported on Music Assistant beta 20+. This is due to MA replacing the built-in player with Sendspin. Support will be added once Sendspin is more stable. Remote control of other players works fine.
|
||||
|
||||
## About Music Assistant
|
||||
|
||||
Ensemble is a mobile client for [Music Assistant](https://music-assistant.io/), an open-source music library manager that integrates with various music sources and streaming providers.
|
||||
|
||||
@@ -141,7 +141,8 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
_serverUrl = serverUrl;
|
||||
await SettingsService.setServerUrl(serverUrl);
|
||||
|
||||
await _api?.disconnect();
|
||||
// Dispose the old API to stop any pending reconnects
|
||||
_api?.dispose();
|
||||
|
||||
_api = MusicAssistantAPI(serverUrl, _authManager);
|
||||
|
||||
|
||||
@@ -45,6 +45,30 @@ class _AlbumDetailsScreenState extends State<AlbumDetailsScreen> with SingleTick
|
||||
_loadTracks();
|
||||
_extractColors();
|
||||
_loadAlbumDescription();
|
||||
_refreshFavoriteStatus();
|
||||
}
|
||||
|
||||
Future<void> _refreshFavoriteStatus() async {
|
||||
final maProvider = context.read<MusicAssistantProvider>();
|
||||
if (maProvider.api == null) return;
|
||||
|
||||
// Need a URI to fetch fresh album data
|
||||
final albumUri = widget.album.uri;
|
||||
if (albumUri == null || albumUri.isEmpty) {
|
||||
_logger.log('Cannot refresh favorite status: album has no URI');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final freshAlbum = await maProvider.api!.getAlbumByUri(albumUri);
|
||||
if (freshAlbum != null && mounted) {
|
||||
setState(() {
|
||||
_isFavorite = freshAlbum.favorite ?? false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.log('Error refreshing favorite status: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _extractColors() async {
|
||||
@@ -128,6 +152,9 @@ class _AlbumDetailsScreenState extends State<AlbumDetailsScreen> with SingleTick
|
||||
_isFavorite = newState;
|
||||
});
|
||||
|
||||
// Invalidate home cache so the home screen shows updated favorite status
|
||||
maProvider.invalidateHomeCache();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
||||
@@ -63,15 +63,24 @@ class MusicAssistantAPI {
|
||||
|
||||
// Guard to prevent multiple simultaneous connection attempts
|
||||
Completer<void>? _connectionInProgress;
|
||||
bool _isDisposed = false;
|
||||
|
||||
Future<void> connect() async {
|
||||
// If already connected, nothing to do
|
||||
if (_currentState == MAConnectionState.connected) {
|
||||
// If disposed, don't try to connect
|
||||
if (_isDisposed) {
|
||||
_logger.log('Connection: API disposed, skipping connect');
|
||||
return;
|
||||
}
|
||||
|
||||
// If already connected or authenticated, nothing to do
|
||||
if (_currentState == MAConnectionState.connected ||
|
||||
_currentState == MAConnectionState.authenticated) {
|
||||
_logger.log('Connection: Already connected, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// If connection is in progress, wait for it instead of starting another
|
||||
if (_connectionInProgress != null) {
|
||||
if (_connectionInProgress != null && !_connectionInProgress!.isCompleted) {
|
||||
_logger.log('Connection already in progress, waiting...');
|
||||
return _connectionInProgress!.future;
|
||||
}
|
||||
@@ -611,13 +620,14 @@ class MusicAssistantAPI {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Album?> getAlbumDetails(String provider, String itemId) async {
|
||||
/// Get album details by URI
|
||||
/// Use the album's uri field (e.g., "library://album/123" or "spotify://album/xyz")
|
||||
Future<Album?> getAlbumByUri(String uri) async {
|
||||
try {
|
||||
final response = await _sendCommand(
|
||||
'music/album',
|
||||
'music/item_by_uri',
|
||||
args: {
|
||||
'provider': provider,
|
||||
'item_id': itemId,
|
||||
'uri': uri,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -626,7 +636,7 @@ class MusicAssistantAPI {
|
||||
|
||||
return Album.fromJson(result as Map<String, dynamic>);
|
||||
} catch (e) {
|
||||
_logger.log('Error getting album details: $e');
|
||||
_logger.log('Error getting album by URI: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1975,13 +1985,39 @@ class MusicAssistantAPI {
|
||||
}
|
||||
|
||||
Future<void> _reconnect() async {
|
||||
// Don't reconnect if disposed
|
||||
if (_isDisposed) {
|
||||
_logger.log('Reconnect: API disposed, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't reconnect if already connected or connecting
|
||||
if (_currentState == MAConnectionState.connected ||
|
||||
_currentState == MAConnectionState.authenticated ||
|
||||
_currentState == MAConnectionState.connecting) {
|
||||
_logger.log('Reconnect: Already connected/connecting, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't reconnect if another connection is in progress
|
||||
if (_connectionInProgress != null && !_connectionInProgress!.isCompleted) {
|
||||
_logger.log('Reconnect: Connection already in progress, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.delayed(Timings.reconnectDelay);
|
||||
if (_currentState != MAConnectionState.connected) {
|
||||
try {
|
||||
await connect();
|
||||
} catch (e) {
|
||||
_logger.log('Reconnection failed: $e');
|
||||
}
|
||||
|
||||
// Check again after delay
|
||||
if (_isDisposed ||
|
||||
_currentState == MAConnectionState.connected ||
|
||||
_currentState == MAConnectionState.authenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await connect();
|
||||
} catch (e) {
|
||||
_logger.log('Reconnection failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1994,7 +2030,13 @@ class MusicAssistantAPI {
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
_stopHeartbeat();
|
||||
// Complete any pending connection to prevent hanging futures
|
||||
if (_connectionInProgress != null && !_connectionInProgress!.isCompleted) {
|
||||
_connectionInProgress!.completeError(Exception('API disposed'));
|
||||
}
|
||||
_connectionInProgress = null;
|
||||
disconnect();
|
||||
_connectionStateController.close();
|
||||
for (final stream in _eventStreams.values) {
|
||||
|
||||
Reference in New Issue
Block a user