feat(player): add external video player auto-launch feature with preferences

- Implement player preferences management with localStorage and events
- Enable auto-launch and confirmation dialogs in LocalPlayerLauncher component
- Learn user preferences from manual player selections dynamically
- Integrate settings UI for video player configuration and real-time updates
- Support platform-aware player listing and reset to default preferences
- Provide clear current configuration display and toggle switches in settings
- Enhance user experience with smart defaults and customizable workflow
- Maintain backward compatibility with manual player selection option
- Add custom event system for cross-component synchronization of preferences
- Optimize player detection and launching logic to respect user settings
This commit is contained in:
tigeren 2025-10-04 16:59:17 +00:00
parent d048cb3b82
commit 76154123b8
5 changed files with 649 additions and 8 deletions

View File

@ -0,0 +1,134 @@
# External Video Player Auto-Launch Feature Implementation
## Overview
Successfully implemented a seamless auto-launch system for external video players, allowing users to configure preferences once and automatically launch videos without manual selection each time.
## Features Implemented
### 1. Player Preferences Management (`/src/lib/player-preferences.ts`)
- **localStorage-based storage** for user preferences
- **Cross-component state management** with custom events
- **Default configurations** with safe fallbacks
- **Platform-specific player detection**
Key preferences:
- `preferredPlayer`: User's chosen video player (null = manual selection)
- `autoLaunch`: Enable/disable automatic launching
- `confirmBeforeLaunch`: Show confirmation dialog before auto-launch
- `rememberChoice`: Offer to remember manual selections
### 2. Enhanced LocalPlayerLauncher (`/src/components/local-player-launcher.tsx`)
- **Auto-launch detection** on component mount
- **Confirmation dialogs** for auto-launch (when enabled)
- **Smart preference learning** from manual selections
- **Fallback to manual selection** when no preferences set
New workflow:
1. Component loads → Check user preferences
2. If auto-launch enabled → Show confirmation or launch directly
3. If manual selection → Show traditional player selection
4. After manual selection → Offer to remember choice
### 3. Settings Page Integration (`/src/app/settings/page.tsx`)
- **Dedicated Video Player section** with modern toggle switches
- **Real-time preference updates** with immediate feedback
- **Platform-aware player list** showing only compatible players
- **Current configuration display** for transparency
- **Reset to defaults** functionality
Settings UI includes:
- Auto Launch toggle
- Preferred Player selection (with manual option)
- Confirmation toggle
- Remember Choice toggle
- Current status display
## User Experience Flow
### First Time Use
1. User clicks on .ts (or other external format) video
2. LocalPlayerLauncher shows traditional selection dialog
3. User selects preferred player (e.g., VLC)
4. System asks: "Remember VLC as preferred player?"
5. If yes, asks: "Auto-launch videos with VLC in future?"
6. Preferences saved for subsequent videos
### Subsequent Uses (Auto-Launch Enabled)
1. User clicks on external format video
2. System shows: "Launch VLC?" confirmation dialog
3. User clicks "Launch VLC" → Video opens immediately
4. OR clicks "Choose Manually" → Shows full selection dialog
### Subsequent Uses (Auto-Launch Disabled)
1. User clicks on external format video
2. System immediately launches preferred player
3. No confirmation needed
## Technical Implementation
### Storage Architecture
```typescript
interface PlayerPreferences {
preferredPlayer: string | null; // Player ID or null for manual
autoLaunch: boolean; // Enable auto-launch
confirmBeforeLaunch: boolean; // Show confirmation dialog
rememberChoice: boolean; // Offer to remember selections
}
```
### Auto-Launch Logic
```typescript
const autoLaunchCheck = shouldAutoLaunch();
if (autoLaunchCheck.autoLaunch && autoLaunchCheck.playerId) {
if (autoLaunchCheck.needsConfirmation) {
setShowingAutoLaunchConfirm(true);
} else {
handlePlayerLaunch(autoLaunchCheck.playerId, true);
}
}
```
### Settings Integration
- Real-time preference updates using localStorage
- Custom event system for cross-component synchronization
- Platform detection for showing relevant players only
- Visual feedback with toggle switches and status display
## Benefits
### User Experience
- ✅ **One-time setup** → Seamless future use
- ✅ **Configurable experience** → Users choose their level of automation
- ✅ **Smart defaults** → Safe, non-intrusive initial behavior
- ✅ **Clear settings** → Easy to modify preferences later
### Technical
- ✅ **Lightweight implementation** → localStorage-based, no server calls
- ✅ **Cross-platform compatibility** → Detects available players per OS
- ✅ **Backwards compatible** → Manual selection still available
- ✅ **Event-driven updates** → Real-time preference synchronization
## Configuration Options
Users can now configure:
1. **Auto Launch**: Toggle automatic video launching
2. **Preferred Player**: Choose default player or manual selection
3. **Confirmation**: Require confirmation before auto-launch
4. **Learning**: Allow system to learn from manual choices
## Files Modified
1. **Created**: `/src/lib/player-preferences.ts` - Preference management system
2. **Enhanced**: `/src/components/local-player-launcher.tsx` - Auto-launch support
3. **Enhanced**: `/src/app/settings/page.tsx` - Settings UI integration
## Future Enhancements
Potential improvements:
- **Player installation detection** - Verify players are actually installed
- **Custom player paths** - Allow users to specify player locations
- **Per-format preferences** - Different players for different video formats
- **Keyboard shortcuts** - Quick launch hotkeys
The implementation provides a perfect balance between automation and user control, making the external video player experience as seamless as possible while maintaining flexibility for different user preferences.

View File

@ -23,9 +23,9 @@ export default function RootLayout({
<body
className={`${inter.variable} antialiased bg-background text-foreground`}
>
<div className="flex h-screen bg-gradient-to-br from-background via-background to-muted/20 overflow-hidden">
<div className="flex h-screen bg-gradient-to-br from-background via-background to-muted/20">
<Sidebar />
<main className="flex-1 bg-background/50 backdrop-blur-sm overflow-hidden">
<main className="flex-1 bg-background/50 backdrop-blur-sm overflow-y-auto">
{children}
</main>
</div>

View File

@ -8,7 +8,14 @@ import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Header } from "@/components/ui/header";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Trash2, Plus, Folder, HardDrive, Scan, CheckSquare, Square, RefreshCw } from "lucide-react";
import { Trash2, Plus, Folder, HardDrive, Scan, CheckSquare, Square, RefreshCw, Play, Monitor, Settings as SettingsIcon } from "lucide-react";
import {
loadPlayerPreferences,
savePlayerPreferences,
resetPlayerPreferences,
getAvailablePlayersForPlatform,
PlayerPreferences
} from "@/lib/player-preferences";
interface Library {
id: number;
@ -22,11 +29,41 @@ const SettingsPage = () => {
const [scanStatus, setScanStatus] = useState<string>("");
const [selectedLibraries, setSelectedLibraries] = useState<number[]>([]);
const [scanProgress, setScanProgress] = useState<Record<number, boolean>>({});
const [playerPreferences, setPlayerPreferences] = useState<PlayerPreferences | null>(null);
const [availablePlayers, setAvailablePlayers] = useState<Array<{
id: string;
name: string;
description: string;
icon: string;
}>>([]);
useEffect(() => {
fetchLibraries();
loadVideoPlayerPreferences();
}, []);
const loadVideoPlayerPreferences = () => {
try {
const prefs = loadPlayerPreferences();
const players = getAvailablePlayersForPlatform();
console.log('[Settings] Loaded preferences:', prefs);
console.log('[Settings] Available players:', players);
setPlayerPreferences(prefs);
setAvailablePlayers(players);
} catch (error) {
console.error('[Settings] Error loading player preferences:', error);
// Set default preferences if loading fails
const defaultPrefs = {
preferredPlayer: null,
autoLaunch: false,
confirmBeforeLaunch: true,
rememberChoice: true,
};
setPlayerPreferences(defaultPrefs);
setAvailablePlayers([]);
}
};
const fetchLibraries = async () => {
try {
const res = await fetch("/api/libraries");
@ -200,6 +237,21 @@ const SettingsPage = () => {
}
};
const updatePlayerPreferences = (updates: Partial<PlayerPreferences>) => {
if (!playerPreferences) return;
const updated = { ...playerPreferences, ...updates };
savePlayerPreferences(updated);
setPlayerPreferences(updated);
};
const handleResetPlayerPreferences = () => {
if (confirm('Are you sure you want to reset all video player preferences to defaults?')) {
resetPlayerPreferences();
loadVideoPlayerPreferences();
}
};
const getTotalStorage = () => {
return libraries.reduce((total, lib) => {
// Rough estimation based on path length
@ -208,7 +260,7 @@ const SettingsPage = () => {
};
return (
<div className="min-h-screen bg-zinc-950">
<div className="min-h-screen bg-zinc-950 overflow-y-auto">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-8">
<h1 className="text-5xl font-bold text-white tracking-tight mb-2">
@ -219,7 +271,7 @@ const SettingsPage = () => {
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 pb-8">
<div className="lg:col-span-2 space-y-8">
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
<div className="flex items-center gap-3 mb-6">
@ -424,6 +476,169 @@ const SettingsPage = () => {
</p>
</div>
</div>
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center shadow-lg shadow-blue-600/20">
<Play className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-xl font-bold text-white">Video Player</h2>
<p className="text-sm text-zinc-400">Configure external video player preferences</p>
</div>
</div>
{playerPreferences ? (
<div className="space-y-6">
{/* Auto Launch Setting */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-white">Auto Launch</h3>
<p className="text-xs text-zinc-400">Automatically launch videos in external player</p>
</div>
<button
onClick={() => updatePlayerPreferences({ autoLaunch: !playerPreferences.autoLaunch })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
playerPreferences.autoLaunch ? 'bg-blue-600' : 'bg-zinc-700'
}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
playerPreferences.autoLaunch ? 'translate-x-6' : 'translate-x-1'
}`} />
</button>
</div>
</div>
{/* Preferred Player Selection */}
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold text-white">Preferred Player</h3>
<p className="text-xs text-zinc-400">Choose your default video player for external playback</p>
</div>
<div className="space-y-2">
{/* None/Manual Selection Option */}
<button
onClick={() => updatePlayerPreferences({ preferredPlayer: null })}
className={`w-full p-3 rounded-lg border transition-all text-left ${
playerPreferences.preferredPlayer === null
? 'border-blue-500 bg-blue-500/10'
: 'border-zinc-700 bg-zinc-800 hover:border-zinc-600'
}`}
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-zinc-700 rounded-lg flex items-center justify-center">
<Monitor className="h-4 w-4 text-zinc-300" />
</div>
<div>
<div className="font-medium text-white text-sm">Manual Selection</div>
<div className="text-xs text-zinc-400">Show player selection dialog each time</div>
</div>
{playerPreferences.preferredPlayer === null && (
<div className="ml-auto w-2 h-2 bg-blue-500 rounded-full" />
)}
</div>
</button>
{/* Available Players */}
{availablePlayers.map((player) => (
<button
key={player.id}
onClick={() => updatePlayerPreferences({ preferredPlayer: player.id })}
className={`w-full p-3 rounded-lg border transition-all text-left ${
playerPreferences.preferredPlayer === player.id
? 'border-blue-500 bg-blue-500/10'
: 'border-zinc-700 bg-zinc-800 hover:border-zinc-600'
}`}
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-zinc-700 rounded-lg flex items-center justify-center">
<span className="text-sm">{player.icon}</span>
</div>
<div>
<div className="font-medium text-white text-sm">{player.name}</div>
<div className="text-xs text-zinc-400">{player.description}</div>
</div>
{playerPreferences.preferredPlayer === player.id && (
<div className="ml-auto w-2 h-2 bg-blue-500 rounded-full" />
)}
</div>
</button>
))}
</div>
</div>
{/* Confirmation Setting */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-white">Confirm Before Launch</h3>
<p className="text-xs text-zinc-400">Show confirmation dialog before auto-launching</p>
</div>
<button
onClick={() => updatePlayerPreferences({ confirmBeforeLaunch: !playerPreferences.confirmBeforeLaunch })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
playerPreferences.confirmBeforeLaunch ? 'bg-blue-600' : 'bg-zinc-700'
}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
playerPreferences.confirmBeforeLaunch ? 'translate-x-6' : 'translate-x-1'
}`} />
</button>
</div>
</div>
{/* Remember Choice Setting */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-white">Remember Manual Selections</h3>
<p className="text-xs text-zinc-400">Offer to remember when you manually select a player</p>
</div>
<button
onClick={() => updatePlayerPreferences({ rememberChoice: !playerPreferences.rememberChoice })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
playerPreferences.rememberChoice ? 'bg-blue-600' : 'bg-zinc-700'
}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
playerPreferences.rememberChoice ? 'translate-x-6' : 'translate-x-1'
}`} />
</button>
</div>
</div>
{/* Current Status */}
<div className="bg-zinc-800 rounded-lg p-3">
<div className="text-xs font-medium text-zinc-300 mb-2">Current Configuration</div>
<div className="space-y-1 text-xs text-zinc-400">
<div> Auto Launch: {playerPreferences.autoLaunch ? 'Enabled' : 'Disabled'}</div>
<div> Preferred Player: {playerPreferences.preferredPlayer ?
availablePlayers.find(p => p.id === playerPreferences.preferredPlayer)?.name || 'Unknown'
: 'Manual Selection'}</div>
<div> Confirmation: {playerPreferences.confirmBeforeLaunch ? 'Required' : 'Skip'}</div>
</div>
</div>
{/* Reset Button */}
<div className="pt-3 border-t border-zinc-800">
<Button
onClick={handleResetPlayerPreferences}
variant="outline"
size="sm"
className="text-zinc-400 hover:text-white"
>
Reset to Defaults
</Button>
</div>
</div>
) : (
<div className="text-center py-6">
<div className="text-zinc-400">Loading player preferences...</div>
</div>
)}
</div>
</div>
<div className="space-y-6">

View File

@ -16,6 +16,12 @@ import {
} from 'lucide-react';
import { VideoFormat, VideoFile } from '@/lib/video-format-detector';
import { cn } from '@/lib/utils';
import {
loadPlayerPreferences,
savePlayerPreferences,
shouldAutoLaunch,
PlayerPreferences
} from '@/lib/player-preferences';
interface LocalPlayerLauncherProps {
video: VideoFile;
@ -146,15 +152,46 @@ export default function LocalPlayerLauncher({
const [detectedPlayers, setDetectedPlayers] = useState<string[]>([]);
const [isDetecting, setIsDetecting] = useState(true);
const [launchStatus, setLaunchStatus] = useState<'idle' | 'launching' | 'success' | 'error'>('idle');
const [preferences, setPreferences] = useState<PlayerPreferences | null>(null);
const [showingAutoLaunchConfirm, setShowingAutoLaunchConfirm] = useState(false);
const streamUrl = getPlayerSpecificUrl(video.id, 'vlc'); // Use optimized endpoint
const recommendedPlayers = format.recommendedPlayers || ['vlc', 'iina', 'elmedia', 'potplayer'];
// Detect available players on mount
// Load preferences and check for auto-launch on mount
useEffect(() => {
detectAvailablePlayers();
const prefs = loadPlayerPreferences();
setPreferences(prefs);
const autoLaunchCheck = shouldAutoLaunch();
if (autoLaunchCheck.autoLaunch && autoLaunchCheck.playerId) {
if (autoLaunchCheck.needsConfirmation) {
setShowingAutoLaunchConfirm(true);
} else {
// Auto-launch immediately
handlePlayerLaunch(autoLaunchCheck.playerId, true);
}
} else {
// Detect available players normally
detectAvailablePlayers();
}
}, []);
// Handle auto-launch confirmation
const handleConfirmAutoLaunch = () => {
const autoLaunchCheck = shouldAutoLaunch();
if (autoLaunchCheck.playerId) {
setShowingAutoLaunchConfirm(false);
handlePlayerLaunch(autoLaunchCheck.playerId, true);
}
};
const handleCancelAutoLaunch = () => {
setShowingAutoLaunchConfirm(false);
detectAvailablePlayers();
};
const detectAvailablePlayers = async () => {
setIsDetecting(true);
try {
@ -209,7 +246,7 @@ export default function LocalPlayerLauncher({
}
};
const handlePlayerLaunch = async (playerId: string) => {
const handlePlayerLaunch = async (playerId: string, isAutoLaunch: boolean = false) => {
const player = PLAYER_INFO[playerId];
if (!player) return;
@ -229,6 +266,20 @@ export default function LocalPlayerLauncher({
onPlayerSelect?.(playerId);
// If this was manual selection and user hasn't set preferences, offer to remember
if (!isAutoLaunch && preferences && !preferences.preferredPlayer && preferences.rememberChoice) {
const shouldRemember = confirm(`Would you like to remember ${player.name} as your preferred video player? You can change this later in Settings.`);
if (shouldRemember) {
const updatedPrefs = {
...preferences,
preferredPlayer: playerId,
autoLaunch: confirm('Would you like to automatically launch videos with this player in the future?')
};
savePlayerPreferences(updatedPrefs);
setPreferences(updatedPrefs);
}
}
// Auto-close after successful launch
setTimeout(() => {
onClose();
@ -293,6 +344,49 @@ export default function LocalPlayerLauncher({
);
};
// Auto-launch confirmation dialog
if (showingAutoLaunchConfirm && preferences?.preferredPlayer) {
const preferredPlayer = PLAYER_INFO[preferences.preferredPlayer];
return (
<div className={cn("fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4", className)}>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PlayCircle className="h-5 w-5" />
Launch {preferredPlayer?.name}?
</CardTitle>
<CardDescription>
Your preferred video player is ready to open this video.
</CardDescription>
</CardHeader>
<CardContent>
<div className="bg-muted rounded-lg p-3 mb-4">
<div className="font-medium text-sm">{video.title || 'Untitled Video'}</div>
<div className="text-xs text-muted-foreground">
Format: {format.streamInfo?.contentType || 'Unknown'}
Size: {formatFileSize ? formatFileSize(video.size) : `${(video.size / 1024 / 1024).toFixed(1)} MB`}
</div>
</div>
<div className="flex gap-2">
<Button onClick={handleConfirmAutoLaunch} className="flex-1">
<PlayCircle className="h-4 w-4 mr-2" />
Launch {preferredPlayer?.name}
</Button>
<Button onClick={handleCancelAutoLaunch} variant="outline" className="flex-1">
Choose Manually
</Button>
</div>
<div className="mt-3 text-xs text-muted-foreground text-center">
You can change this in Settings Video Player
</div>
</CardContent>
</Card>
</div>
);
}
if (launchStatus === 'error') {
return (
<div className={cn("fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4", className)}>

View File

@ -0,0 +1,198 @@
/**
* Player Preferences Management
* Handles storage and retrieval of user preferences for external video players
*/
import { useState, useEffect, useCallback } from 'react';
import { PLAYER_INFO } from './local-player-launcher';
export interface PlayerPreferences {
preferredPlayer: string | null; // null means show selection dialog
autoLaunch: boolean;
confirmBeforeLaunch: boolean;
rememberChoice: boolean;
}
export const DEFAULT_PREFERENCES: PlayerPreferences = {
preferredPlayer: null,
autoLaunch: false,
confirmBeforeLaunch: true,
rememberChoice: true,
};
const STORAGE_KEY = 'nextav-player-preferences';
/**
* Load player preferences from localStorage
*/
export function loadPlayerPreferences(): PlayerPreferences {
if (typeof window === 'undefined') {
return DEFAULT_PREFERENCES;
}
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) {
return DEFAULT_PREFERENCES;
}
const parsed = JSON.parse(stored);
// Validate that the preferred player still exists
if (parsed.preferredPlayer && !PLAYER_INFO[parsed.preferredPlayer]) {
parsed.preferredPlayer = null;
}
return {
...DEFAULT_PREFERENCES,
...parsed,
};
} catch (error) {
console.error('Error loading player preferences:', error);
return DEFAULT_PREFERENCES;
}
}
/**
* Save player preferences to localStorage
*/
export function savePlayerPreferences(preferences: PlayerPreferences): void {
if (typeof window === 'undefined') {
return;
}
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
// Dispatch custom event for components to react to preference changes
window.dispatchEvent(new CustomEvent('playerPreferencesChanged', {
detail: preferences
}));
} catch (error) {
console.error('Error saving player preferences:', error);
}
}
/**
* Update specific preference fields
*/
export function updatePlayerPreferences(updates: Partial<PlayerPreferences>): PlayerPreferences {
const current = loadPlayerPreferences();
const updated = { ...current, ...updates };
savePlayerPreferences(updated);
return updated;
}
/**
* Reset preferences to defaults
*/
export function resetPlayerPreferences(): PlayerPreferences {
savePlayerPreferences(DEFAULT_PREFERENCES);
return DEFAULT_PREFERENCES;
}
/**
* Check if we should auto-launch a video with current preferences
*/
export function shouldAutoLaunch(): {
autoLaunch: boolean;
playerId: string | null;
needsConfirmation: boolean;
} {
const prefs = loadPlayerPreferences();
return {
autoLaunch: prefs.autoLaunch && prefs.preferredPlayer !== null,
playerId: prefs.preferredPlayer,
needsConfirmation: prefs.confirmBeforeLaunch,
};
}
/**
* Get available players for current platform
*/
export function getAvailablePlayersForPlatform(): Array<{
id: string;
name: string;
description: string;
icon: string;
}> {
if (typeof window === 'undefined') {
return [];
}
const userAgent = window.navigator.userAgent.toLowerCase();
const platform = userAgent.includes('mac') ? 'macOS' :
userAgent.includes('win') ? 'Windows' :
userAgent.includes('linux') ? 'Linux' : 'Unknown';
return Object.values(PLAYER_INFO)
.filter(player => player.platforms.includes(platform))
.map(player => ({
id: player.id,
name: player.name,
description: player.description,
icon: getPlayerIcon(player.id),
}));
}
/**
* Get player icon for UI display
*/
function getPlayerIcon(playerId: string): string {
const icons: Record<string, string> = {
'vlc': '🎬',
'elmedia': '🍎',
'potplayer': '🎯',
'iina': '🍎',
'mpv': '⚡'
};
return icons[playerId] || '🎮';
}
/**
* Hook for React components to use player preferences
*/
export function usePlayerPreferences() {
if (typeof window === 'undefined') {
return {
preferences: DEFAULT_PREFERENCES,
updatePreferences: () => {},
resetPreferences: () => {},
shouldAutoLaunch: () => ({ autoLaunch: false, playerId: null, needsConfirmation: true })
};
}
const [preferences, setPreferences] = useState(loadPlayerPreferences());
const updatePreferences = useCallback((updates: Partial<PlayerPreferences>) => {
const updated = updatePlayerPreferences(updates);
setPreferences(updated);
}, []);
const resetPreferences = useCallback(() => {
const reset = resetPlayerPreferences();
setPreferences(reset);
}, []);
// Listen for preference changes from other components
useEffect(() => {
const handlePreferenceChange = (event: CustomEvent) => {
setPreferences(event.detail);
};
window.addEventListener('playerPreferencesChanged', handlePreferenceChange as EventListener);
return () => {
window.removeEventListener('playerPreferencesChanged', handlePreferenceChange as EventListener);
};
}, []);
return {
preferences,
updatePreferences,
resetPreferences,
shouldAutoLaunch,
};
}