feat: refactor and optimize frontend with Next.js App Router, TypeScript, and Tailwind CSS
- Complete refactoring of old frontend into Next.js App Router workspace - Redesigned sidebar collapsing animation with absolute toggle positioning - Resolved visual canvas bleed transitions between light/dark themes - Added custom dark theme variant for toggle switch buttons - Implemented full localization across Indonesian, English, Spanish, Japanese, and Chinese - Synchronized HTML document themes to apply dark mode styles to portals/overlays
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
|
||||
export const usePictureInPicture = () => {
|
||||
const [isPipActive, setIsPipActive] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const video = document.createElement('video');
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
videoRef.current = video;
|
||||
|
||||
const handleLeavePip = () => {
|
||||
setIsPipActive(false);
|
||||
};
|
||||
|
||||
video.addEventListener('leavepictureinpicture', handleLeavePip);
|
||||
|
||||
return () => {
|
||||
video.removeEventListener('leavepictureinpicture', handleLeavePip);
|
||||
if (document.pictureInPictureElement === video) {
|
||||
document.exitPictureInPicture().catch(() => {});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const togglePip = useCallback(async (canvas: HTMLCanvasElement | null) => {
|
||||
if (!canvas || !videoRef.current) return;
|
||||
|
||||
const video = videoRef.current;
|
||||
|
||||
try {
|
||||
if (document.pictureInPictureElement === video) {
|
||||
await document.exitPictureInPicture();
|
||||
setIsPipActive(false);
|
||||
} else {
|
||||
// Capture a stream of the Canvas at 30 fps
|
||||
const stream = canvas.captureStream
|
||||
? canvas.captureStream(30)
|
||||
: (canvas as any).mozCaptureStream
|
||||
? (canvas as any).mozCaptureStream(30)
|
||||
: null;
|
||||
|
||||
if (!stream) {
|
||||
throw new Error("Canvas.captureStream() is not supported on this browser.");
|
||||
}
|
||||
|
||||
video.srcObject = stream;
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
video.onloadedmetadata = () => {
|
||||
video.play().then(() => resolve());
|
||||
};
|
||||
});
|
||||
|
||||
await video.requestPictureInPicture();
|
||||
setIsPipActive(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Picture-in-Picture failed:", error);
|
||||
alert("Picture-in-Picture error: " + (error instanceof Error ? error.message : String(error)));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isSupported = typeof window !== 'undefined' && 'pictureInPictureEnabled' in document;
|
||||
|
||||
return { togglePip, isPipActive, isSupported };
|
||||
};
|
||||
export default usePictureInPicture;
|
||||
Reference in New Issue
Block a user