Files
onnx-voice-changer/frontend-next/src/hooks/usePictureInPicture.ts
T
akukanara 9d876de930 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
2026-05-31 16:46:57 +07:00

72 lines
2.1 KiB
TypeScript

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;