Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff99492fa | ||
|
|
9dafc53670 | ||
|
|
bfa6374657 | ||
|
|
8b5fbb12b7 | ||
|
|
d62cf28530 |
+19
-18
@@ -9,6 +9,7 @@ import 'services/settings_service.dart';
|
||||
import 'services/audio/massiv_audio_handler.dart';
|
||||
import 'services/auth/auth_manager.dart';
|
||||
import 'services/debug_logger.dart';
|
||||
import 'services/music_assistant_api.dart' show MAConnectionState;
|
||||
import 'theme/theme_provider.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
import 'theme/system_theme_helper.dart';
|
||||
@@ -267,26 +268,26 @@ class _AppStartupState extends State<AppStartup> {
|
||||
|
||||
final provider = context.read<MusicAssistantProvider>();
|
||||
|
||||
// Only attempt connection if not already connected
|
||||
if (!provider.isConnected) {
|
||||
_logger.log('🚀 AppStartup: Auto-connecting to saved server: $serverUrl');
|
||||
try {
|
||||
// Restore auth credentials before connecting (critical for Authelia)
|
||||
final savedCredentials = await SettingsService.getAuthCredentials();
|
||||
if (savedCredentials != null) {
|
||||
_logger.log('🚀 AppStartup: Restoring saved auth credentials');
|
||||
provider.authManager.deserializeCredentials(savedCredentials);
|
||||
}
|
||||
// Connection is handled by MusicAssistantProvider._initialize()
|
||||
// Just wait for it to complete or timeout
|
||||
_logger.log('🚀 AppStartup: Waiting for provider auto-connection to $serverUrl');
|
||||
|
||||
await provider.connectToServer(serverUrl);
|
||||
_logger.log('🚀 AppStartup: Auto-connection successful');
|
||||
} catch (e) {
|
||||
_logger.log('🚀 AppStartup: Auto-connection failed: $e');
|
||||
// Connection failed, but still show home screen
|
||||
// User can manually reconnect from there
|
||||
// Give the provider time to connect (it restores credentials and connects in _initialize)
|
||||
// Check connection state periodically
|
||||
for (var i = 0; i < 20; i++) {
|
||||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
if (provider.isConnected) {
|
||||
_logger.log('🚀 AppStartup: Connection established');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
_logger.log('🚀 AppStartup: Already connected');
|
||||
if (provider.connectionState == MAConnectionState.error) {
|
||||
_logger.log('🚀 AppStartup: Connection failed with error');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider.isConnected) {
|
||||
_logger.log('🚀 AppStartup: Connection still pending or failed');
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
|
||||
@@ -47,6 +47,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
|
||||
// Local player state
|
||||
bool _isLocalPlayerPowered = true;
|
||||
bool _builtinPlayerAvailable = true; // False on MA 2.7.0b20+ (uses Sendspin instead)
|
||||
StreamSubscription? _localPlayerEventSubscription;
|
||||
StreamSubscription? _playerUpdatedEventSubscription;
|
||||
Timer? _localPlayerStateReportTimer;
|
||||
@@ -157,12 +158,16 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
// After authentication succeeds, authenticated state will trigger initialization
|
||||
// Don't call _initializeAfterConnection() here - wait for authenticated state
|
||||
return;
|
||||
}
|
||||
|
||||
// No auth required, initialize immediately
|
||||
await _initializeAfterConnection();
|
||||
} else if (state == MAConnectionState.authenticated) {
|
||||
_logger.log('✅ MA authentication successful');
|
||||
// Now safe to initialize since we're authenticated
|
||||
await _initializeAfterConnection();
|
||||
} else if (state == MAConnectionState.disconnected) {
|
||||
_availablePlayers = [];
|
||||
@@ -364,9 +369,8 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
previousTrackSelectedPlayer();
|
||||
};
|
||||
|
||||
if (isConnected) {
|
||||
await _registerLocalPlayer();
|
||||
}
|
||||
// Player registration is now handled in _initializeAfterConnection()
|
||||
// which runs after authentication completes (when auth is required)
|
||||
}
|
||||
|
||||
Future<bool> _tryAdoptGhostPlayer() async {
|
||||
@@ -439,7 +443,20 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
}
|
||||
_registrationInProgress = null;
|
||||
} catch (e) {
|
||||
_logger.log('❌ CRITICAL: Player registration failed: $e');
|
||||
// Check if this is because builtin_player API is not available (MA 2.7.0b20+)
|
||||
final errorStr = e.toString();
|
||||
if (errorStr.contains('Invalid command') && errorStr.contains('builtin_player')) {
|
||||
_logger.log('⚠️ Builtin player API not available (MA 2.7.0b20+ uses Sendspin)');
|
||||
_logger.log('ℹ️ Local player registration skipped - use other players (Chromecast, etc)');
|
||||
_builtinPlayerAvailable = false;
|
||||
if (_registrationInProgress != null && !_registrationInProgress!.isCompleted) {
|
||||
_registrationInProgress!.complete();
|
||||
}
|
||||
_registrationInProgress = null;
|
||||
return; // Non-fatal, continue without local player
|
||||
}
|
||||
|
||||
_logger.log('❌ Player registration failed: $e');
|
||||
if (_registrationInProgress != null && !_registrationInProgress!.isCompleted) {
|
||||
_registrationInProgress!.completeError(e);
|
||||
}
|
||||
@@ -461,6 +478,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
|
||||
Future<void> _reportLocalPlayerState() async {
|
||||
if (_api == null) return;
|
||||
if (!_builtinPlayerAvailable) return; // Skip on MA 2.7.0b20+ (no builtin_player API)
|
||||
|
||||
final playerId = await SettingsService.getBuiltinPlayerId();
|
||||
if (playerId == null) return;
|
||||
|
||||
@@ -312,14 +312,33 @@ class MusicAssistantAPI {
|
||||
String command, {
|
||||
Map<String, dynamic>? args,
|
||||
}) async {
|
||||
// Allow commands when connected, authenticating, or authenticated
|
||||
final allowedStates = [
|
||||
MAConnectionState.connected,
|
||||
MAConnectionState.authenticating,
|
||||
MAConnectionState.authenticated,
|
||||
];
|
||||
if (!allowedStates.contains(_currentState)) {
|
||||
throw Exception('Not connected to Music Assistant server');
|
||||
// Allow auth commands when connected or authenticating
|
||||
// For all other commands, require authenticated state if auth is required
|
||||
final isAuthCommand = command == 'auth' || command == 'auth/login';
|
||||
|
||||
if (isAuthCommand) {
|
||||
// Auth commands can be sent when connected or authenticating
|
||||
final allowedStates = [
|
||||
MAConnectionState.connected,
|
||||
MAConnectionState.authenticating,
|
||||
];
|
||||
if (!allowedStates.contains(_currentState)) {
|
||||
throw Exception('Not connected to Music Assistant server');
|
||||
}
|
||||
} else {
|
||||
// Non-auth commands require proper authentication state
|
||||
if (_authRequired && !_isAuthenticated) {
|
||||
throw Exception('Not authenticated to Music Assistant server');
|
||||
}
|
||||
|
||||
final allowedStates = [
|
||||
MAConnectionState.connected,
|
||||
MAConnectionState.authenticating,
|
||||
MAConnectionState.authenticated,
|
||||
];
|
||||
if (!allowedStates.contains(_currentState)) {
|
||||
throw Exception('Not connected to Music Assistant server');
|
||||
}
|
||||
}
|
||||
|
||||
final messageId = _uuid.v4();
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: ensemble
|
||||
description: A minimalistic Music Assistant client built with Flutter
|
||||
publish_to: 'none'
|
||||
version: 1.4.0+4
|
||||
version: 1.6.1+8
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
Reference in New Issue
Block a user