diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 018fcc74..9125b209 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -547,8 +547,8 @@ class _VideoSettingsSheetState extends State { }, ), - // Audio Passthrough (desktop and Apple TV) - if (PlatformDetector.isDesktopOS() || PlatformDetector.isAppleTV()) + // Audio Passthrough (desktop and Android TV) + if (PlatformDetector.supportsAudioPassthrough()) _SettingsToggleItem( pref: SettingsService.audioPassthrough, icon: Symbols.surround_sound_rounded, diff --git a/test/widgets/video_settings_sheet_test.dart b/test/widgets/video_settings_sheet_test.dart new file mode 100644 index 00000000..be81c8de --- /dev/null +++ b/test/widgets/video_settings_sheet_test.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/mpv/models.dart'; +import 'package:plezy/mpv/player/player.dart'; +import 'package:plezy/mpv/player/player_state.dart'; +import 'package:plezy/mpv/player/player_streams.dart'; +import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/theme/mono_tokens.dart'; +import 'package:plezy/utils/platform_detector.dart'; +import 'package:plezy/widgets/video_controls/sheets/video_settings_sheet.dart'; + +import '../test_helpers/prefs.dart'; + +const _testTokens = MonoTokens( + radiusSm: 4, + radiusMd: 8, + space: 8, + fast: Duration(milliseconds: 100), + normal: Duration(milliseconds: 200), + slow: Duration(milliseconds: 300), + bg: Colors.black, + surface: Color(0xFF111111), + outline: Color(0xFF333333), + text: Colors.white, + textMuted: Color(0xFFAAAAAA), + splashFactory: NoSplash.splashFactory, +); + +void main() { + setUp(() async { + resetSharedPreferencesForTest(); + SettingsService.resetForTesting(); + await SettingsService.getInstance(); + TvDetectionService.debugSetAppleTVOverride(null); + }); + + tearDown(() { + TvDetectionService.debugSetAppleTVOverride(null); + }); + + testWidgets('shows audio passthrough on supported TV-style surfaces', (tester) async { + await _pumpSheet(tester); + + await tester.scrollUntilVisible(find.text('Audio Passthrough'), 500, scrollable: find.byType(Scrollable).first); + + expect(find.text('Audio Passthrough'), findsOneWidget); + }); + + testWidgets('hides audio passthrough on Apple TV', (tester) async { + TvDetectionService.debugSetAppleTVOverride(true); + + await _pumpSheet(tester); + await tester.drag(find.byType(ListView), const Offset(0, -800)); + await tester.pumpAndSettle(); + + expect(find.text('Audio Passthrough'), findsNothing); + }); +} + +Future _pumpSheet(WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold( + body: SizedBox( + width: 900, + height: 700, + child: VideoSettingsSheet( + player: _FakeSettingsPlayer(), + audioSyncOffset: 0, + subtitleSyncOffset: 0, + canControl: false, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); +} + +class _FakeSettingsPlayer implements Player { + _FakeSettingsPlayer() + : _streams = PlayerStreams( + playing: const Stream.empty(), + completed: const Stream.empty(), + buffering: const Stream.empty(), + position: const Stream.empty(), + duration: const Stream.empty(), + seekable: const Stream.empty(), + buffer: const Stream.empty(), + volume: const Stream.empty(), + rate: const Stream.empty(), + tracks: const Stream.empty(), + track: const Stream.empty(), + log: const Stream.empty(), + error: const Stream.empty(), + audioDevice: const Stream.empty(), + audioDevices: const Stream>.empty(), + bufferRanges: const Stream>.empty(), + playbackRestart: const Stream.empty(), + backendSwitched: const Stream.empty(), + ); + + final PlayerStreams _streams; + + @override + PlayerState get state => const PlayerState(); + + @override + PlayerStreams get streams => _streams; + + @override + String get playerType => 'exoplayer'; + + @override + Future setAudioPassthrough(bool enabled) async {} + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +}