fix: plex track preference

This commit is contained in:
edde746
2025-11-01 12:12:16 +01:00
parent 00fe8039cc
commit 26e7dba0d0
17 changed files with 123 additions and 149 deletions
+7 -8
View File
@@ -169,15 +169,14 @@ class _SetupScreenState extends State<SetupScreen> {
if (mounted) {
context.plexClient.setClient(result.client!);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MainScreen(
client: result.client!,
userProfile: result.userProfile,
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MainScreen(client: result.client!),
),
),
);
);
}
}
return;
} else {
+8 -8
View File
@@ -32,18 +32,18 @@ class PlexHomeUser {
factory PlexHomeUser.fromJson(Map<String, dynamic> json) {
return PlexHomeUser(
id: (json['id'] as num?)?.toInt() ?? 0,
uuid: json['uuid'] as String,
title: json['title'] as String,
uuid: json['uuid'] as String? ?? '',
title: json['title'] as String? ?? 'Unknown',
username: json['username'] as String?,
email: json['email'] as String?,
friendlyName: json['friendlyName'] as String?,
thumb: json['thumb'] as String,
hasPassword: json['hasPassword'] as bool,
restricted: json['restricted'] as bool,
thumb: json['thumb'] as String? ?? '',
hasPassword: json['hasPassword'] as bool? ?? false,
restricted: json['restricted'] as bool? ?? false,
updatedAt: (json['updatedAt'] as num?)?.toInt(),
admin: json['admin'] as bool,
guest: json['guest'] as bool,
protected: json['protected'] as bool,
admin: json['admin'] as bool? ?? false,
guest: json['guest'] as bool? ?? false,
protected: json['protected'] as bool? ?? false,
);
}
+70 -2
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../models/plex_home.dart';
import '../models/plex_home_user.dart';
import '../models/plex_user_profile.dart';
import '../services/plex_auth_service.dart';
import '../services/storage_service.dart';
import '../utils/app_logger.dart';
@@ -10,11 +11,13 @@ import 'plex_client_provider.dart';
class UserProfileProvider extends ChangeNotifier {
PlexHome? _home;
PlexHomeUser? _currentUser;
PlexUserProfile? _profileSettings;
bool _isLoading = false;
String? _error;
PlexHome? get home => _home;
PlexHomeUser? get currentUser => _currentUser;
PlexUserProfile? get profileSettings => _profileSettings;
bool get isLoading => _isLoading;
String? get error => _error;
bool get hasMultipleUsers {
@@ -66,6 +69,19 @@ class UserProfileProvider extends ChangeNotifier {
// Don't set error here as it's not critical for app startup
}
}
// Fetch fresh profile settings from API
appLogger.d('UserProfileProvider: Fetching profile settings from API');
try {
await refreshProfileSettings();
} catch (e) {
appLogger.w(
'UserProfileProvider: Failed to fetch profile settings during initialization',
error: e,
);
// Don't set error here, cached profile (if any) was already loaded
}
appLogger.d('UserProfileProvider: Initialization complete');
} catch (e) {
appLogger.e(
@@ -98,9 +114,41 @@ class UserProfileProvider extends ChangeNotifier {
_currentUser = _home!.getUserByUUID(currentUserUUID);
}
// Profile settings are NOT cached - they will be fetched fresh from API
// in refreshProfileSettings()
notifyListeners();
}
/// Fetch the user's profile settings from the API
Future<void> refreshProfileSettings() async {
if (_authService == null || _storageService == null) {
appLogger.w('refreshProfileSettings: Services not initialized, skipping');
return;
}
appLogger.d('Fetching user profile settings from Plex API');
try {
final currentToken = _storageService!.getPlexToken();
if (currentToken == null) {
appLogger.w(
'refreshProfileSettings: No Plex token available, cannot fetch profile',
);
return;
}
final profile = await _authService!.getUserProfile(currentToken);
_profileSettings = profile;
appLogger.i('Successfully fetched user profile settings from API');
notifyListeners();
} catch (e) {
appLogger.w('Failed to fetch user profile settings from API', error: e);
// Don't set error state, profile will remain null or keep existing value
}
}
Future<void> loadHomeUsers({bool forceRefresh = false}) async {
appLogger.d('loadHomeUsers called - forceRefresh: $forceRefresh');
@@ -263,8 +311,17 @@ class UserProfileProvider extends ChangeNotifier {
// Update current user
_currentUser = user;
// Save user profile
await _storageService!.saveUserProfile(switchResponse.profile.toJson());
// Update user profile settings (fresh from API)
_profileSettings = switchResponse.profile;
appLogger.d(
'Updated profile settings for user: ${user.displayName}',
error: {
'defaultAudioLanguage':
_profileSettings?.defaultAudioLanguage ?? 'not set',
'defaultSubtitleLanguage':
_profileSettings?.defaultSubtitleLanguage ?? 'not set',
},
);
// Update PlexClient with proper server access token
if (clientProvider != null) {
@@ -322,6 +379,7 @@ class UserProfileProvider extends ChangeNotifier {
// Clear user-specific provider state but keep services for future sign-ins
_home = null;
_currentUser = null;
_profileSettings = null;
_onDataInvalidationRequested = null;
_clearError();
@@ -346,6 +404,7 @@ class UserProfileProvider extends ChangeNotifier {
// Clear cached data from previous server (both memory and storage)
_home = null;
_currentUser = null;
_profileSettings = null;
_clearError();
// Re-initialize services with current storage state
@@ -401,6 +460,15 @@ class UserProfileProvider extends ChangeNotifier {
appLogger.w(
'UserProfileProvider: Cannot perform complete profile switch - no context provided',
);
// Still try to fetch profile settings even without full switch
try {
await refreshProfileSettings();
} catch (e) {
appLogger.w(
'UserProfileProvider: Failed to refresh profile settings for new server',
error: e,
);
}
}
} catch (e) {
appLogger.w(
+3 -14
View File
@@ -4,7 +4,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../providers/plex_client_provider.dart';
import '../services/storage_service.dart';
import '../services/plex_auth_service.dart';
@@ -22,10 +21,9 @@ import '../utils/video_player_navigation.dart';
import 'auth_screen.dart';
class DiscoverScreen extends StatefulWidget {
final PlexUserProfile? userProfile;
final VoidCallback? onBecameVisible;
const DiscoverScreen({super.key, this.userProfile, this.onBecameVisible});
const DiscoverScreen({super.key, this.onBecameVisible});
@override
State<DiscoverScreen> createState() => _DiscoverScreenState();
@@ -569,11 +567,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
if (client == null) return;
appLogger.d('Navigating to VideoPlayerScreen for: ${heroItem.title}');
navigateToVideoPlayer(
context,
metadata: heroItem,
userProfile: widget.userProfile,
);
navigateToVideoPlayer(context, metadata: heroItem);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
@@ -898,11 +892,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
if (client == null) return;
appLogger.d('Playing: ${heroItem.title}');
navigateToVideoPlayer(
context,
metadata: heroItem,
userProfile: widget.userProfile,
);
navigateToVideoPlayer(context, metadata: heroItem);
},
borderRadius: BorderRadius.circular(24),
child: Container(
@@ -999,7 +989,6 @@ class _DiscoverScreenState extends State<DiscoverScreen>
width: cardWidth,
height: cardHeight,
onRefresh: updateItem,
userProfile: widget.userProfile,
),
);
},
+1 -5
View File
@@ -4,7 +4,6 @@ import '../client/plex_client.dart';
import '../models/plex_library.dart';
import '../models/plex_metadata.dart';
import '../models/plex_filter.dart';
import '../models/plex_user_profile.dart';
import '../providers/plex_client_provider.dart';
import '../utils/provider_extensions.dart';
import '../widgets/media_card.dart';
@@ -16,9 +15,7 @@ import '../mixins/item_updatable.dart';
import '../theme/theme_helper.dart';
class LibrariesScreen extends StatefulWidget {
final PlexUserProfile? userProfile;
const LibrariesScreen({super.key, this.userProfile});
const LibrariesScreen({super.key});
@override
State<LibrariesScreen> createState() => _LibrariesScreenState();
@@ -436,7 +433,6 @@ class _LibrariesScreenState extends State<LibrariesScreen>
key: Key(item.ratingKey),
item: item,
onRefresh: updateItem,
userProfile: widget.userProfile,
);
}, childCount: _items.length),
),
+3 -6
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import '../client/plex_client.dart';
import '../models/plex_user_profile.dart';
import '../utils/app_logger.dart';
import '../utils/provider_extensions.dart';
import '../main.dart';
@@ -12,9 +11,8 @@ import 'settings_screen.dart';
class MainScreen extends StatefulWidget {
final PlexClient client;
final PlexUserProfile? userProfile;
const MainScreen({super.key, required this.client, this.userProfile});
const MainScreen({super.key, required this.client});
@override
State<MainScreen> createState() => _MainScreenState();
@@ -36,11 +34,10 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
_screens = [
DiscoverScreen(
key: _discoverKey,
userProfile: widget.userProfile,
onBecameVisible: _onDiscoverBecameVisible,
),
LibrariesScreen(key: _librariesKey, userProfile: widget.userProfile),
SearchScreen(key: _searchKey, userProfile: widget.userProfile),
LibrariesScreen(key: _librariesKey),
SearchScreen(key: _searchKey),
SettingsScreen(key: _settingsKey),
];
+2 -14
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../providers/plex_client_provider.dart';
import '../widgets/desktop_app_bar.dart';
import '../widgets/app_bar_back_button.dart';
@@ -15,13 +14,8 @@ import 'season_detail_screen.dart';
class MediaDetailScreen extends StatefulWidget {
final PlexMetadata metadata;
final PlexUserProfile? userProfile;
const MediaDetailScreen({
super.key,
required this.metadata,
this.userProfile,
});
const MediaDetailScreen({super.key, required this.metadata});
@override
State<MediaDetailScreen> createState() => _MediaDetailScreenState();
@@ -214,11 +208,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
if (client == null) return;
appLogger.d('Playing first episode: ${firstEpisode.title}');
await navigateToVideoPlayer(
context,
metadata: firstEpisode,
userProfile: widget.userProfile,
);
await navigateToVideoPlayer(context, metadata: firstEpisode);
appLogger.d('Returned from playback, refreshing metadata');
// Refresh metadata when returning from video player
_loadFullMetadata();
@@ -541,7 +531,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
await navigateToVideoPlayer(
context,
metadata: _onDeckEpisode!,
userProfile: widget.userProfile,
);
appLogger.d(
'Returned from playback, refreshing metadata',
@@ -562,7 +551,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
await navigateToVideoPlayer(
context,
metadata: metadata,
userProfile: widget.userProfile,
);
appLogger.d(
'Returned from playback, refreshing metadata',
+1 -5
View File
@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../utils/provider_extensions.dart';
import '../widgets/media_card.dart';
import '../widgets/desktop_app_bar.dart';
@@ -10,9 +9,7 @@ import '../mixins/refreshable.dart';
import '../mixins/item_updatable.dart';
class SearchScreen extends StatefulWidget {
final PlexUserProfile? userProfile;
const SearchScreen({super.key, this.userProfile});
const SearchScreen({super.key});
@override
State<SearchScreen> createState() => _SearchScreenState();
@@ -228,7 +225,6 @@ class _SearchScreenState extends State<SearchScreen>
key: Key(item.ratingKey),
item: item,
onRefresh: updateItem,
userProfile: widget.userProfile,
);
}, childCount: _searchResults.length),
),
+2 -8
View File
@@ -3,7 +3,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../providers/plex_client_provider.dart';
import '../utils/provider_extensions.dart';
import '../utils/video_player_navigation.dart';
@@ -14,9 +13,8 @@ import '../theme/theme_helper.dart';
class SeasonDetailScreen extends StatefulWidget {
final PlexMetadata season;
final PlexUserProfile? userProfile;
const SeasonDetailScreen({super.key, required this.season, this.userProfile});
const SeasonDetailScreen({super.key, required this.season});
@override
State<SeasonDetailScreen> createState() => _SeasonDetailScreenState();
@@ -136,11 +134,7 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
metadata: episode,
onRefresh: updateItem,
onTap: () async {
await navigateToVideoPlayer(
context,
metadata: episode,
userProfile: widget.userProfile,
);
await navigateToVideoPlayer(context, metadata: episode);
// Refresh episodes when returning from video player
_loadEpisodes();
},
+7 -11
View File
@@ -20,7 +20,6 @@ class VideoPlayerScreen extends StatefulWidget {
final AudioTrack? preferredAudioTrack;
final SubtitleTrack? preferredSubtitleTrack;
final double? preferredPlaybackRate;
final PlexUserProfile? userProfile;
const VideoPlayerScreen({
super.key,
@@ -28,7 +27,6 @@ class VideoPlayerScreen extends StatefulWidget {
this.preferredAudioTrack,
this.preferredSubtitleTrack,
this.preferredPlaybackRate,
this.userProfile,
});
@override
@@ -52,9 +50,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
super.initState();
appLogger.d('VideoPlayerScreen initialized for: ${widget.metadata.title}');
if (widget.userProfile != null) {
appLogger.d('Using user profile for track selection');
}
if (widget.preferredAudioTrack != null) {
appLogger.d(
'Preferred audio track: ${widget.preferredAudioTrack!.title ?? widget.preferredAudioTrack!.id} (${widget.preferredAudioTrack!.language ?? "unknown"})',
@@ -477,6 +472,9 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
Future<void> processTracks(Tracks tracks) async {
appLogger.d('Starting track selection process');
// Get profile settings for track selection
final profileSettings = context.profileSettings;
// Get real tracks (excluding auto and no)
final realAudioTracks = tracks.audio
.where((t) => t.id != 'auto' && t.id != 'no')
@@ -523,11 +521,11 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
}
// Priority 2: If no preferred track matched, try user profile preferences
if (trackToSelect == null && widget.userProfile != null) {
if (trackToSelect == null && profileSettings != null) {
appLogger.d('Priority 2: Checking user profile preferences');
trackToSelect = _findAudioTrackByProfile(
realAudioTracks,
widget.userProfile!,
profileSettings,
);
} else if (trackToSelect == null) {
appLogger.d('Priority 2: No user profile available');
@@ -585,12 +583,12 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
// Priority 2: If no preferred match, apply user profile preferences
if (subtitleToSelect == null &&
widget.userProfile != null &&
profileSettings != null &&
realSubtitleTracks.isNotEmpty) {
appLogger.d('Priority 2: Checking user profile preferences');
subtitleToSelect = _findSubtitleTrackByProfile(
realSubtitleTracks,
widget.userProfile!,
profileSettings,
);
} else if (subtitleToSelect == null && realSubtitleTracks.isNotEmpty) {
appLogger.d('Priority 2: No user profile available');
@@ -720,7 +718,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
navigateToVideoPlayer(
context,
metadata: episodeMetadata,
userProfile: widget.userProfile,
usePushReplacement: true,
);
}
@@ -745,7 +742,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
preferredAudioTrack: currentAudioTrack,
preferredSubtitleTrack: currentSubtitleTrack,
preferredPlaybackRate: currentRate,
userProfile: widget.userProfile,
usePushReplacement: true,
);
}
+5 -38
View File
@@ -87,7 +87,7 @@ class ServerConnectionService {
// Fetch user profile if requested
PlexUserProfile? userProfile;
if (fetchUserProfile && plexToken != null) {
userProfile = await _fetchAndCacheUserProfile(plexToken);
userProfile = await _fetchUserProfile(plexToken);
}
// Return success result
@@ -123,17 +123,15 @@ class ServerConnectionService {
}
}
/// Fetch user profile from Plex API and cache it locally
static Future<PlexUserProfile?> _fetchAndCacheUserProfile(
String plexToken,
) async {
/// Fetch user profile from Plex API
static Future<PlexUserProfile?> _fetchUserProfile(String plexToken) async {
appLogger.d('Fetching user profile from Plex API');
try {
final authService = await PlexAuthService.create();
final profile = await authService.getUserProfile(plexToken);
appLogger.i(
'Successfully fetched user profile',
'Successfully fetched user profile from API',
error: {
'autoSelectAudio': profile.autoSelectAudio,
'defaultAudioLanguage': profile.defaultAudioLanguage ?? 'not set',
@@ -144,40 +142,9 @@ class ServerConnectionService {
},
);
// Cache the profile
final storage = await StorageService.getInstance();
await storage.saveUserProfile(profile.toJson());
appLogger.d('User profile cached locally');
return profile;
} catch (e) {
appLogger.w(
'Failed to fetch user profile from API, attempting to load from cache',
error: e,
);
// Failed to fetch profile, try to load from cache
final storage = await StorageService.getInstance();
final cachedProfile = storage.getUserProfile();
if (cachedProfile != null) {
final profile = PlexUserProfile.fromJson(cachedProfile);
appLogger.i(
'Loaded user profile from cache',
error: {
'autoSelectAudio': profile.autoSelectAudio,
'defaultAudioLanguage': profile.defaultAudioLanguage ?? 'not set',
'autoSelectSubtitle': profile.autoSelectSubtitle,
'defaultSubtitleLanguage':
profile.defaultSubtitleLanguage ?? 'not set',
'defaultSubtitleForced': profile.defaultSubtitleForced,
},
);
return profile;
}
appLogger.w(
'No cached user profile available, track selection will use defaults',
);
appLogger.w('Failed to fetch user profile from API', error: e);
return null;
}
}
+1 -11
View File
@@ -11,14 +11,4 @@ import 'package:logger/logger.dart';
/// appLogger.w('Warning message');
/// appLogger.e('Error message', error: e, stackTrace: stackTrace);
/// ```
final appLogger = Logger(
printer: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
),
level: Level.debug,
);
final appLogger = Logger(printer: SimplePrinter(), level: Level.debug);
+4
View File
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import '../providers/plex_client_provider.dart';
import '../providers/user_profile_provider.dart';
import '../client/plex_client.dart';
import '../models/plex_user_profile.dart';
extension ProviderExtensions on BuildContext {
PlexClientProvider get plexClient =>
@@ -22,4 +23,7 @@ extension ProviderExtensions on BuildContext {
// Null-safe client access
PlexClient get clientSafe => plexClient.client!;
// Direct profile settings access (nullable)
PlexUserProfile? get profileSettings => userProfile.profileSettings;
}
-4
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../screens/video_player_screen.dart';
/// Navigates to the VideoPlayerScreen with instant transitions to prevent white flash.
@@ -13,7 +12,6 @@ import '../screens/video_player_screen.dart';
/// Parameters:
/// - [context]: The build context for navigation
/// - [metadata]: The Plex metadata for the content to play
/// - [userProfile]: Optional user profile for track selection preferences
/// - [preferredAudioTrack]: Optional audio track to select on playback start
/// - [preferredSubtitleTrack]: Optional subtitle track to select on playback start
/// - [preferredPlaybackRate]: Optional playback speed to set on playback start
@@ -25,7 +23,6 @@ import '../screens/video_player_screen.dart';
Future<bool?> navigateToVideoPlayer(
BuildContext context, {
required PlexMetadata metadata,
PlexUserProfile? userProfile,
AudioTrack? preferredAudioTrack,
SubtitleTrack? preferredSubtitleTrack,
double? preferredPlaybackRate,
@@ -34,7 +31,6 @@ Future<bool?> navigateToVideoPlayer(
final route = PageRouteBuilder<bool>(
pageBuilder: (context, animation, secondaryAnimation) => VideoPlayerScreen(
metadata: metadata,
userProfile: userProfile,
preferredAudioTrack: preferredAudioTrack,
preferredSubtitleTrack: preferredSubtitleTrack,
preferredPlaybackRate: preferredPlaybackRate,
+2 -12
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import '../models/plex_metadata.dart';
import '../models/plex_user_profile.dart';
import '../providers/plex_client_provider.dart';
import '../utils/provider_extensions.dart';
import '../utils/video_player_navigation.dart';
@@ -16,7 +15,6 @@ class MediaCard extends StatefulWidget {
final double? width;
final double? height;
final void Function(String ratingKey)? onRefresh;
final PlexUserProfile? userProfile;
const MediaCard({
super.key,
@@ -24,7 +22,6 @@ class MediaCard extends StatefulWidget {
this.width,
this.height,
this.onRefresh,
this.userProfile,
});
@override
@@ -47,7 +44,6 @@ class _MediaCardState extends State<MediaCard>
final result = await navigateToVideoPlayer(
context,
metadata: widget.item,
userProfile: widget.userProfile,
);
// Refresh parent screen if result indicates it's needed
if (result == true) {
@@ -58,10 +54,7 @@ class _MediaCardState extends State<MediaCard>
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SeasonDetailScreen(
season: widget.item,
userProfile: widget.userProfile,
),
builder: (context) => SeasonDetailScreen(season: widget.item),
),
);
// Season screen doesn't return a refresh flag, but we can refresh anyway
@@ -71,10 +64,7 @@ class _MediaCardState extends State<MediaCard>
final result = await Navigator.push<bool>(
context,
MaterialPageRoute(
builder: (context) => MediaDetailScreen(
metadata: widget.item,
userProfile: widget.userProfile,
),
builder: (context) => MediaDetailScreen(metadata: widget.item),
),
);
// Refresh parent screen if result indicates it's needed
+6 -2
View File
@@ -1097,7 +1097,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
initialData: widget.player.state.track,
builder: (context, selectedSnapshot) {
// Use snapshot data or fall back to current state
final currentTrack = selectedSnapshot.data ?? widget.player.state.track;
final currentTrack =
selectedSnapshot.data ??
widget.player.state.track;
final selectedTrack = currentTrack.audio;
final selectedId = selectedTrack?.id;
@@ -1221,7 +1223,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
initialData: widget.player.state.track,
builder: (context, selectedSnapshot) {
// Use snapshot data or fall back to current state
final currentTrack = selectedSnapshot.data ?? widget.player.state.track;
final currentTrack =
selectedSnapshot.data ??
widget.player.state.track;
final selectedTrack = currentTrack.subtitle;
final selectedId = selectedTrack?.id;
final isOffSelected = selectedId == 'no';