Files
Ensemble/lib/services/error_handler.dart
Your NameandClaude Opus 4.5 bc118cdd74 fix(playback): Improve handling of unavailable tracks
- Add pre-flight check for tracks with available provider mappings
- Skip tracks that lack available providers instead of sending them to MA
- Log detailed info about which tracks are playable vs unavailable
- Add smart retry logic that doesn't retry non-transient errors
- Improve user-facing error messages for unavailable content
- Reduce unnecessary retry delays for data-related failures

Fixes "No playable items found" errors when albums contain tracks
that don't have available streaming providers.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 09:02:20 +00:00

139 lines
3.9 KiB
Dart

import 'debug_logger.dart';
/// Error types for user-friendly messages
enum ErrorType {
connection,
authentication,
network,
playback,
library,
unknown,
}
class ErrorInfo {
final ErrorType type;
final String userMessage;
final String technicalMessage;
final bool canRetry;
ErrorInfo({
required this.type,
required this.userMessage,
required this.technicalMessage,
this.canRetry = true,
});
}
class ErrorHandler {
static final _logger = DebugLogger();
/// Convert technical errors into user-friendly error messages
static ErrorInfo handleError(dynamic error, {String context = ''}) {
_logger.log('❌ Error in $context: $error');
final errorStr = error.toString().toLowerCase();
// Connection errors
if (errorStr.contains('not connected') ||
errorStr.contains('disconnected') ||
errorStr.contains('connection closed')) {
return ErrorInfo(
type: ErrorType.connection,
userMessage: 'Not connected to Music Assistant',
technicalMessage: error.toString(),
canRetry: true,
);
}
// Network errors
if (errorStr.contains('socket') ||
errorStr.contains('network') ||
errorStr.contains('timeout') ||
errorStr.contains('failed host lookup')) {
return ErrorInfo(
type: ErrorType.network,
userMessage: 'Network connection failed. Please check your connection.',
technicalMessage: error.toString(),
canRetry: true,
);
}
// Authentication errors
if (errorStr.contains('401') ||
errorStr.contains('unauthorized') ||
errorStr.contains('authentication') ||
errorStr.contains('auth')) {
return ErrorInfo(
type: ErrorType.authentication,
userMessage: 'Authentication failed. Please check your credentials.',
technicalMessage: error.toString(),
canRetry: false,
);
}
// Unavailable content errors - these won't resolve with retry
if (errorStr.contains('no playable') ||
errorStr.contains('lack available providers') ||
errorStr.contains('no available providers')) {
return ErrorInfo(
type: ErrorType.playback,
userMessage: 'These tracks are unavailable. The music provider may not have them available for streaming.',
technicalMessage: error.toString(),
canRetry: false,
);
}
// Playback errors
if (errorStr.contains('queue') ||
errorStr.contains('player') ||
errorStr.contains('track')) {
return ErrorInfo(
type: ErrorType.playback,
userMessage: 'Playback failed. Please try again.',
technicalMessage: error.toString(),
canRetry: true,
);
}
// Library/content errors
if (errorStr.contains('library') ||
errorStr.contains('not found') ||
errorStr.contains('no result')) {
return ErrorInfo(
type: ErrorType.library,
userMessage: 'Failed to load content. Please try again.',
technicalMessage: error.toString(),
canRetry: true,
);
}
// Unknown errors
return ErrorInfo(
type: ErrorType.unknown,
userMessage: 'An unexpected error occurred. Please try again.',
technicalMessage: error.toString(),
canRetry: true,
);
}
/// Get user-friendly message for specific operations
static String getOperationErrorMessage(String operation, dynamic error) {
final errorInfo = handleError(error, context: operation);
return errorInfo.userMessage;
}
/// Check if error is retryable
static bool isRetryable(dynamic error) {
final errorInfo = handleError(error);
return errorInfo.canRetry;
}
/// Log error with context
static void logError(String context, dynamic error, {StackTrace? stackTrace}) {
_logger.log('❌ [$context] $error');
if (stackTrace != null) {
_logger.log('Stack trace: $stackTrace');
}
}
}