fix(tvos): hide unsupported playback options

This commit is contained in:
edde746
2026-06-13 10:13:02 +02:00
parent 90662e2173
commit 85d9b6ebab
8 changed files with 60 additions and 6 deletions
@@ -51,9 +51,9 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
children: [
SettingsSectionHeader(t.settings.player),
if (Platform.isAndroid) _playerBackendSelector(),
_externalPlayerTile(),
if (PlatformDetector.supportsExternalPlayers()) _externalPlayerTile(),
_hardwareDecodingTile(),
if ((Platform.isAndroid && !PlatformDetector.isTV()) || Platform.isIOS || Platform.isMacOS) _autoPipTile(),
if (PlatformDetector.supportsPictureInPicture()) _autoPipTile(),
if (Platform.isAndroid) _matchContentFrameRateTile(),
if (Platform.isWindows) _matchRefreshRateTile(),
if (Platform.isWindows) _matchDynamicRangeTile(),
@@ -10,6 +10,7 @@ import '../media/media_server_client.dart';
import '../media/watch_progress.dart';
import '../models/external_player_models.dart';
import '../utils/app_logger.dart';
import '../utils/platform_detector.dart';
import '../utils/snackbar_helper.dart';
import '../utils/watch_state_notifier.dart';
import '../i18n/strings.g.dart';
@@ -68,6 +69,8 @@ class ExternalPlayerService {
String? mediaSourceId,
String? videoUrl,
}) async {
if (!PlatformDetector.supportsExternalPlayers()) return false;
try {
String resolvedUrl;
+14 -1
View File
@@ -156,6 +156,19 @@ class _AutoPipPref extends Pref<bool> {
Future<void> writeTo(BaseSharedPreferencesService svc, bool value) => svc.writeBool(key, value);
}
class _UseExternalPlayerPref extends Pref<bool> {
const _UseExternalPlayerPref() : super('use_external_player');
@override
bool readFrom(BaseSharedPreferencesService svc) {
if (!PlatformDetector.supportsExternalPlayers()) return false;
return svc.prefs.getBool(key) ?? false;
}
@override
Future<void> writeTo(BaseSharedPreferencesService svc, bool value) => svc.writeBool(key, value);
}
String? _trimEmptyAsNull(String? v) {
final t = v?.trim();
return (t == null || t.isEmpty) ? null : t;
@@ -372,7 +385,7 @@ class SettingsService extends BaseSharedPreferencesService {
static const showNavBarLabels = BoolPref('show_nav_bar_labels', defaultValue: true);
static const globalShaderPreset = StringPref('global_shader_preset', defaultValue: 'none');
static const requireProfileSelectionOnOpen = BoolPref('require_profile_selection_on_open');
static const useExternalPlayer = BoolPref('use_external_player');
static const useExternalPlayer = _UseExternalPlayerPref();
static const forceTvMode = BoolPref('force_tv_mode');
static const visualEffects = EnumPref<VisualEffectsSetting>(
'visual_effects',
+9
View File
@@ -205,6 +205,15 @@ class PlatformDetector {
return Platform.isWindows || Platform.isMacOS || Platform.isLinux;
}
static bool supportsExternalPlayers() {
if (TvDetectionService._tvosBuild || isAppleTV()) return false;
return Platform.isAndroid || Platform.isIOS || Platform.isMacOS || Platform.isLinux || Platform.isWindows;
}
static bool supportsPictureInPicture() {
return !TvDetectionService._tvosBuild && !isTV() && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
}
/// Detects if the device is likely a tablet based on screen size
/// Uses diagonal screen size to determine if device is a tablet
static bool isTablet(BuildContext context) {
+2 -1
View File
@@ -16,6 +16,7 @@ import '../services/external_player_service.dart';
import '../services/offline_watch_sync_service.dart';
import '../services/settings_service.dart';
import 'app_logger.dart';
import 'platform_detector.dart';
const String kVideoPlayerRouteName = '/video_player';
@@ -185,7 +186,7 @@ Future<bool?> navigateToVideoPlayer(
// Check if external player is enabled
try {
final settingsService = await SettingsService.getInstance();
if (settingsService.read(SettingsService.useExternalPlayer)) {
if (PlatformDetector.supportsExternalPlayers() && settingsService.read(SettingsService.useExternalPlayer)) {
bool launched = false;
if (isOffline) {
+4 -1
View File
@@ -403,7 +403,8 @@ class MediaContextMenuState extends State<MediaContextMenu> {
menuActions.add(_MenuAction(value: 'fileinfo', icon: Symbols.info_rounded, label: t.mediaMenu.fileInfo));
}
if (mediaKind == MediaKind.episode || mediaKind == MediaKind.movie) {
if (PlatformDetector.supportsExternalPlayers() &&
(mediaKind == MediaKind.episode || mediaKind == MediaKind.movie)) {
menuActions.add(
_MenuAction(
value: 'play_external',
@@ -1226,6 +1227,8 @@ class MediaContextMenuState extends State<MediaContextMenu> {
/// Handle play in external player action
Future<void> _handlePlayExternal(BuildContext context) async {
if (!PlatformDetector.supportsExternalPlayers()) return;
final item = _mediaItem!;
// Check if the item is downloaded and use local file path if available
@@ -145,7 +145,7 @@ extension _PlexVideoControlsVisibilityMethods on _PlexVideoControlsState {
}
Future<void> _checkPipSupport() async {
if (!Platform.isAndroid && !Platform.isIOS && !Platform.isMacOS) {
if (!PlatformDetector.supportsPictureInPicture()) {
return;
}
+25
View File
@@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/models/hotkey_model.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/services/trackers/tracker_constants.dart';
import 'package:plezy/utils/platform_detector.dart';
import '../test_helpers/prefs.dart';
@@ -12,6 +13,10 @@ void main() {
SettingsService.resetForTesting();
});
tearDown(() {
TvDetectionService.debugSetAppleTVOverride(null);
});
group('SettingsService.parseMpvConfigText', () {
test('parses plain key=value lines', () {
final out = SettingsService.parseMpvConfigText('hwdec=auto\nvolume=100');
@@ -82,6 +87,26 @@ void main() {
});
});
group('SettingsService platform gates', () {
test('forces external player off on Apple TV even when stored enabled', () async {
final settings = await SettingsService.getInstance();
await settings.write(SettingsService.useExternalPlayer, true);
TvDetectionService.debugSetAppleTVOverride(true);
expect(settings.read(SettingsService.useExternalPlayer), isFalse);
});
test('forces auto PiP off on Apple TV even when stored enabled', () async {
final settings = await SettingsService.getInstance();
await settings.write(SettingsService.autoPip, true);
TvDetectionService.debugSetAppleTVOverride(true);
expect(settings.read(SettingsService.autoPip), isFalse);
});
});
group('SettingsService companion remote prefs', () {
test('last manual host address trims whitespace and drops blanks', () async {
final settings = await SettingsService.getInstance();