From 3558ad3b1220ed3f2d166f0e3a66193a3a2ed3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Jab=C5=82onowski?= Date: Sun, 26 Jul 2026 10:37:09 +0200 Subject: [PATCH] fix: queue shows 0:00 duration for sourced tracks YouTubeEngine passed track duration in seconds while SpotubeAudioSourceMatchObject.fromJson interprets `duration` as microseconds, so durations collapsed to ~0 and the queue displayed 0:00. - lib/services/metadata/metadata.dart: send duration in microseconds (search() and getVideo() callbacks) - lib/components/track_tile/track_tile.dart, lib/modules/player/player_queue.dart: add resolveMissingDuration so the queue actively resolves the sourced track's real duration when the track's own metadata duration is effectively zero Fixes #3021 --- lib/components/track_tile/track_tile.dart | 16 ++++++++++++++-- lib/modules/player/player_queue.dart | 1 + lib/services/metadata/metadata.dart | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index ec3f50f3..0e4a6729 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -21,6 +21,7 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/utils/platform.dart'; final isBlacklistedProvider = @@ -46,6 +47,7 @@ class TrackTile extends HookConsumerWidget { final bool userPlaylist; final String? playlistId; final AudioPlayerState playlist; + final bool resolveMissingDuration; final List? leadingActions; @@ -62,6 +64,7 @@ class TrackTile extends HookConsumerWidget { this.userPlaylist = false, this.playlistId, this.leadingActions, + this.resolveMissingDuration = false, }); @override @@ -88,6 +91,16 @@ class TrackTile extends HookConsumerWidget { // toggling a dedicated `selectionMode` flag (e.g. playlists), so we must // disable inner navigation in both cases. final effectiveSelection = selectionMode || onChanged != null; + final sourcedTrack = resolveMissingDuration && + track is SpotubeFullTrackObject && + track.durationMs < Duration.millisecondsPerSecond + ? ref.watch(sourcedTrackProvider(track as SpotubeFullTrackObject)) + : null; + final duration = sourcedTrack?.maybeWhen( + data: (track) => track.info.duration, + orElse: () => Duration(milliseconds: this.track.durationMs), + ) ?? + Duration(milliseconds: track.durationMs); return LayoutBuilder(builder: (context, constrains) { return Listener( @@ -322,8 +335,7 @@ class TrackTile extends HookConsumerWidget { children: [ const SizedBox(width: 8), Text( - Duration(milliseconds: track.durationMs) - .toHumanReadableString(padZero: false), + duration.toHumanReadableString(padZero: false), maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index bfb7a2e3..583c8599 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -308,6 +308,7 @@ class PlayerQueue extends HookConsumerWidget { playlist: playlist, index: i, track: track, + resolveMissingDuration: true, selectionMode: selectionMode.value, selected: selectedTrackIds.value.contains(track.id), diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 5860e0d6..14ad9c60 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -90,7 +90,7 @@ class MetadataPlugin { 'id': video.id.value, 'title': video.title, 'author': video.author, - 'duration': video.duration?.inSeconds, + 'duration': video.duration?.inMicroseconds, 'description': video.description, 'uploadDate': video.uploadDate?.toIso8601String(), 'viewCount': video.engagement.viewCount, @@ -105,7 +105,7 @@ class MetadataPlugin { 'id': video.id.value, 'title': video.title, 'author': video.author, - 'duration': video.duration?.inSeconds, + 'duration': video.duration?.inMicroseconds, 'description': video.description, 'uploadDate': video.uploadDate?.toIso8601String(), 'viewCount': video.engagement.viewCount,