Compare commits
10
Commits
v3.0.4-beta
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7fe6dfbbe | ||
|
|
cb07e55d69 | ||
|
|
cf0336e99d | ||
|
|
73fd1dbdcd | ||
|
|
2eb22cf524 | ||
|
|
3fe7a5800a | ||
|
|
972690b894 | ||
|
|
0e5679262c | ||
|
|
67bae29f9d | ||
|
|
4f7fbe92d9 |
@@ -0,0 +1,167 @@
|
||||
# Music Assistant 2.8.0 Beta 14 — Impact on Ensemble
|
||||
|
||||
**Date:** 2026-03-03
|
||||
**Current server:** 2.8.0 beta 12
|
||||
**Target server:** 2.8.0 beta 14 (released March 2, 2026)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Beta 14 is a large release (50+ PRs). The headline change — merging multi-protocol players into a single entity — directly overlaps with Ensemble's client-side Cast↔Sendspin consolidation logic. Sendspin also received a major v4.0 overhaul. Most changes should degrade gracefully but player ID persistence will break on upgrade.
|
||||
|
||||
---
|
||||
|
||||
## Breaking: Player Merging (PR #3150)
|
||||
|
||||
MA now merges players that support multiple protocols (e.g. a Chromecast with Cast + Sendspin) into a **single player entity** server-side. Previously these appeared as two separate players.
|
||||
|
||||
> *"After this change, all your players will be reset. If you previously had multiple players in Music Assistant per protocol, this will now show up as one single player."*
|
||||
|
||||
### What Ensemble does today
|
||||
|
||||
Ensemble handles this client-side in `music_assistant_provider.dart:4271-4406`:
|
||||
|
||||
- `_castToSendspinIdMap` maps Cast IDs → Sendspin IDs
|
||||
- When Sendspin is grouped: hides Cast player, shows Sendspin (renamed to base name)
|
||||
- When ungrouped: hides Sendspin, shows original Cast
|
||||
- Mappings persisted to database for cold start restoration
|
||||
|
||||
### Impact on Ensemble
|
||||
|
||||
| Component | Location | Impact | Severity |
|
||||
|---|---|---|---|
|
||||
| `_castToSendspinIdMap` + consolidation logic | `provider:4271-4406` | Dead code — server no longer sends separate players | Medium |
|
||||
| `isManuallySyncedPlayer()` ID translation | `provider:533-595` | Lookups against stale map fail silently (returns `false`) | Low |
|
||||
| Player selection persistence | Settings + DB | **Stored player IDs invalid after upgrade** — all players reset | **High** |
|
||||
| `_handlePlayerUpdatedEvent()` ID matching | `provider:2956-3358` | Events arrive with new merged IDs, old translation won't match | Medium |
|
||||
| Database-persisted Cast↔Sendspin mappings | `DatabaseService` | Stale data, won't match anything | Low |
|
||||
|
||||
### Assessment
|
||||
|
||||
The consolidation logic should degrade gracefully — it won't find Sendspin suffixes to strip or Cast pairs to merge, so the server's already-merged players pass through `filterPlayers()` as-is. The main risk is **player selection breaking** because saved player IDs no longer exist.
|
||||
|
||||
### Action required
|
||||
|
||||
1. Verify the existing fallback logic (`provider:4427-4465`) gracefully re-selects when the stored `selectedPlayerId` doesn't exist in the player list
|
||||
2. Remove or gate the Cast↔Sendspin consolidation code behind a server version check (on beta 14+ it's unnecessary and could cause confusion)
|
||||
3. Clear stale database mappings on first connection to a beta 14+ server
|
||||
|
||||
---
|
||||
|
||||
## Sendspin v4.0 (PR #3158)
|
||||
|
||||
Major rewrite of the Sendspin server.
|
||||
|
||||
### Changes
|
||||
|
||||
- New `SendspinPlaybackSession` coordinator architecture
|
||||
- Per-member DSP pipelines, dynamic group membership, late-join audio backfill
|
||||
- Max buffer capacity 5 → 30 seconds
|
||||
- Multiple Sendspin servers with automatic failover
|
||||
- Per-client codec/sample-rate/bit-depth options
|
||||
- Album artwork persists during skip/pause
|
||||
- Reduced latency for live streams
|
||||
|
||||
### Impact on Ensemble
|
||||
|
||||
| Change | Impact |
|
||||
|---|---|
|
||||
| New session architecture | **Likely compatible** — sendspin-js only bumped 2.0.0 → 2.0.1 (minor), suggesting wire protocol didn't fundamentally change |
|
||||
| Per-client codec options | `stream/start` may contain new fields — safe if Ensemble ignores unknown fields |
|
||||
| Multiple servers with failover | Ensemble's 3-tier connection strategy may need awareness |
|
||||
| Buffer increase | No client impact |
|
||||
|
||||
### Action required
|
||||
|
||||
1. Test Sendspin connection against beta 14 — verify `client/hello`, `stream/start`, state reporting all still work
|
||||
2. Confirm unknown fields in protocol messages are ignored gracefully
|
||||
|
||||
---
|
||||
|
||||
## Sendspin-Chromecast Bridge (PR #3255)
|
||||
|
||||
Replaces the inline experimental Sendspin mode in `ChromecastPlayer` with a dedicated bridge. Cast devices now use a Cast receiver app with an embedded JS Sendspin client.
|
||||
|
||||
### Impact on Ensemble
|
||||
|
||||
- **Direct impact: None** — Ensemble is a Sendspin client itself, not going through Cast
|
||||
- **Indirect:** Changes how grouped Cast players behave, which could affect `GroupVolumeManager`
|
||||
|
||||
---
|
||||
|
||||
## Group Volume / Grouping Fixes
|
||||
|
||||
| PR | Change | Ensemble Impact |
|
||||
|---|---|---|
|
||||
| #3192 | Fix grouping for players whose native protocol is also a protocol of other players | Bidirectional grouping translation — may affect group detection logic |
|
||||
| #3205 | Fix group mute for protocol-synced players (`player.state.group_members` vs `player.group_members`) | `GroupVolumeManager` may see different/correct member lists |
|
||||
| #3277 | Fix `volume_up`/`volume_down` for group players | Server-side fix, should improve behavior |
|
||||
| #3118 | Fix sync groups losing members on power off | Affects dynamic group management |
|
||||
|
||||
### Action required
|
||||
|
||||
1. Test group volume behavior — may "just work better" with server-side fixes
|
||||
2. Verify `GroupVolumeManager` handles any changes to `group_members` field population
|
||||
|
||||
---
|
||||
|
||||
## Sendspin Bug Fixes
|
||||
|
||||
| PR | Fix | Ensemble Relevance |
|
||||
|---|---|---|
|
||||
| #3191 | DSP not applying for Sendspin players | DSP config stored on parent player, looked up by protocol player ID — now fixed |
|
||||
| #3249 | Update to aiosendspin 4.2.0 | Library update, potential behavior changes |
|
||||
| #3250 | Metadata sending wrong progress when paused | Progress reporting fix — Ensemble's position tracker may see better data |
|
||||
| #3258 | Floating point for internal audio data | Audio quality improvement, no client impact |
|
||||
|
||||
---
|
||||
|
||||
## New APIs — Opportunities
|
||||
|
||||
| Feature | PR | Details | Ensemble Opportunity |
|
||||
|---|---|---|---|
|
||||
| Playback speed | #3198 | `player_queues/set_playback_speed`, stored in `queue.extra_attributes["playback_speed"]` | Great for audiobooks/podcasts |
|
||||
| Save queue to playlist | #3149 | `save_as_playlist` command on player queue controller | Easy feature add |
|
||||
| Mixed-media playlists | #3216 | Playlists can contain tracks + podcasts + audiobooks. DB schema bumped to v30. `get_playlist_tracks()` returns `Sequence[PlaylistPlayableItem]` | Playlist screens may need to handle heterogeneous items |
|
||||
| Genres v2 | #3164 | New browsing category with icons and SVG support | New library section |
|
||||
|
||||
---
|
||||
|
||||
## Also Changed in Beta 13 (Skipped)
|
||||
|
||||
Relevant changes from beta 13 (between current beta 12 and target beta 14):
|
||||
|
||||
- Group volume mute support (#3034)
|
||||
- Frontend prepared for multiprotocol support (#1409) — groundwork for beta 14 merging
|
||||
- IPv6 support for zeroconf, stream server, AirPlay (#3086)
|
||||
- Snapcast player availability alignment (#3104)
|
||||
- DLNA reconnection fix (#3132)
|
||||
|
||||
---
|
||||
|
||||
## Recommended Action Plan
|
||||
|
||||
### Before upgrading
|
||||
|
||||
- [ ] Verify player selection fallback handles missing player IDs gracefully
|
||||
- [ ] Back up current configuration
|
||||
|
||||
### Immediate (after upgrading)
|
||||
|
||||
- [ ] Test Sendspin protocol compatibility (connection, streaming, state reporting)
|
||||
- [ ] Test player list — confirm merged players appear correctly without client-side consolidation
|
||||
- [ ] Test group volume behavior with new server-side fixes
|
||||
- [ ] Test player selection persistence across app restarts
|
||||
|
||||
### Short-term
|
||||
|
||||
- [ ] Remove or version-gate Cast↔Sendspin consolidation code
|
||||
- [ ] Clear stale database mappings on beta 14+ detection
|
||||
- [ ] Test mixed-media playlist handling if playlists are used
|
||||
|
||||
### Opportunistic
|
||||
|
||||
- [ ] Add playback speed control (audiobook/podcast feature)
|
||||
- [ ] Add save-queue-to-playlist
|
||||
- [ ] Explore Genres v2 browsing
|
||||
@@ -86,6 +86,13 @@ This application was built with AI-assisted development using **Claude Code** an
|
||||
- **Skip Controls** - Skip forward/backward during playback
|
||||
- **High-Resolution Artwork** - Fetched via iTunes for best quality
|
||||
|
||||
### Android Auto
|
||||
- **Media Browsing** - Browse your library from your car's display with categories for Home, Music, Audiobooks, Podcasts, and Radio
|
||||
- **Voice Search** - Find and play tracks using voice commands
|
||||
- **Full Playback Controls** - Play, pause, and skip from your car's head unit
|
||||
- **Album Art & Metadata** - Track info and artwork displayed on your car's screen
|
||||
- **Smart Queueing** - Tap any track in an album or playlist to play from that point with the full queue
|
||||
|
||||
### Smart Features
|
||||
- **Instant App Restore** - App loads instantly with cached library data while syncing in background
|
||||
- **Auto-Reconnect** - Automatically reconnects when connection is lost
|
||||
@@ -141,6 +148,17 @@ If you run Music Assistant as a Home Assistant add-on:
|
||||
- Use the IP address of your Home Assistant server
|
||||
- Do **not** use your Home Assistant URL or ingress URL
|
||||
|
||||
### Android Auto
|
||||
|
||||
Since Ensemble is not installed from the Play Store, you need to enable unknown sources in Android Auto:
|
||||
|
||||
1. Open **Android Auto** settings on your phone
|
||||
2. Tap the **Version** number at the bottom 10 times to enable developer mode
|
||||
3. Tap the three-dot menu and open **Developer settings**
|
||||
4. Enable **Unknown sources**
|
||||
5. Connect your phone to your car via USB or wirelessly
|
||||
6. Ensemble will appear in Android Auto's app list
|
||||
|
||||
### Remote Access
|
||||
|
||||
For access outside your home network, you'll need to expose Music Assistant through a reverse proxy (e.g., Traefik, Nginx Proxy Manager, Cloudflare Tunnel).
|
||||
@@ -157,10 +175,7 @@ For access outside your home network, you'll need to expose Music Assistant thro
|
||||
A huge thank you to my sponsors! 💖
|
||||
|
||||
<a href="https://github.com/pcsokonay"><img src="https://github.com/pcsokonay.png" width="60px" alt="pcsokonay" /></a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/antoinevandenhurk"><img src="https://github.com/antoinevandenhurk.png" width="60px" alt="antoinevandenhurk" /></a>
|
||||
<a href="https://github.com/githubchb"><img src="https://github.com/githubchb.png" width="60px" alt="githubchb" /></a>
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -77,6 +77,16 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86_64': 3]
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def abiVersionCode = abiCodes[output.getFilter('ABI')]
|
||||
if (abiVersionCode != null) {
|
||||
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
@@ -3063,7 +3063,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
_cacheService.setCachedTrackForPlayer(playerId, mergedTrack);
|
||||
_logger.log('📋 Merged album info into existing track for $playerName: ${albumName}');
|
||||
} else {
|
||||
_logger.log('📋 Skipped caching for $playerName - already have better data (artist: $existingHasProperArtist, image: $existingHasImage)');
|
||||
_logger.debug('📋 Skipped caching for $playerName - already have better data (artist: $existingHasProperArtist, image: $existingHasImage)');
|
||||
}
|
||||
|
||||
// For selected player, _updatePlayerState() is already called above which fetches queue data
|
||||
@@ -4556,7 +4556,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
// Sendspin PCM player - continue to start timer
|
||||
_logger.log('🔔 Starting notification timer for Sendspin PCM player');
|
||||
_logger.debug('🔔 Starting notification timer for Sendspin PCM player');
|
||||
}
|
||||
|
||||
// Only run timer if player is playing
|
||||
@@ -4800,7 +4800,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
/// Called when playback stops (player state becomes idle)
|
||||
void _startIdleServiceTimer() {
|
||||
_idleServiceTimer?.cancel();
|
||||
_logger.log('⏱️ Starting idle service timer (${_idleServiceTimeout.inMinutes} min)');
|
||||
_logger.debug('⏱️ Starting idle service timer (${_idleServiceTimeout.inMinutes} min)');
|
||||
_idleServiceTimer = Timer(_idleServiceTimeout, () {
|
||||
_logger.log('⏱️ Idle service timeout reached - stopping foreground service');
|
||||
audioHandler.stopService();
|
||||
@@ -5730,7 +5730,7 @@ class MusicAssistantProvider with ChangeNotifier {
|
||||
|
||||
final itemJsonList = queue.items.map((item) => jsonEncode(item.toJson())).toList();
|
||||
await DatabaseService.instance.saveQueue(playerId, itemJsonList);
|
||||
_logger.log('💾 Persisted ${queue.items.length} queue items to database');
|
||||
_logger.debug('💾 Persisted ${queue.items.length} queue items to database');
|
||||
} catch (e) {
|
||||
_logger.log('⚠️ Error persisting queue to database: $e');
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import '../../models/media_item.dart' as ma;
|
||||
|
||||
/// Custom AudioHandler for Ensemble that provides full control over
|
||||
/// notification actions and metadata updates.
|
||||
class MassivAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
class MassivAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
final AuthManager authManager;
|
||||
final _logger = DebugLogger();
|
||||
@@ -335,6 +335,7 @@ class MassivAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
/// Called from _MusicAssistantAppState.initState().
|
||||
void setProvider(MusicAssistantProvider provider) {
|
||||
_autoProvider = provider;
|
||||
_logger.log('AndroidAuto: provider set');
|
||||
}
|
||||
|
||||
// Media ID constants — root categories
|
||||
@@ -377,7 +378,10 @@ class MassivAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
Future<List<MediaItem>> getChildren(String parentMediaId,
|
||||
[Map<String, dynamic>? options]) async {
|
||||
final provider = _autoProvider;
|
||||
if (provider == null) return [];
|
||||
if (provider == null) {
|
||||
_logger.log('AndroidAuto: getChildren("$parentMediaId") — provider not set yet');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Ensure provider is connected — AA may browse before app is fully ready
|
||||
if (!provider.isConnected) {
|
||||
|
||||
@@ -251,7 +251,7 @@ class MusicAssistantAPI {
|
||||
void _handleMessage(dynamic message) {
|
||||
try {
|
||||
final data = jsonDecode(message as String) as Map<String, dynamic>;
|
||||
_logger.log('Received message: ${data.keys}');
|
||||
_logger.debug('Received message: ${data.keys}');
|
||||
|
||||
// Check for server info message (first message on connect)
|
||||
if (data.containsKey('server_version')) {
|
||||
@@ -1981,7 +1981,7 @@ class MusicAssistantAPI {
|
||||
currentIndex = queueResult['current_index'] as int?;
|
||||
shuffleEnabled = queueResult['shuffle_enabled'] as bool?;
|
||||
repeatMode = queueResult['repeat_mode'] as String?;
|
||||
_logger.log('🔀 Queue metadata: shuffle_enabled=$shuffleEnabled, repeat_mode=$repeatMode');
|
||||
_logger.debug('🔀 Queue metadata: shuffle_enabled=$shuffleEnabled, repeat_mode=$repeatMode');
|
||||
|
||||
final currentItemData = queueResult['current_item'] as Map<String, dynamic>?;
|
||||
final currentItemName = currentItemData?['name'] as String?;
|
||||
@@ -2017,7 +2017,7 @@ class MusicAssistantAPI {
|
||||
}
|
||||
|
||||
if (items.isEmpty) {
|
||||
_logger.log('⚠️ Queue is empty or all items failed to parse');
|
||||
_logger.debug('⚠️ Queue is empty or all items failed to parse');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class PositionTracker {
|
||||
Duration? duration,
|
||||
double? serverTimestamp,
|
||||
}) {
|
||||
_logger.log('⏱️ PositionTracker.updateFromServer: pos=${position.toStringAsFixed(1)}s, isPlaying=$isPlaying, playerId=$playerId');
|
||||
_logger.debug('⏱️ PositionTracker.updateFromServer: pos=${position.toStringAsFixed(1)}s, isPlaying=$isPlaying, playerId=$playerId');
|
||||
final bool playerChanged = _playerId != playerId;
|
||||
final bool playStateChanged = _isPlaying != isPlaying;
|
||||
final bool durationChanged = duration != null && _duration != duration;
|
||||
@@ -179,11 +179,11 @@ class PositionTracker {
|
||||
final newAnchorPos = anchorIsGettingStale && !playerChanged && !playStateChanged && isPlaying
|
||||
? currentInterpolated // Keep our interpolated position, just refresh the anchor time
|
||||
: anchorPos; // Use server position for other cases
|
||||
_logger.log('⏱️ PositionTracker: Anchor updated ${_anchorPosition.toStringAsFixed(1)}s -> ${newAnchorPos.toStringAsFixed(1)}s (playerChanged=$playerChanged, playStateChanged=$playStateChanged, positionDiff=${positionDiff.toStringAsFixed(1)}, suspiciousReset=$isSuspiciousReset, anchorStale=$anchorIsGettingStale)');
|
||||
_logger.debug('⏱️ PositionTracker: Anchor updated ${_anchorPosition.toStringAsFixed(1)}s -> ${newAnchorPos.toStringAsFixed(1)}s (playerChanged=$playerChanged, playStateChanged=$playStateChanged, positionDiff=${positionDiff.toStringAsFixed(1)}, suspiciousReset=$isSuspiciousReset, anchorStale=$anchorIsGettingStale)');
|
||||
_anchorPosition = newAnchorPos;
|
||||
_anchorTime = DateTime.now();
|
||||
} else if (positionDiff > 2) {
|
||||
_logger.log('⏱️ PositionTracker: Anchor NOT updated (suspiciousReset=$isSuspiciousReset, staleTimestamp=$hasStaleTimestamp, interpolated=${currentInterpolated.toStringAsFixed(1)}s)');
|
||||
_logger.debug('⏱️ PositionTracker: Anchor NOT updated (suspiciousReset=$isSuspiciousReset, staleTimestamp=$hasStaleTimestamp, interpolated=${currentInterpolated.toStringAsFixed(1)}s)');
|
||||
}
|
||||
|
||||
_isPlaying = isPlaying;
|
||||
@@ -227,7 +227,7 @@ class PositionTracker {
|
||||
|
||||
/// Clear tracker state (e.g., when disconnecting)
|
||||
void clear() {
|
||||
_logger.log('⏱️ PositionTracker.clear() called');
|
||||
_logger.debug('⏱️ PositionTracker.clear() called');
|
||||
_stopInterpolationTimer();
|
||||
_playerId = null;
|
||||
_isPlaying = false;
|
||||
@@ -238,17 +238,17 @@ class PositionTracker {
|
||||
|
||||
void _startInterpolationTimer() {
|
||||
if (_interpolationTimer != null) {
|
||||
_logger.log('⏱️ PositionTracker: Timer already running, skipping start');
|
||||
_logger.debug('⏱️ PositionTracker: Timer already running, skipping start');
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.log('⏱️ PositionTracker: Starting interpolation timer (anchor=${_anchorPosition.toStringAsFixed(1)}s)');
|
||||
_logger.debug('⏱️ PositionTracker: Starting interpolation timer (anchor=${_anchorPosition.toStringAsFixed(1)}s)');
|
||||
_interpolationTimer = Timer.periodic(const Duration(milliseconds: 250), (timer) {
|
||||
// Log every 4 ticks (1 second) if no emission happened
|
||||
if (timer.tick % 4 == 0) {
|
||||
final pos = currentPosition;
|
||||
if (pos.inSeconds == _lastEmittedSeconds) {
|
||||
_logger.log('⏱️ PositionTracker: Timer tick ${timer.tick}, pos=${pos.inSeconds}s unchanged');
|
||||
_logger.debug('⏱️ PositionTracker: Timer tick ${timer.tick}, pos=${pos.inSeconds}s unchanged');
|
||||
}
|
||||
}
|
||||
_emitPosition();
|
||||
@@ -259,7 +259,7 @@ class PositionTracker {
|
||||
}
|
||||
|
||||
void _stopInterpolationTimer() {
|
||||
_logger.log('⏱️ PositionTracker: Stopping interpolation timer (was ${_interpolationTimer != null ? "running" : "null"})');
|
||||
_logger.debug('⏱️ PositionTracker: Stopping interpolation timer (was ${_interpolationTimer != null ? "running" : "null"})');
|
||||
_interpolationTimer?.cancel();
|
||||
_interpolationTimer = null;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class PositionTracker {
|
||||
// Only emit if second changed (reduces unnecessary updates)
|
||||
if (seconds != _lastEmittedSeconds) {
|
||||
_lastEmittedSeconds = seconds;
|
||||
_logger.log('⏱️ PositionTracker: Emitting position ${seconds}s (anchor=${_anchorPosition.toStringAsFixed(1)}s, playing=$_isPlaying)');
|
||||
_logger.debug('⏱️ PositionTracker: Emitting position ${seconds}s (anchor=${_anchorPosition.toStringAsFixed(1)}s, playing=$_isPlaying)');
|
||||
_positionController.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ class SendspinService {
|
||||
|
||||
try {
|
||||
final json = jsonEncode(message);
|
||||
_logger.log('Sendspin: Sending message: ${message['type']}');
|
||||
_logger.debug('Sendspin: Sending message: ${message['type']}');
|
||||
_channel!.sink.add(json);
|
||||
} catch (e) {
|
||||
_logger.log('Sendspin: Error sending message: $e');
|
||||
|
||||
@@ -657,7 +657,7 @@ class ExpandablePlayerState extends State<ExpandablePlayer>
|
||||
_logger.log('📊 Subscribing to positionStream for progress bar');
|
||||
_positionSubscription = maProvider.positionTracker.positionStream.listen((position) {
|
||||
if (!mounted) return;
|
||||
_logger.log('📊 Progress received position: ${position.inSeconds}s');
|
||||
_logger.debug('📊 Progress received position: ${position.inSeconds}s');
|
||||
_progressNotifier.value = position.inSeconds;
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: ensemble
|
||||
description: A minimalistic Music Assistant client built with Flutter
|
||||
publish_to: 'none'
|
||||
version: 3.0.4+56
|
||||
version: 3.0.5+57
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
Reference in New Issue
Block a user