Implement audio settings persistence in ChannelPlayer: add functionality to read and store volume and mute state in localStorage, ensuring user preferences are retained across sessions. Update playback logic to respect saved audio settings and handle autoplay restrictions gracefully.
This commit is contained in:
@@ -3,6 +3,25 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Maximize, Pause, Play, Volume2, VolumeX } from 'lucide-react'
|
||||
|
||||
const STORAGE_KEY = 'tw:player'
|
||||
|
||||
/** Читает сохранённые громкость/mute из localStorage (с валидацией и дефолтами). */
|
||||
function readStoredAudio(): { volume: number; muted: boolean } {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as { volume?: unknown; muted?: unknown }
|
||||
const volume =
|
||||
typeof parsed.volume === 'number' ? Math.min(1, Math.max(0, parsed.volume)) : 1
|
||||
const muted = typeof parsed.muted === 'boolean' ? parsed.muted : true
|
||||
return { volume, muted }
|
||||
}
|
||||
} catch {
|
||||
/* недоступен/битый localStorage — дефолты */
|
||||
}
|
||||
return { volume: 1, muted: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* HLS-плеер линейного канала. Перемотка невозможна: своя минимальная панель без таймлайна, а на
|
||||
* play/после ошибки плеер прыгает к живому краю. Cookie tw_stream уже выдана к монтированию.
|
||||
@@ -19,8 +38,19 @@ export function ChannelPlayer({
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const hlsRef = useRef<Hls | null>(null)
|
||||
const [playing, setPlaying] = useState(false)
|
||||
const [muted, setMuted] = useState(true)
|
||||
const [volume, setVolume] = useState(1)
|
||||
const [muted, setMuted] = useState(() => readStoredAudio().muted)
|
||||
const [volume, setVolume] = useState(() => readStoredAudio().volume)
|
||||
|
||||
// Последние настройки звука без пересоздания HLS-эффекта + сохранение в localStorage.
|
||||
const audioRef = useRef({ volume, muted })
|
||||
useEffect(() => {
|
||||
audioRef.current = { volume, muted }
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ volume, muted }))
|
||||
} catch {
|
||||
/* localStorage недоступен — не критично */
|
||||
}
|
||||
}, [volume, muted])
|
||||
const [controlsVisible, setControlsVisible] = useState(true)
|
||||
const hideTimerRef = useRef<number | null>(null)
|
||||
const overControlsRef = useRef(false)
|
||||
@@ -66,12 +96,25 @@ export function ChannelPlayer({
|
||||
const src = `/api/channels/${slug}/live.m3u8`
|
||||
let hls: Hls | null = null
|
||||
|
||||
// Восстанавливаем сохранённую громкость/mute и запускаем; если браузер блокирует автоплей со
|
||||
// звуком — откатываемся на воспроизведение без звука.
|
||||
const startPlayback = () => {
|
||||
const audio = audioRef.current
|
||||
video.volume = audio.volume
|
||||
video.muted = audio.muted
|
||||
video.play().catch(() => {
|
||||
video.muted = true
|
||||
setMuted(true)
|
||||
void video.play().catch(() => undefined)
|
||||
})
|
||||
}
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls({ liveSyncDurationCount: 3, enableWorker: true, lowLatencyMode: false })
|
||||
hlsRef.current = hls
|
||||
hls.loadSource(src)
|
||||
hls.attachMedia(video)
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => void video.play().catch(() => undefined))
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, startPlayback)
|
||||
hls.on(Hls.Events.ERROR, (_event, data) => {
|
||||
if (data.fatal) {
|
||||
hls?.destroy()
|
||||
@@ -82,7 +125,7 @@ export function ChannelPlayer({
|
||||
})
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = src
|
||||
video.addEventListener('loadedmetadata', () => void video.play().catch(() => undefined))
|
||||
video.addEventListener('loadedmetadata', startPlayback)
|
||||
video.addEventListener('error', () => onUnavailable?.())
|
||||
} else {
|
||||
onUnavailable?.()
|
||||
|
||||
Reference in New Issue
Block a user