feat: Implement IPTV stream proxy to resolve CORS issues and refine desktop player menu positioning for fullscreen.

This commit is contained in:
kuekhaoyang
2026-02-17 17:57:56 +08:00
parent 5d139114b5
commit 6a2e28c615
8 changed files with 425 additions and 77 deletions
+46 -5
View File
@@ -93,9 +93,9 @@ export function DesktopMoreMenu({
const calculateMenuPosition = React.useCallback(() => {
if (!buttonRef.current || !containerRef.current) return;
if (!isRotated) {
// Normal Mode: Non-rotated (Portrait on Mobile)
// Use Viewport Coordinates but position relative to button (User Request: "Below button")
if (!isRotated && !isFullscreen) {
// Normal Mode: Non-rotated, non-fullscreen
// Use Viewport Coordinates but position relative to button
// And use Body Portal to escape container clipping
const buttonRect = buttonRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
@@ -136,6 +136,47 @@ export function DesktopMoreMenu({
openUpward: openUpward,
align: align
});
} else if (isFullscreen && !isRotated) {
// Fullscreen Mode (not rotated): Use container-relative coordinates
// Portal goes to containerRef to stay visible within fullscreen element
let top = 0;
let left = 0;
let el: HTMLElement | null = buttonRef.current;
while (el && el !== containerRef.current) {
top += el.offsetTop;
left += el.offsetLeft;
el = el.offsetParent as HTMLElement;
}
const buttonHeight = buttonRef.current.offsetHeight;
const buttonWidth = buttonRef.current.offsetWidth;
const containerWidth = containerRef.current.offsetWidth;
const containerHeight = containerRef.current.offsetHeight;
const spaceBelow = containerHeight - (top + buttonHeight) - 10;
const spaceAbove = top - 10;
const estimatedMenuHeight = 450;
const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
const openUpward = spaceBelow < Math.min(actualMenuHeight, 300) && spaceAbove > spaceBelow;
const maxHeight = openUpward
? Math.min(spaceAbove, actualMenuHeight)
: Math.min(spaceBelow, containerHeight * 0.7);
const isLeftHalf = left < containerWidth / 2;
const align = isLeftHalf ? 'left' : 'right';
setMenuPosition({
top: openUpward
? top - 10
: top + buttonHeight + 10,
left: isLeftHalf ? left : left + buttonWidth,
maxHeight: `${maxHeight}px`,
openUpward: openUpward,
align: align
});
} else {
// Rotated Mode: Use Container Coordinates (offset loop) and Portal to Container
let top = 0;
@@ -185,7 +226,7 @@ export function DesktopMoreMenu({
align: align
});
}
}, [containerRef, isRotated]);
}, [containerRef, isRotated, isFullscreen]);
@@ -583,7 +624,7 @@ export function DesktopMoreMenu({
{/* More Menu Dropdown (Portal) */}
{/* More Menu Dropdown (Portal) */}
{showMoreMenu && typeof document !== 'undefined' && createPortal(MenuContent, (isRotated && containerRef.current) ? containerRef.current : document.body)}
{showMoreMenu && typeof document !== 'undefined' && createPortal(MenuContent, ((isRotated || isFullscreen) && containerRef.current) ? containerRef.current : document.body)}
</div>
);
}
+45 -5
View File
@@ -51,9 +51,9 @@ export function DesktopSpeedMenu({
const calculateMenuPosition = React.useCallback(() => {
if (!buttonRef.current || !containerRef.current) return;
if (!isRotated) {
// Normal Mode: Non-rotated
// Use Viewport Coordinates but position relative to button (User Request: "Below button")
if (!isRotated && !isFullscreen) {
// Normal Mode: Non-rotated, non-fullscreen
// Use Viewport Coordinates but position relative to button
// And use Body Portal to escape container clipping
const buttonRect = buttonRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
@@ -94,6 +94,46 @@ export function DesktopSpeedMenu({
openUpward: openUpward,
align: align
});
} else if (isFullscreen && !isRotated) {
// Fullscreen Mode (not rotated): Use container-relative coordinates
let top = 0;
let left = 0;
let el: HTMLElement | null = buttonRef.current;
while (el && el !== containerRef.current) {
top += el.offsetTop;
left += el.offsetLeft;
el = el.offsetParent as HTMLElement;
}
const buttonHeight = buttonRef.current.offsetHeight;
const buttonWidth = buttonRef.current.offsetWidth;
const containerWidth = containerRef.current.offsetWidth;
const containerHeight = containerRef.current.offsetHeight;
const spaceBelow = containerHeight - (top + buttonHeight) - 10;
const spaceAbove = top - 10;
const estimatedMenuHeight = 250;
const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow;
const maxHeight = openUpward
? Math.min(spaceAbove, actualMenuHeight)
: Math.min(spaceBelow, containerHeight * 0.7);
const isLeftHalf = left < containerWidth / 2;
const align = isLeftHalf ? 'left' : 'right';
setMenuPosition({
top: openUpward
? top - 10
: top + buttonHeight + 10,
left: isLeftHalf ? left : left + buttonWidth,
maxHeight: `${maxHeight}px`,
openUpward: openUpward,
align: align
});
} else {
// Rotated Mode: Fullscreen/Landscape forced
// Use Container Coordinates (offset loop) and Portal to Container
@@ -146,7 +186,7 @@ export function DesktopSpeedMenu({
align: align
});
}
}, [containerRef, isRotated]);
}, [containerRef, isRotated, isFullscreen]);
@@ -255,7 +295,7 @@ export function DesktopSpeedMenu({
So portaling to containerRef is SAFE and CORRECT.
*/}
{/* Speed Menu (Portal) */}
{showSpeedMenu && typeof document !== 'undefined' && createPortal(MenuContent, (isRotated && containerRef.current) ? containerRef.current : document.body)}
{showSpeedMenu && typeof document !== 'undefined' && createPortal(MenuContent, ((isRotated || isFullscreen) && containerRef.current) ? containerRef.current : document.body)}
</div>
);
}