From acc7149537e8f1e199fad1dbe2a47159cfc8bdca Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 11 May 2026 19:11:44 -0400 Subject: [PATCH] diag(flutter): instrument playTracks + cache appdir; fix CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI fixes: - artist_detail_screen.dart: drop unnecessary foundation import (debugPrint comes from material) and unused metadata_prefetcher import. Playback timing visibility (so we can stop guessing where the lag lives): - playTracks now logs serverUrl / token / configure / setQueue / play() returned, each stage in milliseconds. The next time you tap play, we'll see exactly where the seconds go. - setQueueFromTracks adds two more measurements: total source-build time across all tracks, and setAudioSources duration. Small concrete win: - audio_handler caches the application cache dir path on first use (already cached in _maybeRegisterStreamCache; now also used in _buildAudioSource for the LockCachingAudioSource path). One less platform channel hit per track on cache-miss queue builds. Once we see real numbers we can decide whether the fix is to build sources lazily (initial source first → play → background-add the rest), pre-warm the audio handler at app start so playTracks skips serverUrl + token reads entirely, or something else. --- .../lib/library/artist_detail_screen.dart | 2 -- flutter_client/lib/player/audio_handler.dart | 10 ++++++++-- flutter_client/lib/player/player_provider.dart | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/flutter_client/lib/library/artist_detail_screen.dart b/flutter_client/lib/library/artist_detail_screen.dart index 004ea9e1..784776f1 100644 --- a/flutter_client/lib/library/artist_detail_screen.dart +++ b/flutter_client/lib/library/artist_detail_screen.dart @@ -1,10 +1,8 @@ -import 'package:flutter/foundation.dart' show debugPrint; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../api/endpoints/likes.dart'; -import '../cache/metadata_prefetcher.dart'; import '../likes/like_button.dart'; import '../models/album.dart'; import '../models/artist.dart'; diff --git a/flutter_client/lib/player/audio_handler.dart b/flutter_client/lib/player/audio_handler.dart index 00ec6326..0490f5ae 100644 --- a/flutter_client/lib/player/audio_handler.dart +++ b/flutter_client/lib/player/audio_handler.dart @@ -80,12 +80,18 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl debugPrint('audio_handler.setQueueFromTracks: ' '_baseUrl="$_baseUrl" trackCount=${tracks.length}'); + final sw = Stopwatch()..start(); final sources = await Future.wait(tracks.map(_buildAudioSource)); + debugPrint('audio_handler: built ${sources.length} sources in ' + '${sw.elapsedMilliseconds}ms'); + sw.reset(); + sw.start(); await _player.setAudioSources( sources, initialIndex: initialIndex, ); + debugPrint('audio_handler: setAudioSources ${sw.elapsedMilliseconds}ms'); // Kick the cover fetch for the initial item — async, doesn't block // playback. Subsequent track changes are handled by the @@ -143,8 +149,8 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl // pin / Download buttons cover the index path; LockCaching handles // the network optimization. if (mgr != null) { - final cacheDir = await getApplicationCacheDirectory(); - final cacheFile = File('${cacheDir.path}/audio_cache/${t.id}.mp3'); + _cacheDirPath ??= (await getApplicationCacheDirectory()).path; + final cacheFile = File('${_cacheDirPath!}/audio_cache/${t.id}.mp3'); debugPrint('audio_handler: cache miss for track.id=${t.id}, ' 'using LockCachingAudioSource → ${cacheFile.path}'); // ignore: experimental_member_use diff --git a/flutter_client/lib/player/player_provider.dart b/flutter_client/lib/player/player_provider.dart index baca1af3..77d82319 100644 --- a/flutter_client/lib/player/player_provider.dart +++ b/flutter_client/lib/player/player_provider.dart @@ -53,8 +53,22 @@ class PlayerActions { final Ref _ref; Future playTracks(List tracks, {int initialIndex = 0}) async { + // Stage-by-stage timing so we can see exactly where the + // "tap → audio" delay lives. Look for "playTracks: ..." lines + // in the next log capture; each stage label is followed by its + // elapsed millis since the previous stage. + final sw = Stopwatch()..start(); + int lap() { + final ms = sw.elapsedMilliseconds; + sw.reset(); + sw.start(); + return ms; + } + final url = await _ref.read(serverUrlProvider.future); + debugPrint('playTracks: serverUrl ${lap()}ms'); final token = await _ref.read(secureStorageProvider).read(key: 'session_token'); + debugPrint('playTracks: token ${lap()}ms'); final cache = _ref.read(albumCoverCacheProvider); final audioCache = _ref.read(audioCacheManagerProvider); final h = _ref.read(audioHandlerProvider) @@ -64,8 +78,11 @@ class PlayerActions { coverCache: cache, audioCacheManager: audioCache, ); + debugPrint('playTracks: configure ${lap()}ms'); await h.setQueueFromTracks(tracks, initialIndex: initialIndex); + debugPrint('playTracks: setQueue (${tracks.length} tracks) ${lap()}ms'); await h.play(); + debugPrint('playTracks: play() returned ${lap()}ms'); } Future playNext(TrackRef track) async {