From 3ba4759d99b249028fa25c671b993eac0278983e Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sun, 31 May 2026 10:41:35 +0800 Subject: [PATCH] =?UTF-8?q?tv=E9=A1=B5=E9=9D=A2=E9=9F=B3=E9=87=8F=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/tv/live/play/page.tsx | 28 ++++++++++++++++++++++++++-- src/app/tv/play/page.tsx | 28 ++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/app/tv/live/play/page.tsx b/src/app/tv/live/play/page.tsx index 1bb7691..7754f7e 100644 --- a/src/app/tv/live/play/page.tsx +++ b/src/app/tv/live/play/page.tsx @@ -15,8 +15,25 @@ type EpgProgram = { start: string; end: string; title: string }; type TVPlayerSourceType = 'm3u8' | 'flv' | 'native'; const TV_LIVE_LAST_CHANNEL_KEY = 'tv_live_last_channel'; +const TV_VOLUME_KEY = 'tv_player_volume'; +const TV_MUTED_KEY = 'tv_player_muted'; const REMOTE_KEY_DEDUPE_MS = 350; +function loadTVVolumeState() { + if (typeof window === 'undefined') return { volume: 1, muted: false }; + + const savedVolume = Number(localStorage.getItem(TV_VOLUME_KEY)); + const volume = Number.isFinite(savedVolume) + ? Math.max(0, Math.min(1, savedVolume)) + : 1; + const savedMuted = localStorage.getItem(TV_MUTED_KEY); + + return { + volume, + muted: savedMuted === null ? volume <= 0 : savedMuted === 'true', + }; +} + function getLogoUrl(logo?: string, source?: string) { if (!logo) return ''; const sourceParam = source ? `&source=${encodeURIComponent(source)}` : ''; @@ -94,8 +111,9 @@ function TVLivePlayClient() { const [favorited, setFavorited] = useState(false); const [epgPrograms, setEpgPrograms] = useState([]); const [epgLoading, setEpgLoading] = useState(false); - const [muted, setMuted] = useState(false); - const [volume, setVolume] = useState(1); + const [initialVolumeState] = useState(loadTVVolumeState); + const [muted, setMuted] = useState(initialVolumeState.muted); + const [volume, setVolume] = useState(initialVolumeState.volume); const [showVolumeHint, setShowVolumeHint] = useState(false); const [channelHint, setChannelHint] = useState<{ number: number; name: string } | null>(null); const channelButtonRefs = useRef>({}); @@ -223,6 +241,12 @@ function TVLivePlayClient() { } }, [channel, source]); + useEffect(() => { + if (typeof window === 'undefined') return; + localStorage.setItem(TV_VOLUME_KEY, String(volume)); + localStorage.setItem(TV_MUTED_KEY, String(muted)); + }, [muted, volume]); + useEffect(() => { if (!playbackError || !channel || retryCount >= 3) return; const timer = window.setTimeout(() => { diff --git a/src/app/tv/play/page.tsx b/src/app/tv/play/page.tsx index 87fd0cf..416e6a1 100644 --- a/src/app/tv/play/page.tsx +++ b/src/app/tv/play/page.tsx @@ -67,6 +67,8 @@ const TV_DANMAKU_SPAWN_GRACE = 0.6; const TV_DANMAKU_SEEK_WINDOW = 8; const TV_DANMAKU_MAX_ITEMS = 3000; const TV_DANMAKU_SETTINGS_KEY = 'tv_danmaku_settings'; +const TV_VOLUME_KEY = 'tv_player_volume'; +const TV_MUTED_KEY = 'tv_player_muted'; type TVDanmakuSettings = { fontSize: number; @@ -80,6 +82,21 @@ const DEFAULT_TV_DANMAKU_SETTINGS: TVDanmakuSettings = { opacity: 0.75, }; +function loadTVVolumeState() { + if (typeof window === 'undefined') return { volume: 1, muted: false }; + + const savedVolume = Number(localStorage.getItem(TV_VOLUME_KEY)); + const volume = Number.isFinite(savedVolume) + ? Math.max(0, Math.min(1, savedVolume)) + : 1; + const savedMuted = localStorage.getItem(TV_MUTED_KEY); + + return { + volume, + muted: savedMuted === null ? volume <= 0 : savedMuted === 'true', + }; +} + function loadTVDanmakuSettings(): TVDanmakuSettings { if (typeof window === 'undefined') return DEFAULT_TV_DANMAKU_SETTINGS; @@ -218,8 +235,9 @@ function TVPlayClient() { const [isPlaying, setIsPlaying] = useState(false); const [isBuffering, setIsBuffering] = useState(false); const [favorited, setFavorited] = useState(false); - const [muted, setMuted] = useState(false); - const [volume, setVolume] = useState(1); + const [initialVolumeState] = useState(loadTVVolumeState); + const [muted, setMuted] = useState(initialVolumeState.muted); + const [volume, setVolume] = useState(initialVolumeState.volume); const [showVolumeHint, setShowVolumeHint] = useState(false); const [seekHint, setSeekHint] = useState<{ current: number; @@ -403,6 +421,12 @@ function TVPlayClient() { localStorage.setItem('tv_playback_rate', String(playbackRate)); }, [playbackRate]); + useEffect(() => { + if (typeof window === 'undefined') return; + localStorage.setItem(TV_VOLUME_KEY, String(volume)); + localStorage.setItem(TV_MUTED_KEY, String(muted)); + }, [muted, volume]); + useEffect(() => { let alive = true; async function loadDanmaku() {