| |
| 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(); |