diff --git a/docs/en/guide/web-player.md b/docs/en/guide/web-player.md index 3ef895b..112794f 100644 --- a/docs/en/guide/web-player.md +++ b/docs/en/guide/web-player.md @@ -31,18 +31,6 @@ The player page path can be customized via the `player-page-path` configuration > [!IMPORTANT] > The player relies on the browser's native decoding capabilities. Some encoding formats (such as E-AC3) may not play in certain browsers (manifested as no audio or black screen). We recommend using the latest versions of Chrome, Edge, or Safari. -## MP2 Audio Software Decoding - -Most IPTV HD and SD channels use MPEG-1 Layer 2 (MP2) audio encoding. Some browsers (such as iOS Safari) do not natively support MP2 decoding, causing programs to fail to play or play with video only (no audio). - -The player has built-in MP2 audio software decoding capability: - -- **iOS Safari**: Enabled by default. Testing shows most programs can now play normally. -- **Other browsers**: Disabled by default. You can manually enable the "MP2 Audio Software Decoding" option by clicking the sidebar settings button. - -> [!NOTE] -> Audio software decoding relies on browser Web Workers and WebAssembly for background decoding, which consumes some computational resources and may cause slight heating on mobile devices — this is normal. Additionally, due to browser limitations, background playback on mobile devices is not supported when using software decoding. - ## PWA Support and Add to Home Screen The built-in web player supports PWA (Progressive Web App). You can add the player page to your device's home screen (including phones, tablets, computers, and LG webOS smart TVs) and launch it like a native app for a full-screen, immersive viewing experience. diff --git a/docs/guide/web-player.md b/docs/guide/web-player.md index 4fe67ec..0e2db1f 100644 --- a/docs/guide/web-player.md +++ b/docs/guide/web-player.md @@ -31,18 +31,6 @@ http://192.168.1.1:5140/player > [!IMPORTANT] > 播放器依赖浏览器的原生解码能力,部分编码格式(如 E-AC3)可能在某些浏览器中无法播放(表现为无音频、画面黑屏)。推荐使用最新版本的 Chrome、Edge 或 Safari。 -## MP2 音频软解 - -IPTV 大多数高清、标清频道使用 MPEG-1 Layer 2 (MP2) 音频编码,一些浏览器(例如 iOS Safari)无法原生支持 MP2 解码,导致节目无法播放,或者只有画面没有音频。 - -播放器内置了 MP2 音频软解能力: - -- **iOS Safari**:默认启用,经测试大多数节目已经可以正常播放 -- **其他浏览器**:默认关闭,可以点击侧边栏设置按钮手动启用「MP2 音频软解」选项 - -> [!NOTE] -> 音频软解依赖于浏览器 Web Worker 和 WebAssembly 在后台解码,会占用一些计算资源,在手机上可能会产生轻微发热,这是正常现象。此外,受限于浏览器,使用软解时在手机上无法保持后台播放。 - ## PWA 支持与添加到主屏幕 内置 Web 播放器支持 PWA(Progressive Web App)。你可以将播放器页面添加到设备主屏幕(包括手机、平板、电脑,以及 LG webOS 智能电视),像打开原生应用一样一键进入,获得全屏、沉浸式的观看体验。 diff --git a/web-ui/src/components/player/settings-dropdown.tsx b/web-ui/src/components/player/settings-dropdown.tsx index 1d72a86..33e6aa0 100644 --- a/web-ui/src/components/player/settings-dropdown.tsx +++ b/web-ui/src/components/player/settings-dropdown.tsx @@ -13,8 +13,6 @@ interface SettingsDropdownProps { onThemeChange: (theme: ThemeMode) => void; seamlessSwitch: boolean; onSeamlessSwitchChange: (enabled: boolean) => void; - mp2SoftDecode: boolean; - onMp2SoftDecodeChange: (enabled: boolean) => void; } const localeOptions: Array<{ value: Locale; label: string }> = [ @@ -38,8 +36,6 @@ function SettingsDropdownComponent({ onThemeChange, seamlessSwitch, onSeamlessSwitchChange, - mp2SoftDecode, - onMp2SoftDecodeChange, }: SettingsDropdownProps) { const t = usePlayerTranslation(locale); const [isOpen, setIsOpen] = useState(false); @@ -113,12 +109,6 @@ function SettingsDropdownComponent({ aria-label={t("seamlessSwitch")} /> - - {/* MP2 Audio Software Decode Toggle */} -
- {t("mp2SoftDecode")} - -
)} diff --git a/web-ui/src/components/player/video-player.tsx b/web-ui/src/components/player/video-player.tsx index 1006bf6..2e5ef7d 100644 --- a/web-ui/src/components/player/video-player.tsx +++ b/web-ui/src/components/player/video-player.tsx @@ -44,7 +44,6 @@ interface VideoPlayerProps { onToggleSidebar?: () => void; onFullscreenToggle?: () => void; seamlessSwitch?: boolean; - mp2SoftDecode?: boolean; activeSourceIndex?: number; onSourceChange?: (index: number) => void; onPlaybackStarted?: () => void; @@ -137,7 +136,6 @@ export function VideoPlayer({ onToggleSidebar, onFullscreenToggle, seamlessSwitch = true, - mp2SoftDecode = false, activeSourceIndex = 0, onSourceChange, onPlaybackStarted, @@ -541,7 +539,7 @@ export function VideoPlayer({ setNeedsUserInteraction(true); }); - const createPlayerForSlot = useEffectEvent((slotId: SlotId, useMp2SoftDecode = mp2SoftDecode): Player | null => { + const createPlayerForSlot = useEffectEvent((slotId: SlotId): Player | null => { const video = slotVideoRef(slotId).current; if (!video || !isSupported()) return null; @@ -552,7 +550,7 @@ export function VideoPlayer({ video.muted = isMuted; const p = createPlayer(video, { - wasmDecoders: useMp2SoftDecode ? { mp2: mp2WasmUrl } : {}, + wasmDecoders: { mp2: mp2WasmUrl }, }); p.on("error", (e) => { if (slotPlayerRef(slotId).current === p) { @@ -650,9 +648,11 @@ export function VideoPlayer({ // Load segments whenever they change (channel/source switch, seek, retry — all go through here) const handleLoadSegments = useEffectEvent((newSegments: PlayerSegment[]) => { + if (!newSegments.length) return; + const activeId = getActiveSlotId(); - const activePlayer = slotPlayerRef(activeId).current; - if (!newSegments.length || !activePlayer) return; + const activePlayer = slotPlayerRef(activeId).current ?? createPlayerForSlot(activeId); + if (!activePlayer) return; console.log("Loading segments..."); @@ -723,10 +723,6 @@ export function VideoPlayer({ handleLoadSegments(newSegments); }); - const reloadAfterDecoderChange = useEffectEvent(() => { - handleLoadSegments(segments); - }); - useEffect(() => { return () => { cancelPendingTransition(); @@ -735,37 +731,6 @@ export function VideoPlayer({ }; }, []); - // Recreate decoder pipeline when mp2SoftDecode toggles - useEffect(() => { - if (!slotAVideoRef.current || !isSupported()) return; - const hadPlayer = slotAPlayerRef.current !== null || slotBPlayerRef.current !== null; - const activeVideo = (activeSlotIdRef.current === "a" ? slotAVideoRef : slotBVideoRef).current; - const shouldResumeAfterRecreate = activeVideo - ? !activeVideo.paused && !activeVideo.ended - : shouldAutoPlayRef.current; - - cancelPendingTransition(); - transitionGenRef.current++; - if (hadPlayer) { - shouldAutoPlayRef.current = shouldResumeAfterRecreate; - userPausedRef.current = !shouldResumeAfterRecreate; - setNeedsUserInteraction(false); - } - wallClockCalibratedRef.current = false; - setLiveSessionAnchor(null); - destroySlot("a"); - destroySlot("b"); - hasStartedPlaybackRef.current = false; - activeSlotIdRef.current = "a"; - setVisibleSlotId("a"); - - createPlayerForSlot("a", mp2SoftDecode); - - if (hadPlayer) { - reloadAfterDecoderChange(); - } - }, [mp2SoftDecode]); - useEffect(() => { if (!seamlessSwitch) { stopPendingTransition(); diff --git a/web-ui/src/i18n/player.ts b/web-ui/src/i18n/player.ts index 66a059c..6cda23a 100644 --- a/web-ui/src/i18n/player.ts +++ b/web-ui/src/i18n/player.ts @@ -98,7 +98,6 @@ const base: TranslationDict = { themeLight: "Light", themeDark: "Dark", seamlessSwitch: "Seamless switch", - mp2SoftDecode: "MP2 Audio Software Decoder", }; const zhHans: TranslationDict = { @@ -197,7 +196,6 @@ const zhHans: TranslationDict = { themeLight: "浅色", themeDark: "深色", seamlessSwitch: "无缝换台", - mp2SoftDecode: "MP2 音频软解", }; // 繁體中文(偏好香港用語) @@ -297,7 +295,6 @@ const zhHant: TranslationDict = { themeLight: "淺色", themeDark: "深色", seamlessSwitch: "無縫換台", - mp2SoftDecode: "MP2 音頻軟解", }; export const translations: Record = { diff --git a/web-ui/src/lib/player-storage.ts b/web-ui/src/lib/player-storage.ts index f1252cc..88b9f5f 100644 --- a/web-ui/src/lib/player-storage.ts +++ b/web-ui/src/lib/player-storage.ts @@ -5,8 +5,6 @@ * JSON serialization, error handling, and backward-compatible reads. */ -import { isIOS } from "./platform"; - function cloneDefaultValue(value: T): T { if (value === null || typeof value !== "object") { return value; @@ -45,7 +43,6 @@ export const [getLastChannelId, saveLastChannelId] = createStore( ); export const [getSidebarVisible, saveSidebarVisible] = createStore("rtp2httpd-player-sidebar-visible", true); export const [getSeamlessSwitch, saveSeamlessSwitch] = createStore("rtp2httpd-player-seamless-switch", true); -export const [getMp2SoftDecode, saveMp2SoftDecode] = createStore("rtp2httpd-player-mp2-soft-decode", isIOS()); export const [getVolume, saveVolume] = createStore("rtp2httpd-player-volume", 1); export const [getMuted, saveMuted] = createStore("rtp2httpd-player-muted", false); diff --git a/web-ui/src/pages/player.tsx b/web-ui/src/pages/player.tsx index 33b84d5..98cd266 100644 --- a/web-ui/src/pages/player.tsx +++ b/web-ui/src/pages/player.tsx @@ -20,12 +20,10 @@ import { buildCatchupSegments, clampCatchupStartTime, parseM3U } from "../lib/m3 import { getLastChannelId, getLastSourceIndex, - getMp2SoftDecode, getSeamlessSwitch, getSidebarVisible, saveLastChannelId, saveLastSourceIndex, - saveMp2SoftDecode, saveSeamlessSwitch, saveSidebarVisible, } from "../lib/player-storage"; @@ -57,7 +55,6 @@ function PlayerPage() { const [isFullscreen, setIsFullscreen] = useState(false); const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768); const [seamlessSwitch, setSeamlessSwitch] = useState(() => getSeamlessSwitch()); - const [mp2SoftDecode, setMp2SoftDecode] = useState(() => getMp2SoftDecode()); const pageContainerRef = useRef(null); // Track stream start time - the absolute time position when current stream started @@ -340,11 +337,6 @@ function PlayerPage() { saveSeamlessSwitch(enabled); }, []); - const handleMp2SoftDecodeChange = useCallback((enabled: boolean) => { - setMp2SoftDecode(enabled); - saveMp2SoftDecode(enabled); - }, []); - const handleToggleSidebar = useCallback(() => { setShowSidebar((prev) => { const newState = !prev; @@ -363,21 +355,10 @@ function PlayerPage() { onThemeChange={setTheme} seamlessSwitch={seamlessSwitch} onSeamlessSwitchChange={handleSeamlessSwitchChange} - mp2SoftDecode={mp2SoftDecode} - onMp2SoftDecodeChange={handleMp2SoftDecodeChange} /> ); - }, [ - locale, - theme, - seamlessSwitch, - mp2SoftDecode, - setLocale, - setTheme, - handleSeamlessSwitchChange, - handleMp2SoftDecodeChange, - ]); + }, [locale, theme, seamlessSwitch, setLocale, setTheme, handleSeamlessSwitchChange]); // Main UI content const mainContent = ( @@ -405,7 +386,6 @@ function PlayerPage() { onToggleSidebar={handleToggleSidebar} onFullscreenToggle={handleFullscreenToggle} seamlessSwitch={seamlessSwitch} - mp2SoftDecode={mp2SoftDecode} activeSourceIndex={activeSourceIndex} onSourceChange={handleSourceChange} onPlaybackStarted={handlePlaybackStarted}