Nhìn lại bài toán chụp ảnh sử dụng webcam trên website

 



Hello mọi người, cũng khá lâu rồi mình chưa viết lại blog.Nay nhân dịp vừa phải làm cái task liên quan đến chụp ảnh sử dụng webcam trên web hoặc camera trên mobile, mình viết bài này để lưu lại cho đỡ quên.

I. Nói qua về công nghệ sử dụng.

WebRTC (Web Real-Time Communication) là một công nghệ cho phép truyền tải dữ liệu âm thanh và video trực tiếp qua mạng internet giữa các trình duyệt web. Với WebRTC, người dùng có thể thực hiện cuộc gọi video và truyền tải nội dung trực tiếp trên trang web mà không cần phải cài đặt phần mềm hoặc plugin bổ sung.

WebRTC cung cấp cho các nhà phát triển web các API để truy cập các thiết bị phần cứng như webcam và microphone trên máy tính, từ đó có thể sử dụng webcam để thực hiện cuộc gọi video trực tuyến hoặc truyền tải nội dung video trực tiếp trên trang web mà không cần sử dụng phần mềm hoặc plugin bổ sung. Để sử dụng webcam trong WebRTC, chúng ta cần sử dụng API MediaStream và các đối tượng liên quan. Sau khi yêu cầu quyền truy cập vào thiết bị webcam từ phía người dùng thông qua API getUserMedia, chúng ta có thể tạo ra một đối tượng MediaStream để chứa dữ liệu video và âm thanh được truyền tải từ webcam.

II. Vấn đề đang gặp phải

Có thể nhiều người sẽ thấy để sử dụng webcam và chụp 1 bức ảnh khá đơn giản vì đã có nhiều example trên mạng hướng dẫn phần đó.Dưới đây là bài viết chi tiết hướng dẫn làm sao để có thể chụp ảnh bằng webcam trên website.Tại đây

Nếu bạn sử dụng laptop hay PC có webcam code chạy khá mượt mà, không lỗi lầm gì mấy.Tuy nhiên ác mộng xảy ra khi bạn sử dụng trên mobile, một hầm bà lằng các rắc rối xảy ra nào là không mở được camera của điện thoại, nào là ảnh chụp outnet, rồi ảnh chụp có dung lượng quá nặng, một số mobile quá cũ nên việc play stream video trên nó thật là...Nó khiến bạn đau đầu và cảm thấy bất lực.Một vấn đề tưởng chừng đơn giản là sử getUserMedia() lại có nhiều rắc rối đến vậy.

III. Đoạn code tôi sử dụng để khắc phục 1 số tồn tại trên.

+, Nói qua ý tưởng, mình sẽ không chỉ sử dụng đoạn code sau để mở cam và sử dụng cam như bình thường
    stream = await navigator.mediaDevices.getUserMedia(constraintsUpdate);  
+, Mình sẽ sử dụng thêm 1 method để lấy danh sách các thiết bị âm thanh và video
    devices = await navigator.mediaDevices.enumerateDevices();  
+, Khi có danh sách các thiết bị âm thanh và video mình sẽ lọc và sắp xếp các thiết bị video để sử dụng vào bài toán.Thực ra ở đây mình sử dụng 1 số trick nhỏ các bạn có thể thay đổi trick này để lọc theo yêu cầu của mình nhé.

+, Tiếp theo mình sẽ chọn ra ID của thiết bị có chất lượng tốt nhất và gán nó vào constraints

+, Sử dụng getUserMedia với giá trị constraints đã được cấu hình để lấy stream

+, Sử dụng bộ đệm video để giảm tải cho phần cứng của những thiết bị mobile cũ

+, Gán stream vào video để play video.

Dưới đây là đoạn code mình đã sửa để phù hợp với yêu cầu bài toán của mình

  try {  
   const constraintsDefault = { video: true };  
   if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {  
    alert('getUserMedia is not supported by your browser');  
    throw new Error('getUserMedia is not supported by your browser');  
   }  
   const isIphone = isiPhone();  
   if (isIphone) {  
    await navigator.mediaDevices.getUserMedia(constraintsDefault);  
   }  
   // Lấy danh sách thiết bị âm thanh và video  
   let devices = [];  
   try {  
    devices = await navigator.mediaDevices.enumerateDevices();  
   } catch (err) {  
    console.error('Failed to enumerate devices:', err);  
    throw new Error('Failed to enumerate devices');  
   }  
   // Lọc danh sách các thiết bị video và sắp xếp theo chất lượng từ thấp đến cao  
   const videoDevices = devices.filter(device => (device.kind === 'videoinput' && (device.label.toLowerCase().includes('back') || device.label.toLowerCase().includes('sau')))).sort((a, b) => {  
    if (a.label > b.label) {  
     return 1;  
    }  
    if (a.label < b.label) {  
     return -1;  
    }  
    return 0;  
   });  
   // Lấy deviceId của camera sau hoặc camera có chất lượng cao nhất  
   const deviceId = videoDevices.length > 0 ? videoDevices[0].deviceId : null;  
   // Cấu hình constraints để lấy stream video từ camera sau hoặc camera có chất lượng cao nhất  
   const windowWidth = Math.min(window.screen.width, window.outerWidth);  
   const constraintsUpdate = windowWidth < 1100 ? {  
    audio: false,  
    video: {  
     facingMode: "environment",  
     aspectRatio: 16 / 9,  
     width: {  
      ideal: 1920,  
     },  
     height: {  
      ideal: 1080,  
     },  
     // frameRate: { ideal: 30, max: 60 },  
     deviceId: deviceId ? deviceId : null  
    },  
   } : {  
    video: {  
     facingMode: "user",  
     width: { ideal: 1280 },  
     aspectRatio: 4 / 3,  
    },  
    audio: false,  
   };  
   // Lấy stream từ camera với constraints đã cấu hình  
   let stream = null;  
   try {  
    stream = await navigator.mediaDevices.getUserMedia(constraintsUpdate);  
   } catch (err) {  
    console.error('Failed to get user media:', err);  
    throw new Error('Failed to get user media');  
   }  
   // Sử dụng bộ đệm video để giảm tải cho phần cứng  
   const videoBuffer = document.createElement('canvas');  
   const videoContext = videoBuffer.getContext('2d');  
   const video = document.getElementById(videoID);  
   video.addEventListener('play', () => {  
    const loop = () => {  
     if (!video.paused && !video.ended) {  
      videoContext.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);  
      requestAnimationFrame(loop);  
     }  
    };  
    loop();  
   }, false);  
   // Gán stream vào video  
   video.srcObject = stream;  
   if (cb) {  
    cb(stream);  
   }  
   await video.play();  
  } catch (error) {  
   console.error(error);  
  }  
Vấn đề chính mình gặp phải đó là trên các thiết bị đời mới có hơn 2 camera thì method getUserMedia() đang lấy cái camera góc rộng để chụp thành ra cái ảnh của mình nó trông rất gớm, vì vậy mình mới cần chọn thiết bị để chụp ảnh được ưng ý.
 Tùy nhu cầu các bạn có thể sửa đoạn code trên để phù hợp, và 1 điều quan trọng là code thay đổi từng ngày các bạn có thể phải sửa đoạn code trên khá nhiều, tuy nhiên về logic thì vẫn từng đó.Happy coding!



Nhận xét

Bài đăng phổ biến từ blog này

Cài đặt SSL cho website sử dụng certbot

Xây dựng một hệ thống comment real-time hoặc chat đơn giản sử dụng Pusher

CÁC BÀI TẬP SQL CƠ BẢN - PART 1

Xây dựng một hệ thống tracking hành vi người dùng (phần 1)

Xây dựng một hệ thống tracking hành vi người dùng (phần 2)

Enterprise architecture trên 1 tờ A4

Web caching (P2)

Bàn về async/await trong vòng lặp javascript

Web caching (P1)

Cài đặt môi trường để code website Rails