Spaces:
Running
Running
Download assets/js/camera.js from Hphuccoder28/leaf-ai-app: direct link, hf CLI and curl.
- Browser
- Download file 4.06 kB
-
https://huggingface.co/spaces/Hphuccoder28/leaf-ai-app/resolve/main/assets/js/camera.js
- Command line
-
hf download hf://spaces/Hphuccoder28/leaf-ai-app/assets/js/camera.js
-
curl -L -o camera.js https://huggingface.co/spaces/Hphuccoder28/leaf-ai-app/resolve/main/assets/js/camera.js
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); | |
| } | |
| } | |