From 6a3a85f7c9dbd5e86bb1ea93169aa02a7797c438 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 1 Aug 2026 15:38:40 +0700 Subject: [PATCH] fix(player): refresh lyrics after automatic track changes --- lib/screens/now_playing_screen.dart | 8 +- test/now_playing_lyrics_transition_test.dart | 86 ++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 test/now_playing_lyrics_transition_test.dart diff --git a/lib/screens/now_playing_screen.dart b/lib/screens/now_playing_screen.dart index d682e95a..206c094e 100644 --- a/lib/screens/now_playing_screen.dart +++ b/lib/screens/now_playing_screen.dart @@ -186,7 +186,13 @@ class _NowPlayingScreenState extends ConsumerState { super.initState(); _mediaItemSub = ref.listenManual>( currentMediaItemProvider, - (previous, next) => _loadMetadataForItem(next.value), + (previous, next) => _loadMetadataForItem( + next.value, + // When automatic playback advances while Lyrics is already visible, + // onPageChanged will not run again. Inspect an unresolved SAF URI now + // instead of leaving the new track with an empty Lyrics page. + inspectUnresolvedContentUri: _currentPage == 1, + ), ); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; diff --git a/test/now_playing_lyrics_transition_test.dart b/test/now_playing_lyrics_transition_test.dart new file mode 100644 index 00000000..6f672737 --- /dev/null +++ b/test/now_playing_lyrics_transition_test.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/providers/music_player_provider.dart'; +import 'package:spotiflac_android/screens/now_playing_screen.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + const backendChannel = MethodChannel('com.zarz.spotiflac/backend'); + late StreamController mediaItems; + + setUp(() { + mediaItems = StreamController.broadcast(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(backendChannel, (call) async { + if (call.method != 'readFileMetadata') { + fail('Unexpected platform call: ${call.method}'); + } + final arguments = (call.arguments as Map).cast(); + final path = arguments['file_path']?.toString() ?? ''; + return jsonEncode({ + 'title': path.endsWith('/second.flac') ? 'Second' : 'First', + 'lyrics': path.endsWith('/second.flac') + ? '[00:01.00]Second lyric' + : '[00:01.00]First lyric', + }); + }); + }); + + tearDown(() async { + await mediaItems.close(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(backendChannel, null); + }); + + MediaItem item(String id) => MediaItem( + id: id, + title: id == 'first' ? 'First' : 'Second', + artist: 'Artist', + duration: const Duration(minutes: 3), + extras: {'source': 'content://library/$id.flac'}, + ); + + testWidgets( + 'automatic SAF track change refreshes lyrics while Lyrics page is active', + (tester) async { + tester.view.physicalSize = const Size(1080, 1920); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + currentMediaItemProvider.overrideWith((ref) => mediaItems.stream), + playbackStateProvider.overrideWith((ref) => const Stream.empty()), + playQueueProvider.overrideWith((ref) => const Stream.empty()), + ], + child: const MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: NowPlayingScreen(), + ), + ), + ); + + mediaItems.add(item('first')); + await tester.pumpAndSettle(); + await tester.drag(find.byType(PageView), const Offset(-700, 0)); + await tester.pumpAndSettle(); + expect(find.text('First lyric'), findsOneWidget); + + mediaItems.add(item('second')); + await tester.pumpAndSettle(); + + expect(find.text('Second lyric'), findsOneWidget); + expect(find.text('First lyric'), findsNothing); + }, + ); +}