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