Bläddra i källkod

fix: keep Jellyfin progress ring duration

Media3 reports an unknown duration for Jellyfin's progressive/transcoded stream, so position/duration was always zero. The native MediaItem now carries the queued track duration, the bridge state falls back to it, and the Dart media snapshot uses the queue duration when the native value is missing. Adds a regression test.
Krasimir Kazakov 5 dagar sedan
förälder
incheckning
2045a489c6

+ 10 - 1
android/app/src/main/kotlin/com/wasp/cardash_launcher/JellyfinPlaybackBridge.kt

@@ -181,10 +181,12 @@ class JellyfinPlaybackBridge(
                 )
                 putInt("playlistLength", rawItems.size)
             }
+            val durationMs = (item["durationMs"] as? Number)?.toLong() ?: 0L
             val metadata = MediaMetadata.Builder()
                 .setTitle(item["title"] as? String)
                 .setArtist(item["artist"] as? String)
                 .setAlbumTitle(item["album"] as? String)
+                .setDurationMs(durationMs)
                 .setExtras(extras)
                 .apply {
                     if (artwork != null && artwork.isNotEmpty()) {
@@ -227,7 +229,14 @@ class JellyfinPlaybackBridge(
         val item = controller.currentMediaItem ?: return mapOf("available" to false)
         val metadata = item.mediaMetadata
         val extras = metadata.extras
-        val duration = if (controller.duration > 0) controller.duration else 0L
+        // Jellyfin's progressive/transcoded audio often has no duration in
+        // ExoPlayer. Keep the queued track duration as the ring denominator.
+        val fallbackDuration = (metadata.durationMs ?: 0L).coerceAtLeast(0L)
+        val duration = if (controller.duration > 0) {
+            controller.duration
+        } else {
+            fallbackDuration
+        }
         return mapOf(
             "available" to true,
             "playing" to controller.isPlaying,

+ 9 - 2
lib/data/models/jellyfin_playback.dart

@@ -184,7 +184,14 @@ class JellyfinPlaybackSnapshot {
   }
 
   /// Snapshot consumed by the Home volume card and the existing media widgets.
-  MediaPlayback toMediaPlayback() {
+  ///
+  /// A progressive/transcoded Jellyfin stream often has an unknown duration in
+  /// ExoPlayer. [fallbackDurationMs] comes from the queued track metadata so
+  /// the progress ring still has a denominator.
+  MediaPlayback toMediaPlayback({int fallbackDurationMs = 0}) {
+    final effectiveDuration = durationMs > 0
+        ? durationMs
+        : (fallbackDurationMs > 0 ? fallbackDurationMs : 0);
     return MediaPlayback(
       hasAccess: available,
       hasSession: available && itemId != null,
@@ -192,7 +199,7 @@ class JellyfinPlaybackSnapshot {
       title: title,
       artist: artist,
       packageName: appPackageName,
-      durationMs: durationMs,
+      durationMs: effectiveDuration,
       positionMs: positionMs,
     );
   }

+ 3 - 1
lib/features/jellyfin/jellyfin_playback_controller.dart

@@ -76,7 +76,9 @@ class JellyfinPlaybackController extends ChangeNotifier {
 
   MediaPlayback get mediaPlayback {
     if (!_snapshot.available) return const MediaPlayback();
-    return _snapshot.toMediaPlayback();
+    return _snapshot.toMediaPlayback(
+      fallbackDurationMs: currentItem?.durationMs ?? 0,
+    );
   }
 
   bool get canGoNext => _snapshot.hasNext;

+ 2 - 1
test/jellyfin_fakes.dart

@@ -354,6 +354,7 @@ class FakeJellyfinPlaybackPlatform implements JellyfinPlaybackPlatform {
   int setQueueCalls = 0;
   bool setQueueResult = true;
   bool permissionGranted = false;
+  bool zeroDuration = false;
 
   @override
   Future<bool> setQueue(JellyfinPlaybackQueue queue) async {
@@ -384,7 +385,7 @@ class FakeJellyfinPlaybackPlatform implements JellyfinPlaybackPlatform {
       title: item.title,
       artist: item.artist,
       album: item.album,
-      durationMs: item.durationMs,
+      durationMs: zeroDuration ? 0 : item.durationMs,
       positionMs: positionMs,
       index: safeIndex,
       hasPrevious: safeIndex > 0,

+ 22 - 0
test/jellyfin_playback_controller_test.dart

@@ -175,6 +175,28 @@ void main() {
     expect(fixture.platform.permissionGranted, isTrue);
   });
 
+  test('media progress falls back to the queued track duration', () async {
+    final fixture = await _fixture();
+    addTearDown(fixture.playback.dispose);
+    addTearDown(fixture.jellyfin.dispose);
+
+    await fixture.playback.playAlbum(album('album-1'));
+    fixture.platform.zeroDuration = true;
+    fixture.platform.state = fixture.platform.snapshotAt(
+      0,
+      playing: true,
+      positionMs: 30000,
+    );
+
+    await fixture.playback.toggle();
+
+    expect(fixture.playback.snapshot.durationMs, 0);
+    final media = fixture.playback.mediaPlayback;
+    expect(media.durationMs, fixture.playback.currentItem!.durationMs);
+    expect(media.positionMs, 30000);
+    expect(media.progress, greaterThan(0));
+  });
+
   test('a failed queue start surfaces an error', () async {
     final fixture = await _fixture();
     addTearDown(fixture.playback.dispose);