Compare commits

..
5 Commits
Author SHA1 Message Date
Your Name eff99492fa chore: Bump version to 1.6.1 2025-12-09 08:59:03 +00:00
Your NameandClaude Opus 4.5 9dafc53670 Handle builtin_player API removal in MA 2.7.0b20+
- Detect 'Invalid command: builtin_player/register' error
- Mark builtin_player as unavailable instead of crashing
- Skip state reporting when builtin_player not available
- App continues to work with other players (Chromecast, squeezelite, etc)

Note: MA 2.7.0b20+ replaced builtin_player with Sendspin (WebRTC-based).
Full Sendspin integration would require implementing WebRTC in the app.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 06:20:39 +00:00
Your NameandClaude Opus 4.5 bfa6374657 Fix player registration race condition with auth
- Remove premature player registration from _initializeLocalPlayback()
- Player registration now only happens in _initializeAfterConnection()
- This ensures auth is complete before any player API calls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 06:05:09 +00:00
Your NameandClaude Opus 4.5 8b5fbb12b7 Fix double connection race condition on app startup
- Remove duplicate connectToServer call from main.dart
- Provider._initialize() already handles connection with auth
- main.dart now waits for provider connection instead of racing it
- Fixes auth state check failing due to stale API reference

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 05:54:44 +00:00
Your NameandClaude Opus 4.5 d62cf28530 Fix auth state handling for MA beta 22+
- Block non-auth commands when server requires auth but not yet authenticated
- Properly wait for authenticated state before initializing connection
- Fixes issue where app tried to fetch data before authentication completed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 05:45:51 +00:00
4 changed files with 69 additions and 31 deletions
+19 -18
View File
@@ -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) {
+22 -4
View File
@@ -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;
+27 -8
View File
@@ -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
View File
@@ -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'