145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
export const useMediaPrefs = () => {
|
|
// 1. Global State
|
|
// ใช้ useState เพื่อแชร์ค่าเดียวกันทั่วทั้ง App (เช่น เปลี่ยนหน้าแล้วเสียงยังเท่าเดิม)
|
|
const volume = useState<number>('media_prefs_volume', () => 1)
|
|
const muted = useState<boolean>('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<typeof setTimeout> | 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
|
|
}
|
|
}
|