| | |
| | const videoElement = document.getElementById("live-preview"); |
| | const canvas = document.getElementById("output-canvas"); |
| | const ctx = canvas.getContext("2d"); |
| | const videoSourceSelect = document.getElementById("video-source"); |
| | const videoStatus = document.getElementById("video-status"); |
| | const enableBgRemoval = document.getElementById("enable-bg-removal"); |
| | const showPreview = document.getElementById("show-preview"); |
| | const modelQuality = document.getElementById("model-quality"); |
| | const edgeSmoothness = document.getElementById("edge-smoothness"); |
| | const backgroundBlur = document.getElementById("background-blur"); |
| | const foregroundBrightness = document.getElementById("foreground-brightness"); |
| | const processingTimeDisplay = document.getElementById("processing-time"); |
| | const fpsDisplay = document.getElementById("fps"); |
| | const copyUrlButton = document.getElementById("copy-url"); |
| | const customImageInput = document.getElementById("custom-image-input"); |
| | const customVideoInput = document.getElementById("custom-video-input"); |
| |
|
| | |
| | canvas.width = 1920; |
| | canvas.height = 1080; |
| |
|
| | |
| | let customImage = null; |
| | let customVideo = null; |
| | let lastFrameTime = performance.now(); |
| | let currentBackground = "transparent"; |
| |
|
| | |
| | async function populateVideoSources() { |
| | try { |
| | const devices = await navigator.mediaDevices.enumerateDevices(); |
| | videoSourceSelect.innerHTML = '<option value="">Select a video source</option>'; |
| | devices |
| | .filter((device) => device.kind === "videoinput") |
| | .forEach((device) => { |
| | const option = document.createElement("option"); |
| | option.value = device.deviceId; |
| | option.text = device.label || `Camera ${videoSourceSelect.options.length}`; |
| | videoSourceSelect.appendChild(option); |
| | }); |
| | } catch (err) { |
| | console.error("Lỗi khi liệt kê thiết bị video:", err); |
| | videoStatus.textContent = "Không thể liệt kê thiết bị video."; |
| | } |
| | } |
| |
|
| | |
| | async function startVideo(deviceId) { |
| | try { |
| | const stream = await navigator.mediaDevices.getUserMedia({ |
| | video: { deviceId: deviceId ? { exact: deviceId } : undefined }, |
| | }); |
| | videoElement.srcObject = stream; |
| | videoElement.play(); |
| | videoStatus.textContent = ""; |
| | processFrame(); |
| | } catch (err) { |
| | console.error("Lỗi khi truy cập webcam:", err); |
| | videoStatus.textContent = "Không thể truy cập webcam. Vui lòng kiểm tra quyền camera."; |
| | } |
| | } |
| |
|
| | |
| | function simulateBackgroundRemoval(imageData) { |
| | const data = imageData.data; |
| | for (let i = 0; i < data.length; i += 4) { |
| | |
| | const r = data[i]; |
| | const g = data[i + 1]; |
| | const b = data[i + 2]; |
| | |
| | if (g > 150 && r < 100 && b < 100) { |
| | data[i + 3] = 0; |
| | } |
| | } |
| | return imageData; |
| | } |
| |
|
| | |
| | function processFrame() { |
| | const startTime = performance.now(); |
| |
|
| | |
| | ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); |
| |
|
| | |
| | if (enableBgRemoval.checked) { |
| | let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| | imageData = simulateBackgroundRemoval(imageData); |
| | ctx.putImageData(imageData, 0, 0); |
| |
|
| | |
| | const tempCanvas = document.createElement("canvas"); |
| | tempCanvas.width = canvas.width; |
| | tempCanvas.height = canvas.height; |
| | const tempCtx = tempCanvas.getContext("2d"); |
| |
|
| | |
| | if (currentBackground === "solid-black") { |
| | tempCtx.fillStyle = "black"; |
| | tempCtx.fillRect(0, 0, canvas.width, canvas.height); |
| | } else if (currentBackground === "solid-gray") { |
| | tempCtx.fillStyle = "gray"; |
| | tempCtx.fillRect(0, 0, canvas.width, canvas.height); |
| | } else if (currentBackground === "solid-green") { |
| | tempCtx.fillStyle = "green"; |
| | tempCtx.fillRect(0, 0, canvas.width, canvas.height); |
| | } else if (currentBackground === "custom-image" && customImage) { |
| | tempCtx.drawImage(customImage, 0, 0, canvas.width, canvas.height); |
| | } else if (currentBackground === "custom-video" && customVideo) { |
| | tempCtx.drawImage(customVideo, 0, 0, canvas.width, canvas.height); |
| | } else if (currentBackground === "blurred") { |
| | tempCtx.filter = `blur(${backgroundBlur.value}px)`; |
| | tempCtx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); |
| | } |
| |
|
| | |
| | tempCtx.globalCompositeOperation = "destination-over"; |
| | tempCtx.drawImage(canvas, 0, 0); |
| |
|
| | |
| | tempCtx.filter = `brightness(${foregroundBrightness.value})`; |
| | tempCtx.drawImage(tempCanvas, 0, 0); |
| |
|
| | |
| | ctx.filter = `blur(${edgeSmoothness.value}px)`; |
| | ctx.drawImage(tempCanvas, 0, 0); |
| | } |
| |
|
| | |
| | const endTime = performance.now(); |
| | const processingTime = endTime - startTime; |
| | processingTimeDisplay.textContent = `${processingTime.toFixed(2)} ms`; |
| | fpsDisplay.textContent = `${(1000 / processingTime).toFixed(2)}`; |
| | lastFrameTime = endTime; |
| |
|
| | |
| | requestAnimationFrame(processFrame); |
| | } |
| |
|
| | |
| | videoSourceSelect.addEventListener("change", () => { |
| | startVideo(videoSourceSelect.value); |
| | }); |
| |
|
| | showPreview.addEventListener("change", () => { |
| | videoElement.style.display = showPreview.checked ? "block" : "none"; |
| | }); |
| |
|
| | document.querySelectorAll('input[name="background"]').forEach((input) => { |
| | input.addEventListener("change", () => { |
| | currentBackground = input.value; |
| | }); |
| | }); |
| |
|
| | customImageInput.addEventListener("change", (e) => { |
| | const file = e.target.files[0]; |
| | if (file) { |
| | customImage = new Image(); |
| | customImage.src = URL.createObjectURL(file); |
| | currentBackground = "custom-image"; |
| | document.querySelector('input[value="custom-image"]').checked = true; |
| | } |
| | }); |
| |
|
| | customVideoInput.addEventListener("change", (e) => { |
| | const file = e.target.files[0]; |
| | if (file) { |
| | customVideo = document.createElement("video"); |
| | customVideo.src = URL.createObjectURL(file); |
| | customVideo.loop = true; |
| | customVideo.play(); |
| | currentBackground = "custom-video"; |
| | document.querySelector('input[value="custom-video"]').checked = true; |
| | } |
| | }); |
| |
|
| | copyUrlButton.addEventListener("click", () => { |
| | const url = window.location.href; |
| | navigator.clipboard.writeText(url).then(() => { |
| | alert("Đã sao chép URL nguồn trình duyệt OBS!"); |
| | }); |
| | }); |
| |
|
| | |
| | populateVideoSources(); |