Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8686edd8d7 | ||
|
|
8e5d4ab002 |
@@ -240,6 +240,22 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a server player ID matches our local builtin player ID.
|
||||||
|
/// MA beta 14+ wraps Sendspin player IDs with 'up' prefix and lowercases
|
||||||
|
/// them when merging into universal_player entities, so we need flexible matching.
|
||||||
|
/// e.g. local "ensemble_Chris_CP21.260" matches server "upensemble_chris_cp21.260"
|
||||||
|
bool _isBuiltinPlayer(String serverPlayerId, String builtinPlayerId) {
|
||||||
|
if (serverPlayerId == builtinPlayerId) return true;
|
||||||
|
return serverPlayerId.toLowerCase() == 'up${builtinPlayerId.toLowerCase()}' ||
|
||||||
|
serverPlayerId.toLowerCase() == builtinPlayerId.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a server player ID is an Ensemble player (this or other device)
|
||||||
|
bool _isEnsemblePlayer(String serverPlayerId) {
|
||||||
|
final lower = serverPlayerId.toLowerCase();
|
||||||
|
return lower.startsWith('ensemble_') || lower.startsWith('upensemble_');
|
||||||
|
}
|
||||||
|
|
||||||
/// Player filter from MA user settings (empty = all players allowed)
|
/// Player filter from MA user settings (empty = all players allowed)
|
||||||
List<String> get playerFilter => _playerFilter;
|
List<String> get playerFilter => _playerFilter;
|
||||||
|
|
||||||
@@ -4171,8 +4187,8 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
// Smart sort: local player first, then playing, then on, then off
|
// Smart sort: local player first, then playing, then on, then off
|
||||||
players.sort((a, b) {
|
players.sort((a, b) {
|
||||||
// Local player always first
|
// Local player always first
|
||||||
final aIsLocal = builtinPlayerId != null && a.playerId == builtinPlayerId;
|
final aIsLocal = builtinPlayerId != null && _isBuiltinPlayer(a.playerId, builtinPlayerId);
|
||||||
final bIsLocal = builtinPlayerId != null && b.playerId == builtinPlayerId;
|
final bIsLocal = builtinPlayerId != null && _isBuiltinPlayer(b.playerId, builtinPlayerId);
|
||||||
if (aIsLocal && !bIsLocal) return -1;
|
if (aIsLocal && !bIsLocal) return -1;
|
||||||
if (bIsLocal && !aIsLocal) return 1;
|
if (bIsLocal && !aIsLocal) return 1;
|
||||||
|
|
||||||
@@ -4249,8 +4265,8 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.playerId.startsWith('ensemble_')) {
|
if (_isEnsemblePlayer(player.playerId)) {
|
||||||
if (builtinPlayerId == null || player.playerId != builtinPlayerId) {
|
if (builtinPlayerId == null || !_isBuiltinPlayer(player.playerId, builtinPlayerId)) {
|
||||||
_logger.log('🚫 Filtering out other device\'s player: ${player.name}');
|
_logger.log('🚫 Filtering out other device\'s player: ${player.name}');
|
||||||
filteredCount++;
|
filteredCount++;
|
||||||
return false;
|
return false;
|
||||||
@@ -4258,7 +4274,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!player.available) {
|
if (!player.available) {
|
||||||
if (builtinPlayerId != null && player.playerId == builtinPlayerId) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(player.playerId, builtinPlayerId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
filteredCount++;
|
filteredCount++;
|
||||||
@@ -4482,7 +4498,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
// Try available first, then accept unavailable (Sendspin may still be registering)
|
// Try available first, then accept unavailable (Sendspin may still be registering)
|
||||||
if (playerToSelect == null && builtinPlayerId != null) {
|
if (playerToSelect == null && builtinPlayerId != null) {
|
||||||
playerToSelect = _availablePlayers.cast<Player?>().firstWhere(
|
playerToSelect = _availablePlayers.cast<Player?>().firstWhere(
|
||||||
(p) => p!.playerId == builtinPlayerId && p.available,
|
(p) => _isBuiltinPlayer(p!.playerId, builtinPlayerId) && p.available,
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
if (playerToSelect != null) {
|
if (playerToSelect != null) {
|
||||||
@@ -4490,7 +4506,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
// Player exists but not yet available (Sendspin registration in progress)
|
// Player exists but not yet available (Sendspin registration in progress)
|
||||||
playerToSelect = _availablePlayers.cast<Player?>().firstWhere(
|
playerToSelect = _availablePlayers.cast<Player?>().firstWhere(
|
||||||
(p) => p!.playerId == builtinPlayerId,
|
(p) => _isBuiltinPlayer(p!.playerId, builtinPlayerId),
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
if (playerToSelect != null) {
|
if (playerToSelect != null) {
|
||||||
@@ -4568,7 +4584,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// Switch audio handler mode based on player type
|
// Switch audio handler mode based on player type
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
final isBuiltinPlayer = builtinPlayerId != null && player.playerId == builtinPlayerId;
|
final isBuiltinPlayer = builtinPlayerId != null && _isBuiltinPlayer(player.playerId, builtinPlayerId);
|
||||||
if (isBuiltinPlayer) {
|
if (isBuiltinPlayer) {
|
||||||
audioHandler.setLocalMode();
|
audioHandler.setLocalMode();
|
||||||
// Update notification for builtin player using local mode method (keeps pause working)
|
// Update notification for builtin player using local mode method (keeps pause working)
|
||||||
@@ -5355,7 +5371,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
final track = _currentTrack!;
|
final track = _currentTrack!;
|
||||||
final artworkUrl = _api?.getImageUrl(track, size: 512);
|
final artworkUrl = _api?.getImageUrl(track, size: 512);
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
final isBuiltinPlayer = builtinPlayerId != null && _selectedPlayer!.playerId == builtinPlayerId;
|
final isBuiltinPlayer = builtinPlayerId != null && _isBuiltinPlayer(_selectedPlayer!.playerId, builtinPlayerId);
|
||||||
|
|
||||||
if (isBuiltinPlayer) {
|
if (isBuiltinPlayer) {
|
||||||
// Local playback - use local mode notification (keeps pause working)
|
// Local playback - use local mode notification (keeps pause working)
|
||||||
@@ -5401,7 +5417,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
// No queue data available from API
|
// No queue data available from API
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
final isBuiltinPlayer = builtinPlayerId != null && _selectedPlayer!.playerId == builtinPlayerId;
|
final isBuiltinPlayer = builtinPlayerId != null && _isBuiltinPlayer(_selectedPlayer!.playerId, builtinPlayerId);
|
||||||
|
|
||||||
// For builtin player, try to preserve cached track data since queue API doesn't return it
|
// For builtin player, try to preserve cached track data since queue API doesn't return it
|
||||||
// The cached track was set in selectPlayer() and contains valid data
|
// The cached track was set in selectPlayer() and contains valid data
|
||||||
@@ -5498,7 +5514,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
// Sendspin PCM streaming has ~5s buffering delay on resume.
|
// Sendspin PCM streaming has ~5s buffering delay on resume.
|
||||||
// For audiobooks, seek back to compensate so the user doesn't miss narration.
|
// For audiobooks, seek back to compensate so the user doesn't miss narration.
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
if (builtinPlayerId != null && playerId == builtinPlayerId && _sendspinConnected && isPlayingAudiobook) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(playerId, builtinPlayerId) && _sendspinConnected && isPlayingAudiobook) {
|
||||||
final pos = _positionTracker.currentPosition.inSeconds;
|
final pos = _positionTracker.currentPosition.inSeconds;
|
||||||
if (pos > 10) {
|
if (pos > 10) {
|
||||||
_logger.log('📚 Seeking back 10s to compensate for Sendspin buffering');
|
_logger.log('📚 Seeking back 10s to compensate for Sendspin buffering');
|
||||||
@@ -6079,7 +6095,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
// Get builtin player ID - this is cached so should be fast
|
// Get builtin player ID - this is cached so should be fast
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
|
|
||||||
if (builtinPlayerId != null && playerId == builtinPlayerId && _sendspinConnected) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(playerId, builtinPlayerId) && _sendspinConnected) {
|
||||||
_logger.log('⏸️ Non-blocking local pause for builtin player');
|
_logger.log('⏸️ Non-blocking local pause for builtin player');
|
||||||
|
|
||||||
// CRITICAL: Don't await these - they can block the UI thread
|
// CRITICAL: Don't await these - they can block the UI thread
|
||||||
@@ -6181,7 +6197,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
// Optimistic local stop for builtin player on skip - non-blocking
|
// Optimistic local stop for builtin player on skip - non-blocking
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
if (builtinPlayerId != null && playerId == builtinPlayerId && _sendspinConnected) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(playerId, builtinPlayerId) && _sendspinConnected) {
|
||||||
_logger.log('⏭️ Non-blocking local stop for skip on builtin player');
|
_logger.log('⏭️ Non-blocking local stop for skip on builtin player');
|
||||||
// Stop current audio immediately - fire and forget, but log errors
|
// Stop current audio immediately - fire and forget, but log errors
|
||||||
unawaited((_pcmAudioPlayer?.pause() ?? Future.value()).catchError(
|
unawaited((_pcmAudioPlayer?.pause() ?? Future.value()).catchError(
|
||||||
@@ -6200,7 +6216,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
// Optimistic local stop for builtin player on previous - non-blocking
|
// Optimistic local stop for builtin player on previous - non-blocking
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
if (builtinPlayerId != null && playerId == builtinPlayerId && _sendspinConnected) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(playerId, builtinPlayerId) && _sendspinConnected) {
|
||||||
_logger.log('⏮️ Non-blocking local stop for previous on builtin player');
|
_logger.log('⏮️ Non-blocking local stop for previous on builtin player');
|
||||||
// Stop current audio immediately - fire and forget, but log errors
|
// Stop current audio immediately - fire and forget, but log errors
|
||||||
unawaited((_pcmAudioPlayer?.pause() ?? Future.value()).catchError(
|
unawaited((_pcmAudioPlayer?.pause() ?? Future.value()).catchError(
|
||||||
@@ -6614,7 +6630,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
final builtinPlayerId = await SettingsService.getBuiltinPlayerId();
|
||||||
if (builtinPlayerId != null && playerId == builtinPlayerId) {
|
if (builtinPlayerId != null && _isBuiltinPlayer(playerId, builtinPlayerId)) {
|
||||||
_localPlayerVolume = volumeLevel;
|
_localPlayerVolume = volumeLevel;
|
||||||
FlutterVolumeController.setVolume(volumeLevel / 100.0);
|
FlutterVolumeController.setVolume(volumeLevel / 100.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user