leaf-ai-app / assets /js /camera.js
Hphuccoder28's picture
Deploy LEAF_AI Botanical Recruiter & Diagnostic SPA
f67ba6f verified
Raw History Blame Contribute Delete
4.06 kB
/**
* LEAF_AI - Camera & Upload Management
* Chụp ảnh trực tiếp từ webcam/camera sau tại vườn & kéo thả upload ảnh.
*/
class LeafCameraManager {
constructor(options = {}) {
this.videoElement = options.videoElement;
this.captureCanvas = document.createElement('canvas');
this.stream = null;
this.isStreaming = false;
this.facingMode = 'environment'; // Ưu tiên camera sau của điện thoại khi soi lá cây
this.onImageCaptured = options.onImageCaptured || (() => {});
}
async startCamera() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error("Trình duyệt không hỗ trợ truy cập Camera trực tiếp.");
}
try {
this.stopCamera();
const constraints = {
video: {
facingMode: { ideal: this.facingMode },
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
};
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
if (this.videoElement) {
this.videoElement.srcObject = this.stream;
await this.videoElement.play();
this.isStreaming = true;
}
return true;
} catch (err) {
console.warn("Không mở được camera:", err);
throw err;
}
}
stopCamera() {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
this.stream = null;
}
if (this.videoElement) {
this.videoElement.srcObject = null;
}
this.isStreaming = false;
}
toggleFacingMode() {
this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
return this.startCamera();
}
captureSnapshot() {
if (!this.isStreaming || !this.videoElement) return null;
const v = this.videoElement;
const w = v.videoWidth || 640;
const h = v.videoHeight || 480;
this.captureCanvas.width = w;
this.captureCanvas.height = h;
const ctx = this.captureCanvas.getContext('2d');
ctx.drawImage(v, 0, 0, w, h);
const base64Data = this.captureCanvas.toDataURL('image/jpeg', 0.92);
this.stopCamera();
this.onImageCaptured(base64Data, "camera_capture.jpg");
return base64Data;
}
initDropZone(dropZoneElement, fileInputElement) {
if (!dropZoneElement) return;
// Click mở file dialog
dropZoneElement.addEventListener('click', (e) => {
if (e.target.tagName !== 'BUTTON' && fileInputElement) {
fileInputElement.click();
}
});
// Drag over
['dragenter', 'dragover'].forEach(eventName => {
dropZoneElement.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
dropZoneElement.classList.add('drag-active');
}, false);
});
// Drag leave / drop
['dragleave', 'drop'].forEach(eventName => {
dropZoneElement.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
dropZoneElement.classList.remove('drag-active');
}, false);
});
// Xử lý khi thả file vào
dropZoneElement.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const files = dt.files;
if (files && files.length > 0) {
this.handleFile(files[0]);
}
});
// Khi chọn file qua input
if (fileInputElement) {
fileInputElement.addEventListener('change', (e) => {
if (e.target.files && e.target.files.length > 0) {
this.handleFile(e.target.files[0]);
}
});
}
}
handleFile(file) {
if (!file.type.startsWith('image/')) {
alert("Vui lòng chọn tệp hình ảnh hợp lệ (.jpg, .png, .webp)");
return;
}
if (file.size > 10 * 1024 * 1024) {
alert("Ảnh vượt quá giới hạn dung lượng 10MB!");
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const base64 = e.target.result;
this.onImageCaptured(base64, file.name);
};
reader.readAsDataURL(file);
}
}