elearning/Frontend-Learner/composables/useMediaPrefs.ts

146 lines
4.2 KiB
TypeScript
Raw Permalink Normal View History

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
}
}