export const useMediaPrefs = () => { // 1. Global State // ใช้ useState เพื่อแชร์ค่าเดียวกันทั่วทั้ง App (เช่น เปลี่ยนหน้าแล้วเสียงยังเท่าเดิม) const volume = useState('media_prefs_volume', () => 1) const muted = useState('media_prefs_muted', () => false) const { user } = useAuth() // 2. Storage Key Helper (User Specific) const getStorageKey = () => { const userId = user.value?.id || 'guest' return `media:prefs:v1:${userId}` } // 3. Save Logic (Throttled) let saveTimeout: ReturnType | null = null const save = () => { if (import.meta.server) return if (saveTimeout) clearTimeout(saveTimeout) saveTimeout = setTimeout(() => { try { const key = getStorageKey() const data = { volume: volume.value, muted: muted.value, updatedAt: Date.now() } localStorage.setItem(key, JSON.stringify(data)) } catch (e) { console.error('Failed to save media prefs', e) } }, 500) // Throttle 500ms } // 4. Load Logic const load = () => { if (import.meta.server) return try { const key = getStorageKey() const stored = localStorage.getItem(key) if (stored) { const parsed = JSON.parse(stored) if (typeof parsed.volume === 'number') { volume.value = Math.max(0, Math.min(1, parsed.volume)) } if (typeof parsed.muted === 'boolean') { muted.value = parsed.muted } } } catch (e) { console.error('Failed to load media prefs', e) } } // 5. Setters (With Logic) const setVolume = (val: number) => { const clamped = Math.max(0, Math.min(1, val)) volume.value = clamped // Auto unmute if volume increased from 0 if (clamped > 0 && muted.value) { muted.value = false } // Auto mute if volume set to 0 if (clamped === 0 && !muted.value) { muted.value = true } save() } const setMuted = (val: boolean) => { muted.value = val // Logic: Unmuting should restore volume if it was 0 if (!val && volume.value === 0) { volume.value = 1 } save() } // 6. Apply & Bind to Element (The Magic) const applyTo = (el: HTMLMediaElement | null | undefined) => { if (!el) return () => {} // Initial Apply el.volume = volume.value el.muted = muted.value // A. Watch State -> Update Element const stopVolWatch = watch(volume, (v) => { if (Math.abs(el.volume - v) > 0.01) el.volume = v }) const stopMutedWatch = watch(muted, (m) => { if (el.muted !== m) el.muted = m }) // B. Listen Element -> Update State (e.g. Native Controls) const onVolumeChange = () => { // Update state only if diff allows (prevent loop) if (Math.abs(el.volume - volume.value) > 0.01) { volume.value = el.volume save() } if (el.muted !== muted.value) { muted.value = el.muted save() } } el.addEventListener('volumechange', onVolumeChange) // Cleanup function return () => { stopVolWatch() stopMutedWatch() el.removeEventListener('volumechange', onVolumeChange) } } // 7. Lifecycle & Sync if (import.meta.client) { onMounted(() => { load() // Cross-tab sync window.addEventListener('storage', (e) => { if (e.key === getStorageKey()) { load() } }) }) } return { volume, muted, setVolume, setMuted, applyTo, load } }