150 lines
6.8 KiB
TypeScript
150 lines
6.8 KiB
TypeScript
/**
|
|
* @composable useMediaPrefs
|
|
* @description จัดการตั้งค่าระดับเสียงและสถานะปิดเสียง (Mute) ของวิดีโอ/สื่อ
|
|
* โดยจะเก็บค่าแยกตามบัญชีผู้ใช้งาน และสั่งประยุกต์ใช้กับ <video> ให้อัตโนมัติ
|
|
*/
|
|
export const useMediaPrefs = () => {
|
|
// 1. สถานะส่วนกลาง (Global State)
|
|
// ใช้ useState เพื่อแชร์ค่าเดียวกันทั่วหน้าเว็บ (เช่น เปลี่ยนหน้าแล้วระดับเสียงยังคงที่)
|
|
const volume = useState<number>('media_prefs_volume', () => 1)
|
|
const muted = useState<boolean>('media_prefs_muted', () => false)
|
|
|
|
const { user } = useAuth()
|
|
|
|
// 2. ฟังก์ชันช่วยสร้าง Key สำหรับ Storage (เก็บแยกตาม User)
|
|
const getStorageKey = () => {
|
|
const userId = user.value?.id || 'guest'
|
|
return `media:prefs:v1:${userId}`
|
|
}
|
|
|
|
// 3. ระบบบันทึกการตั้งค่าลงเบราว์เซอร์ (Throttled เพื่อไม่ให้บันทึกถี่เกินไป)
|
|
let saveTimeout: ReturnType<typeof setTimeout> | null = null
|
|
|
|
const save = () => {
|
|
if (import.meta.server) return // เลี่ยงไม่ได้ต้องทำงานบนฝั่ง Client เท่านั้น
|
|
|
|
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('ไม่สามารถบันทึกการตั้งค่าสื่อได้', e)
|
|
}
|
|
}, 500) // หน่วงเวลา 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('ไม่สามารถโหลดการตั้งค่าสื่อได้', e)
|
|
}
|
|
}
|
|
|
|
// 5. ฟังก์ชันสำหรับอัปเดตและสั่งบันทึกการตั้งค่า (Setters)
|
|
const setVolume = (val: number) => {
|
|
const clamped = Math.max(0, Math.min(1, val))
|
|
volume.value = clamped
|
|
|
|
// ยกเลิกปิดเสียงอัตโนมัติ ถ้าระดับเสียงเพิ่มขึ้นจาก 0
|
|
if (clamped > 0 && muted.value) {
|
|
muted.value = false
|
|
}
|
|
// ปิดเสียงอัตโนมัติ ถ้าระดับเสียงกลายเป็น 0
|
|
if (clamped === 0 && !muted.value) {
|
|
muted.value = true
|
|
}
|
|
|
|
save()
|
|
}
|
|
|
|
const setMuted = (val: boolean) => {
|
|
muted.value = val
|
|
|
|
// หากผู้ใช้กดยกเลิกการปิดเสียงขณะที่ระดับเสียงเคยเป็น 0 ควรตั้งค่าเริ่มต้นให้เป็น 1
|
|
if (!val && volume.value === 0) {
|
|
volume.value = 1
|
|
}
|
|
|
|
save()
|
|
}
|
|
|
|
// 6. ฟังก์ชันจับคู่ใช้กับการเล่นสื่อ (ตย. <video ref="videoEl"> -> applyTo(videoEl.value))
|
|
const applyTo = (el: HTMLMediaElement | null | undefined) => {
|
|
if (!el) return () => {}
|
|
|
|
// ใส่ค่าตั้งต้นให้กับออบเจ็กต์สื่อ
|
|
el.volume = volume.value
|
|
el.muted = muted.value
|
|
|
|
// A. สังเกตการเปลี่ยนแปลงจาก State -> เพื่อส่งไปอัปเดต 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. สังเกตการเปลี่ยนแปลงจาก Element (เช่น ผู้ใช้กดปุ่มเร่งเสียงในวิดีโอตรงๆ) -> เพื่อเอาค่ามาอัปเดต State
|
|
const onVolumeChange = () => {
|
|
// อัปเดตเฉพาะเมื่อมีความแตกต่างเพื่อหลีกเลี่ยง 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 แบบส่งกลับ (Return))
|
|
return () => {
|
|
stopVolWatch()
|
|
stopMutedWatch()
|
|
el.removeEventListener('volumechange', onVolumeChange)
|
|
}
|
|
}
|
|
|
|
// 7. จังหวะวงจรชีวิตตอนโหลดเสร็จและระบบ Sync
|
|
if (import.meta.client) {
|
|
onMounted(() => {
|
|
load()
|
|
// ระบบ Sync กับแท็บหรือหน้าต่างเดียวกันหากถูกเปิดไว้
|
|
window.addEventListener('storage', (e) => {
|
|
if (e.key === getStorageKey()) {
|
|
load()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
return {
|
|
volume,
|
|
muted,
|
|
setVolume,
|
|
setMuted,
|
|
applyTo,
|
|
load
|
|
}
|
|
}
|