دسترسی به دوربین جلو و عقب با getUserMedia در JavaScript

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 23 اسفند 1397
دسته بندی ها : جاوا اسکریپت

به نظر می‌رسد در گذشته‌ای نه چندان دور، همه مرورگرها پلاگین فلش را داشتند تا به سخت‌افزار رسانه دستگاه دسترسی داشته باشند و صدا و تصویر را دریافت کنند. با کمک این پلاگین‌ها، توسعه دهندگان توانستند به صدا و تصویر دستگاه‌ها دسترسی داشته باشند و یک ویدیو زنده را بر روی مرورگر پخش کنند.

وقتی که HTML5‌ معرفی شد، همه چیز هم برای توسعه دهندگان و هم برای کاربران آسان‌تر شد. با معرفی HTML5، به APIهایی رسیدیم که به سخت‌افزار دستگاه دسترسی داشتند، و یکی از آن‌ها MediaDevices بود. این API دسترسی به ورودی‌های رسانه‌ای مانند صدا، تصویر و... را فراهم می‌کند. این آبجکت، شامل متد getUserData می‌باشد که با آن کار خواهیم کرد.

قبل از این که به سراغ خواندن این مقاله بروید، می‌توانید دوره آموزشی JavaScript بر روی راکت را بگذرانید، تا پیش‌زمینه بهتری برای انجام این فعالیت‌ها داشته باشید.

جدول محتوا:

  • اِی‌پی‌آی getUserMedia چیست؟
  • شروع کار
  • درخواست برای مجوزهای کاربر
  • پیکربندی محدودیت‌های رسانه
  • استفاده از متد enumerateDevices
  • نمایش ویدیو زنده بر روی مرورگر
  • نتیجه گیری

اِی‌پی‌آی getUserMedia چیست؟

اِی‌پی‌آی getUserMedia از برخی ورودی‌های رسانه‌ای دستگاه استفاده می‌کند تا یک MediaStream ایجاد کند. با استفاده از این MediaStream که از API برگردانده شده است، ویدیوها می‌توانند بر روی مرورگر نمایش داده شوند که این یک ارتباط کاربردی realtime بر روی مرورگر می‌باشد. وقتی که از آن به همراه اِی‌پی‌آی MediaStramRecorder استفاده کنیم، می‌توانیم داده‌های رسانه‌ای دریافت شده بر روی مرورگر را ضبط و ذخیره کنیم. این API فقط بر روی منابع امن مانند APIهای دیگری که معرفی شده‌اند، کار کرده، و چه بر روی localhost و چه بر روی urlهای فایل کار می‌کند.

شروع کار

بیایید قدم‌های درخواست مجوز مربوط به دریافت داده‌های ویدیوای برای نمایش زنده از ورودی‌های دستگاه بر روی مرورگر را بررسی کنیم. در ابتدا باید بررسی کنیم که آیا مرورگر کاربر مورد نظر از اِی‌پی‌آی mediaDevices پشتیبانی می‌کند یا نه. این رسانه داخل رابط navigator وجود دارد. این رابط شامل state فعلی و هویت کاربر می‌باشد. ما روند بررسی را به این صورت اجرا می‌کنیم:

if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices){
  console.log("Let's get this party started")
}

در ابتدا بررسی می‌کنیم که آیا اِی‌پی‌آی mediaDevices داخل navigator وجود دارد یا نه، و سپس بررسی می‌کنیم که آیا اِی‌پی‌آی getUserMeda داخل mediaDevices در دسترس است یا نه. اگر این بررسی مقدار true را برگرداند، می‌توانیم کار خود را شروع کنیم.

درخواست برای مجوزهای کاربر

قدم بعدی پس از تایید پشتیبانی مرورگر برای getUserMedia، این است که برای مجوز استفاده از ورودی‌های دستگاه کاربر درخواست کنیم. معمولا بعد از این که یک کاربر اجازه را می‌دهد، یک Promise برگردانده می‌شود و این promise به یک جریان رسانه ختم می‌شود. این Promise وقتی که مجوز مربوطه توسط کاربر رد می‌شود، برگردانده نمی‌شود و دسترسی به این دستگاه را مسدود می‌کند.

if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices){
  const stream = await navigator.mediaDevices.getUserMedia({video: true})
}

آبجکتی که به عنوان یک آرگومان برای متد getUserMedia فراهم شده است، constraints نام دارد. این آبجکت تعیین می‌کند که ما برای کدام دستگاه‌های ورودی رسانه، درخواست مجوز می‌کنیم. اگر آبجکت مورد نظر شامل audio:true باشد، از کاربر خواسته خواهد شد تا اجازه دسترسی به ورودی صدا را بدهد.

پیکربندی محدودیت‌های رسانه

آبجکت constraints یک آبجکت MediaStreamConstranints است که نوع داده‌های مورد درخواست، و نیازمندی‌های هر نوع داده را مشخص می‌کند. ما با استفاده از آبجکت constraints می‌توانیم نیازمندی‌های ویدیو مورد درخواست، مانند رزولوشن آن را مشخص کنیم.

وقتی که برای یک نوع رسانه درخواست می‌کنید، باید نوع آن یعنی video یا audio را تعیین کنید. در صورتی که نوع داده درخواست شده نتواند بر روی مرورگر کاربر یافت شود، یک خطای NotFoundError برگردانده خواهد شد. اگر ما بخواهیم یک ویدیو با رزولوشن 1280 * 720‌ را درخواست کنیم، می‌توانیم آبجکت constrants‌ را به این صورت بروزرسانی کنیم:

{
  video: {
    width: 1280,
    height: 720,
  }
}

با این بروزرسانی، مرورگر تلاش خواهد کرد تا کیفیت با این تنظیمات برای ویدیو تطبیق یابد، اما اگر دستگاه ویدیو نتواند این رزولوشن را تحویل دهد، مرورگر رزولوشن‌های در دسترس دیگر را بر خواهد گرداند. برای تضمین این که رزولوشن برگردانده شده توسط مرورگر از رزولوشن فراهم شده پایین‌تر نیست، ما باید از ویژگی min استفاده کنیم. آبجکت constraints را به این صورت بروزرسانی کنید تا شامل آبجکت min‌ باشد:

{
  video: {
    width: { 
      min: 1280,
    },
    height: {
      min: 720,
    }
  }
}

این کار تضمین خواهد کرد که رزولوشن ویدیو حداقل 1280 * 720 خواهد بود. اگر این نیازمندی‌های حداقلی فراهم نباشند، promise مورد نظر با یک خطای OverconstraintedError رد خواهد شد.

گاهی اوقات شما نگران ذخیره داده‌ها هستید و می‌خواهید ویدیو از یک رزولوشن تعیین شده تجاوز نکند. این مورد وقتی که کاربر یک حجم داده محدود دارد، می‌تواند کاربردی باشد. برای فعال کردن این عملکرد، آبجکت Constraints را بروزرسانی کنید تا شامل یک فیلد max باشد:

{
  video: {
    width: { 
      min: 1280,
      max: 1920,
    },
    height: {
      min: 720,
      max: 1080
    }
  }
}

با این تنظیمات، مرورگر تضمین خواهد کرد دکمه ویدیو پایین‌تر از 1280 * 720 نمی‌آید و از 1920 * 1080 هم بالاتر نمی‌رود. اصطلاحات دیگری که می‌توانند استفاده شوند، exact و ideal هستند. تنظیمات ideal معمولا به همراه ویژگی‌های min و max استفاده می‌شود تا بهترین تنظیمات ممکن را که به مقادیر ایده‌آل فراهم شده نزدیک هستند، پیدا کند.

شما می‌توانید constraints را بروزرسانی کنید، تا از کلیدواژه ideal استفاده کند:

{
  video: {
    width: { 
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    }
  }
}

برای این که به مرورگر بگویید از دوربین جلو یا عقب بر روی دستگاه موبایل استفاده کند، می‌توانید ویژگی facingMode را در آبجکت video مشخص کنید:

{
  video: {
    width: { 
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    },
    facingMode: 'user'
  }
}

این تنظیمات از دوربین جلو در تمام زمان‌ها و در تمام دستگاه‌ها استفاده خواهد کرد. برای استفاده از دوربین عقب بر روی دستگاه‌های موبایل، ما می‌توانیم ویژگی facingMode را به environment تغییر دهیم.

{
  video: {
    ...
    facingMode: { 
      exact: 'environment'
    }
  }
}

استفاده از متد enumerateDevices

وقتی که این متد فراخوانی می‌شود، تمام دستگاه‌های رسانه‌ای ورودی در دسترس را بر روی کامپیوتر کاربر بر می‌گرداند.

با این متد، شما می‌توانید گزینه‌هایی را درباره این که کاربر از کدام دستگاه‌های رسانه‌ای ورودی برای نمایش محتویات ویدیوای یا صوتی استفاده کند، فراهم کنید. این متد یک promise ختم شده به یک آرایه MediaDeviceInfo را بر می‌گردند که شامل اطلاعاتی درباره هر دستگاه می‌باشد.

مثالی از نحوه استفاده از این متد، در قطعه کد زیر نمایش داده شده است:

async function getDevices(){

  const devices = await navigator.mediaDevices.enumerateDevices();

}

یک پاسخ نمونه برای دستگاه، به این صورت خواهد بود:

{
  deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",
  groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",
  kind: "audiooutput",
  label: "",
}

نمایش ویدیو زنده بر روی مرورگر

ما از روند ارسال درخواست‌ها و دسترسی به دستگاه‌های رسانه‌ای، محدودیت‌های پیکربندی شده برای شامل شدن رزولوشن‌های مورد نیاز و همچنین دوربین انتخابی برای ضبط ویدیو گذشته‌ایم. بعد از این که از تمام این قدم‌ها گذشتیم، حداقل می‌خواهیم ببینیم که آیا ویدیو ما بر حسب تنظیمات پیکربندی شده تحویل داده می‌شود یا نه. برای تضمین این مسئله، ما باید از عنصر video برای نمایش ویدیو بر روی مرورگر استفاده کنیم.

همانطور که پیش‌تر در این مقاله گفتیم، متد getUserMedia آن promise که می‌تواند به نمایش زنده ختم شود را بر می‌گرداند. ویدیو برگردانده شده، می‌تواند با استفاده از متد createObjectURL به یک URL آبجکت تبدیل شود. این URL به عنوان منبع ویدیو تنظیم خواهد شد.

ما یک دمو کوتاه خواهیم ساخت، که در آن با استفاده از متد enumerateDevices به کاربر اجازه می‌دهیم تا از میان لیست دستگاه‌های ویدیو، یکی را انتخاب کند. این یک متد navigator.mediaDevices می‌باشد، که دستگاه‌های رسانه‌ای در دسترس مانند میکروفون‌ها، دوربین‌ها و... را لیست می‌کند. این متد یک promise قابل ختم به یک آرایه از آبجکت‌ها را بر می‌گرداند، که دستگاه‌های رسانه‌ای در دسترس را به همراه جزئیات فراهم می‌نماید.

یک فایل به نام index.js بسازید و محتویات آن را با کد زیر بروزرسانی کنید:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
<div>
    <video autoplay></video>
    <canvas class="d-none"></canvas>
</div>
<div class="video-options">
    <select name="" id="" class="custom-select">
        <option value="">Select camera</option>
    </select>
</div>

<img class="screenshot-image" alt="">

<div class="controls">
    <button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
    <button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
    <button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
</div>

<script src="https://unpkg.com/feather-icons"></script>
<script src="script.js"></script>
</body>
</html>

در قطعه کد بالا، ما عناصری که نیاز داریم و برخی کنترل‌ها را برای ویدیو راه‌اندازی کرده‌ایم. همچنین یک دکمه برای گرفتن اسکرین‌شات از ویدیو فعلی هم شامل شده است. حال بیایید کامپوننت‌ها را کمی استایل‌بندی کنیم.

یک فایل به نام style.css بسازید و این استایل‌ها را در آن قرار دهید. اگر متوجه شده باشید، Bootstrap هم در آن شامل شده بود تا میزان کد CSSای که باید برای راه‌اندازی کامپوننت‌ها را بنویسیم را کاهش دهد.

// style.css
.screenshot-image {
    width: 150px;
    height: 90px;
    border-radius: 4px;
    border: 2px solid whitesmoke;
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
    position: absolute;
    bottom: 5px;
    left: 10px;
    background: white;
}

.display-cover {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 70%;
    margin: 5% auto;
    position: relative;
}

video {
    width: 100%;
    background: rgba(0, 0, 0, 0.2);
}

.video-options {
    position: absolute;
    left: 20px;
    top: 30px;
}

.controls {
    position: absolute;
    right: 20px;
    top: 20px;
    display: flex;
}

.controls > button {
    width: 45px;
    height: 45px;
    text-align: center;
    border-radius: 100%;
    margin: 0 6px;
    background: transparent;
}

.controls > button:hover svg {
    color: white !important;
}

@media (min-width: 300px) and (max-width: 400px) {
    .controls {
        flex-direction: column;
    }

    .controls button {
        margin: 5px 0 !important;
    }
}

.controls > button > svg {
    height: 20px;
    width: 18px;
    text-align: center;
    margin: 0 auto;
    padding: 0;
}

.controls button:nth-child(1) {
    border: 2px solid #D2002E;
}

.controls button:nth-child(1) svg {
    color: #D2002E;
}

.controls button:nth-child(2) {
    border: 2px solid #008496;
}

.controls button:nth-child(2) svg {
    color: #008496;
}

.controls button:nth-child(3) {
    border: 2px solid #00B541;
}

.controls button:nth-child(3) svg {
    color: #00B541;
}

.controls > button {
    width: 45px;
    height: 45px;
    text-align: center;
    border-radius: 100%;
    margin: 0 6px;
    background: transparent;
}

.controls > button:hover svg {
    color: white;
}

قدم بعدی این است که عملکرد مربوطه را با استفاده از متد enumerateDevices به دمو خود اضافه کنیم. ما دستگاه‌های ویدیو در دسترس را به دست آورده، و آن‌ها را به عنوان گزینه‌های موجود در عنصر انتخاب قرار خواهیم داد. یک فایل به نام script.js بسازید و آن را با این قطعه کد پر کنید:

feather.replace();

const controls = document.querySelector('.controls');
const cameraOptions = document.querySelector('.video-options>select');
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const screenshotImage = document.querySelector('img');
const buttons = [...controls.querySelectorAll('button')];
let streamStarted = false;

const [play, pause, screenshot] = buttons;

const constraints = {
  video: {
    width: {
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    },
  }
};

const getCameraSelection = async () => {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const videoDevices = devices.filter(device => device.kind === 'videoinput');
  const options = videoDevices.map(videoDevice => {
    return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
  });
  cameraOptions.innerHTML = options.join('');
};

play.onclick = () => {
  if (streamStarted) {
    video.play();
    play.classList.add('d-none');
    pause.classList.remove('d-none');
    return;
  }
  if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
    const updatedConstraints = {
      ...constraints,
      deviceId: {
        exact: cameraOptions.value
      }
    };
    startStream(updatedConstraints);
  }
};

const startStream = async (constraints) => {
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
  handleStream(stream);
};

const handleStream = (stream) => {
  video.srcObject = stream;
  play.classList.add('d-none');
  pause.classList.remove('d-none');
  screenshot.classList.remove('d-none');
  streamStarted = true;
};

getCameraSelection();

در قطعه کد بالا، برخی اتفاقات پیش می‌آیند. بیایید آن‌ها را تجزیه و تحلیل کنیم:

۱. ‘feather.replace()’: یک مجموعه آیکون خوب برای توسعه‌دهی وب.

۲. متغیر constraints پیکربندی‌های اولیه را برای پخش زنده نگه می‌دارد. این مورد گسترش خواهد یافت، تا شامل دستگاه‌های رسانه‌ای که کاربر انتخاب می‌کند هم باشد.

۳. ‘getCameraSelection’: این تابع متد enumerateDevices را فراخوانی می‌کند. سپس، ما آرایه را از promise نهایی فیلتر می‌کنیم و دستگاه‌های ورودی ویدیو را انتخاب می‌کنیم. ما گزینه‌هایی را برای عنصر select از نتایج فیلتر شده می‌سازیم.

۴. فراخوانی متد getUserMedia داخل listener با نام onclick مربوط به دکمه play اتفاق می‌افتد. در اینجا قبل از شروع به پخش زنده ویدیو، بررسی می‌کنیم که آیا متد مورد نظر توسط مرورگر کاربر پشتیبانی می‌شود یا نه.

۵. سپس، ما تابع startStream را فراخوانی می‌کنیم که یک آرگومان constrains را می‌گیرد. این تابع، متد getUserMeda را فراخوانی می‌کند که این متد هم constrains را فراهم می‌کند. handleStream با استفاده از پخش زنده به دست آمده از promise مختومه فراخوانی می‌شود. این متد پخش زنده برگردانده شده را برابر با srcObject مربوط به عنصر ویدیو قرار می‌دهد.

سپس، ما listener نوع click را به کنترل‌های دکمه برای pause، stop و گرفتن اسکرین‌شات بر روی صفحه اضافه می‌کنیم. همچنین ما یک listener به عنصر select اضافه می‌کنیم تا محدودیت‌های پخش زنده را با دستگاه ویدیو انتخاب شده بروزرسانی کند.

فایل script.js را با این کد بروزرسانی کنید:

...

const startStream = async (constraints) => {
  ...
};

const handleStream = (stream) => {
  ...
};

cameraOptions.onchange = () => {
  const updatedConstraints = {
    ...constraints,
    deviceId: {
      exact: cameraOptions.value
    }
  };
  startStream(updatedConstraints);
};

const pauseStream = () => {
  video.pause();
  play.classList.remove('d-none');
  pause.classList.add('d-none');
};

const doScreenshot = () => {
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  screenshotImage.src = canvas.toDataURL('image/webp');
  screenshotImage.classList.remove('d-none');
};

pause.onclick = pauseStream;
screenshot.onclick = doScreenshot;

حال وقتی که فایل index.html را بر روی مرورگر باز کنید، کلیک کردن بر روی دکمه play باید پخش زنده را شروع کند.

دمو کامل مربوطه را می‌توانید در این لینک بیابید.

نتیجه گیری

این مقاله، اِی‌پی‌آی getUserMedia را معرفی کرد. یک ضمیمه جالب به وب که روند دریافت رسانه‌ها بر روی اینترنت را آسان‌تر می‌کند. این API یک پارامتر (constraints) را می‌گیرد که می‌تواند برای پیکربندی دسترسی به دستگاه‌های ورودی ویدیو و صدا استفاده شود. این پارامتر همچنین می‌تواند برای مشخص کردن رزولوشن ویدیو مورد نیاز برنامه شما استفاده شود. شما می‌توانید این دمو را بیشتر گسترش دهید، تا گزینه‌ای مربوط به ذخیره اسکرین‌شات‌های گرفته شده، و همچنین ضبط و ذخیره داده‌های ویدیو و صدا هم با کمک اِی‌پی‌آی MediaStreamRecorder به کاربر بدهید.

منبع

مقالات پیشنهادی

افسانه های UX - به تمام صفحات باید در سه کلیک دسترسی پیدا کرد

یکی از چالش های مفید که دارای پیشنیه زیادی است چالشی است به اسم «قانون سه کلیک» یا «قانون سه ضربه (Tap) ». قانون سه کلیک یا سه ضربه می گوید که فاصله ب...

نکات ضروری تبدیل طرح دسکتاپ یک سایت، به طرح موبایل آن

از آنجایی که دستگاه های موبایل در محبوبیت در حال پیشی گرفتن از دسکتاپ هستند ، ضروری است طراحان، طراحی برای موبایل را هم در ذهن داشته باشند. Mobile-fir...

5 سوال ساده برای وقتی که به دنبال بازخورد کاربر هستید

شما نمی توانید اولین برداشت را از نو بسازید درسته؟ البته که درسته و این یکی از دلایلی است که باید وقت و تلاش زیادی صرف طراحی یک وب سایت کنید . شما می...

10 نکته برای داشتن تجربه کاربری بهتر در موبایل

لایه ها، فرم ها، فونت ها و موارد دیگر، چیزهایی هستند که در هنگام طراحی وبسایت باید آنها را در نظر بگیرید. جدای از آن شما باید در رابطه با اینکه این ال...