feat: Implement orientation-aware progress bar interaction for forced landscape mode on iOS and update player dependencies.

This commit is contained in:
kuekhaoyang
2026-02-04 16:47:34 +08:00
parent 56004edb29
commit 22d6d4fee6
6 changed files with 181 additions and 131 deletions
+25 -24
View File
@@ -42,9 +42,32 @@ export function DesktopVideoPlayer({
const { fullscreenType: settingsFullscreenType } = usePlayerSettings();
const isIOS = useIsIOS();
// State to track if device is in landscape mode
const [isLandscape, setIsLandscape] = React.useState(true);
React.useEffect(() => {
const checkOrientation = () => {
// Check if width > height
if (typeof window !== 'undefined') {
setIsLandscape(window.innerWidth > window.innerHeight);
}
};
checkOrientation();
window.addEventListener('resize', checkOrientation);
window.addEventListener('orientationchange', checkOrientation);
return () => {
window.removeEventListener('resize', checkOrientation);
window.removeEventListener('orientationchange', checkOrientation);
};
}, []);
// Force windowed fullscreen on iOS to avoid native player hijacking
const fullscreenType = isIOS ? 'window' : settingsFullscreenType;
// Check if we need to force landscape (iOS + Fullscreen + Portrait)
const shouldForceLandscape = data.isFullscreen && fullscreenType === 'window' && isIOS && !isLandscape;
// Initialize HLS Player
useHlsPlayer({
videoRef: refs.videoRef,
@@ -84,7 +107,8 @@ export function DesktopVideoPlayer({
refs,
data,
actions,
fullscreenType
fullscreenType,
isForceLandscape: shouldForceLandscape
});
// Auto-skip intro/outro and auto-next episode
@@ -119,29 +143,6 @@ export function DesktopVideoPlayer({
handleVideoError,
} = logic;
// State to track if device is in landscape mode
const [isLandscape, setIsLandscape] = React.useState(true);
React.useEffect(() => {
const checkOrientation = () => {
// Check if width > height
if (typeof window !== 'undefined') {
setIsLandscape(window.innerWidth > window.innerHeight);
}
};
checkOrientation();
window.addEventListener('resize', checkOrientation);
window.addEventListener('orientationchange', checkOrientation);
return () => {
window.removeEventListener('resize', checkOrientation);
window.removeEventListener('orientationchange', checkOrientation);
};
}, []);
// Check if we need to force landscape (iOS + Fullscreen + Portrait)
const shouldForceLandscape = data.isFullscreen && fullscreenType === 'window' && isIOS && !isLandscape;
return (
<div
ref={containerRef}
+65 -22
View File
@@ -23,30 +23,68 @@ export function DesktopSpeedMenu({
containerRef
}: DesktopSpeedMenuProps) {
const buttonRef = React.useRef<HTMLButtonElement>(null);
const [menuPosition, setMenuPosition] = React.useState({ top: 0, left: 0 });
const menuRef = React.useRef<HTMLDivElement>(null);
const [menuPosition, setMenuPosition] = React.useState({ top: 0, left: 0, maxHeight: 'none', openUpward: false });
const [isFullscreen, setIsFullscreen] = React.useState(false);
React.useEffect(() => {
const updateFullscreen = () => {
setIsFullscreen(!!document.fullscreenElement);
// Check both native fullscreen and window fullscreen (CSS-based)
const nativeFullscreen = !!document.fullscreenElement;
const windowFullscreen = containerRef.current?.closest('.is-web-fullscreen') !== null;
setIsFullscreen(nativeFullscreen || windowFullscreen);
};
document.addEventListener('fullscreenchange', updateFullscreen);
// Also check periodically for window fullscreen changes (CSS class based)
const interval = setInterval(updateFullscreen, 500);
updateFullscreen();
return () => document.removeEventListener('fullscreenchange', updateFullscreen);
}, []);
return () => {
document.removeEventListener('fullscreenchange', updateFullscreen);
clearInterval(interval);
};
}, [containerRef]);
React.useEffect(() => {
if (showSpeedMenu && buttonRef.current && containerRef.current) {
const buttonRect = buttonRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
// Calculate menu position with available space awareness
const calculateMenuPosition = React.useCallback(() => {
if (!buttonRef.current || !containerRef.current) return;
const buttonRect = buttonRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
// Space available below and above the button
const spaceBelow = viewportHeight - buttonRect.bottom - 20; // 20px margin
const spaceAbove = buttonRect.top - containerRect.top - 20;
// Estimate menu height (or use actual if already rendered)
const estimatedMenuHeight = 250; // approximate height of speed menu
const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
// Determine if we should open upward
const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow;
// Calculate max-height based on available space
const maxHeight = openUpward
? Math.min(spaceAbove, actualMenuHeight)
: Math.min(spaceBelow, viewportHeight * 0.7);
if (openUpward) {
setMenuPosition({
top: buttonRect.top - containerRect.top - 10, // Position above button
left: buttonRect.right - containerRect.left,
maxHeight: `${maxHeight}px`,
openUpward: true
});
} else {
setMenuPosition({
top: buttonRect.bottom - containerRect.top + 10,
left: buttonRect.right - containerRect.left
left: buttonRect.right - containerRect.left,
maxHeight: `${maxHeight}px`,
openUpward: false
});
}
}, [showSpeedMenu, containerRef]);
}, [containerRef]);
// Auto-close menu on scroll
React.useEffect(() => {
@@ -60,28 +98,33 @@ export function DesktopSpeedMenu({
return () => window.removeEventListener('scroll', handleScroll);
}, [showSpeedMenu, onToggleSpeedMenu]);
const handleToggle = () => {
if (!showSpeedMenu && buttonRef.current && containerRef.current) {
const buttonRect = buttonRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
// Recalculate position when menu opens
React.useEffect(() => {
if (showSpeedMenu) {
calculateMenuPosition();
// Recalculate after a small delay to get actual menu height
const timer = setTimeout(calculateMenuPosition, 50);
return () => clearTimeout(timer);
}
}, [showSpeedMenu, calculateMenuPosition]);
setMenuPosition({
top: buttonRect.bottom - containerRect.top + 10,
left: buttonRect.right - containerRect.left
});
const handleToggle = () => {
if (!showSpeedMenu) {
calculateMenuPosition();
}
onToggleSpeedMenu();
};
const MenuContent = (
<div
className={`absolute z-[9999] bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] rounded-[var(--radius-2xl)] border border-[var(--glass-border)] shadow-[var(--shadow-md)] p-1 sm:p-1.5 w-fit min-w-[3.5rem] sm:min-w-[4.5rem] animate-in fade-in zoom-in-95 duration-200 ${isFullscreen ? 'max-h-[60vh] overflow-y-auto' : ''
}`}
ref={menuRef}
className={`absolute z-[9999] bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] rounded-[var(--radius-2xl)] border border-[var(--glass-border)] shadow-[var(--shadow-md)] p-1 sm:p-1.5 w-fit min-w-[3.5rem] sm:min-w-[4.5rem] animate-in fade-in zoom-in-95 duration-200 overflow-y-auto`}
style={{
top: `${menuPosition.top}px`,
top: menuPosition.openUpward ? 'auto' : `${menuPosition.top}px`,
bottom: menuPosition.openUpward ? `calc(100% - ${menuPosition.top}px + 10px)` : 'auto',
left: `${menuPosition.left}px`,
transform: 'translateX(-100%)', // Align right edge
maxHeight: isFullscreen ? menuPosition.maxHeight : 'none',
}}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
@@ -6,6 +6,7 @@ interface UseProgressControlsProps {
duration: number;
setCurrentTime: (time: number) => void;
isDraggingProgressRef: React.MutableRefObject<boolean>;
isRotated?: boolean;
}
export function useProgressControls({
@@ -13,19 +14,34 @@ export function useProgressControls({
progressBarRef,
duration,
setCurrentTime,
isDraggingProgressRef
isDraggingProgressRef,
isRotated = false
}: UseProgressControlsProps) {
const lastDragTimeRef = useRef<number>(0);
const getEventPos = useCallback((e: any, rect: DOMRect) => {
// Handle both mouse and touch events
const clientX = e.clientX || (e.touches && e.touches[0]?.clientX) || 0;
const clientY = e.clientY || (e.touches && e.touches[0]?.clientY) || 0;
if (isRotated) {
// When rotated 90deg, visual left->right is physical top->bottom
// The bounding rect height is the visual width of the bar
return Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
} else {
return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
}
}, [isRotated]);
const handleProgressClick = useCallback((e: any) => {
if (!videoRef.current || !progressBarRef.current) return;
const rect = progressBarRef.current.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const pos = getEventPos(e, rect);
const newTime = pos * duration;
videoRef.current.currentTime = newTime;
lastDragTimeRef.current = newTime; // Update ref to prevent snap-back on mouseup
setCurrentTime(newTime);
}, [videoRef, progressBarRef, duration, setCurrentTime]);
}, [videoRef, progressBarRef, duration, setCurrentTime, getEventPos]);
const handleProgressMouseDown = useCallback((e: any) => {
e.preventDefault();
@@ -34,29 +50,17 @@ export function useProgressControls({
}, [isDraggingProgressRef, handleProgressClick]);
const handleProgressTouchStart = useCallback((e: any) => {
// e.preventDefault(); // Do not prevent default immediately to allow scrolling if needed, or check logic
// But for a slider, we usually want to capture the drag.
e.preventDefault();
isDraggingProgressRef.current = true;
// Calculate new time immediately on touch start
if (!videoRef.current || !progressBarRef.current) return;
const rect = progressBarRef.current.getBoundingClientRect();
const touch = e.touches[0];
const pos = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));
const newTime = pos * duration;
videoRef.current.currentTime = newTime;
lastDragTimeRef.current = newTime;
setCurrentTime(newTime);
}, [videoRef, progressBarRef, duration, setCurrentTime, isDraggingProgressRef]);
handleProgressClick(e);
}, [isDraggingProgressRef, handleProgressClick]);
useEffect(() => {
const handleProgressMouseMove = (e: MouseEvent) => {
if (!isDraggingProgressRef.current || !progressBarRef.current || !videoRef.current) return;
e.preventDefault();
const rect = progressBarRef.current.getBoundingClientRect();
const pos = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
const pos = getEventPos(e, rect);
const newTime = pos * duration;
lastDragTimeRef.current = newTime;
setCurrentTime(newTime);
@@ -73,11 +77,10 @@ export function useProgressControls({
const handleProgressTouchMove = (e: TouchEvent) => {
if (!isDraggingProgressRef.current || !progressBarRef.current || !videoRef.current) return;
// e.preventDefault(); // Prevent scrolling while dragging progress
if (e.cancelable) e.preventDefault();
const rect = progressBarRef.current.getBoundingClientRect();
const touch = e.touches[0];
const pos = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));
const pos = getEventPos(e, rect);
const newTime = pos * duration;
lastDragTimeRef.current = newTime;
setCurrentTime(newTime);
@@ -23,6 +23,7 @@ interface UseDesktopPlayerLogicProps {
data: DesktopPlayerState['data'];
actions: DesktopPlayerState['actions'];
fullscreenType?: 'native' | 'window';
isForceLandscape?: boolean;
}
export function useDesktopPlayerLogic({
@@ -34,7 +35,8 @@ export function useDesktopPlayerLogic({
refs,
data,
actions,
fullscreenType = 'native'
fullscreenType = 'native',
isForceLandscape = false
}: UseDesktopPlayerLogicProps) {
const {
videoRef, containerRef, progressBarRef, volumeBarRef,
@@ -104,7 +106,8 @@ export function useDesktopPlayerLogic({
const progressControls = useProgressControls({
videoRef, progressBarRef, duration,
setCurrentTime, isDraggingProgressRef
setCurrentTime, isDraggingProgressRef,
isRotated: isForceLandscape
});
const skipControls = useSkipControls({
+61 -61
View File
@@ -14,10 +14,10 @@
"@vercel/analytics": "^1.6.1",
"hls.js": "^1.6.15",
"lucide-react": "^0.563.0",
"next": "16.1.4",
"react": "19.2.3",
"react-dom": "19.2.3",
"zustand": "^5.0.10"
"next": "16.1.6",
"react": "19.2.4",
"react-dom": "19.2.4",
"zustand": "^5.0.11"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.13.16",
@@ -26,7 +26,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.4",
"eslint-config-next": "16.1.6",
"postcss": "^8.5.6",
"tailwindcss": "^4",
"typescript": "^5"
@@ -1306,15 +1306,15 @@
}
},
"node_modules/@next/env": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.4.tgz",
"integrity": "sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
"integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.4.tgz",
"integrity": "sha512-38WMjGP8y+1MN4bcZFs+GTcBe0iem5GGTzFE5GWW/dWdRKde7LOXH3lQT2QuoquVWyfl2S0fQRchGmeacGZ4Wg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz",
"integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1322,9 +1322,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.4.tgz",
"integrity": "sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
"integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
"cpu": [
"arm64"
],
@@ -1338,9 +1338,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.4.tgz",
"integrity": "sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
"integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
"cpu": [
"x64"
],
@@ -1354,9 +1354,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.4.tgz",
"integrity": "sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
"integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
"cpu": [
"arm64"
],
@@ -1370,9 +1370,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.4.tgz",
"integrity": "sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
"integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
"cpu": [
"arm64"
],
@@ -1386,9 +1386,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.4.tgz",
"integrity": "sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
"integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
"cpu": [
"x64"
],
@@ -1402,9 +1402,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.4.tgz",
"integrity": "sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
"integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
"cpu": [
"x64"
],
@@ -1418,9 +1418,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.4.tgz",
"integrity": "sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
"integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
"cpu": [
"arm64"
],
@@ -1434,9 +1434,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.4.tgz",
"integrity": "sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
"integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
"cpu": [
"x64"
],
@@ -3863,13 +3863,13 @@
}
},
"node_modules/eslint-config-next": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.4.tgz",
"integrity": "sha512-iCrrNolUPpn/ythx0HcyNRfUBgTkaNBXByisKUbusPGCl8DMkDXXAu7exlSTSLGTIsH9lFE/c4s/3Qiyv2qwdA==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz",
"integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "16.1.4",
"@next/eslint-plugin-next": "16.1.6",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -5906,12 +5906,12 @@
"license": "MIT"
},
"node_modules/next": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/next/-/next-16.1.4.tgz",
"integrity": "sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
"integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
"license": "MIT",
"dependencies": {
"@next/env": "16.1.4",
"@next/env": "16.1.6",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
@@ -5925,14 +5925,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.1.4",
"@next/swc-darwin-x64": "16.1.4",
"@next/swc-linux-arm64-gnu": "16.1.4",
"@next/swc-linux-arm64-musl": "16.1.4",
"@next/swc-linux-x64-gnu": "16.1.4",
"@next/swc-linux-x64-musl": "16.1.4",
"@next/swc-win32-arm64-msvc": "16.1.4",
"@next/swc-win32-x64-msvc": "16.1.4",
"@next/swc-darwin-arm64": "16.1.6",
"@next/swc-darwin-x64": "16.1.6",
"@next/swc-linux-arm64-gnu": "16.1.6",
"@next/swc-linux-arm64-musl": "16.1.6",
"@next/swc-linux-x64-gnu": "16.1.6",
"@next/swc-linux-x64-musl": "16.1.6",
"@next/swc-win32-arm64-msvc": "16.1.6",
"@next/swc-win32-x64-msvc": "16.1.6",
"sharp": "^0.34.4"
},
"peerDependencies": {
@@ -6371,24 +6371,24 @@
"license": "MIT"
},
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.3"
"react": "^19.2.4"
}
},
"node_modules/react-is": {
@@ -7639,9 +7639,9 @@
}
},
"node_modules/zustand": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
"integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",
"integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "4.0.6",
"version": "4.0.7",
"private": true,
"scripts": {
"dev": "next dev",