بدء استخدام أجهزة الوسائط

عند تطوير الويب، يوفّر معيار WebRTC واجهات برمجة التطبيقات للوصول إلى الكاميرات والميكروفونات المتصلة بالكمبيوتر أو الهاتف الذكي. ويُشار إلى هذه الأجهزة عادةً باسم أجهزة الوسائط ويمكن الوصول إليها باستخدام JavaScript من خلال الكائن navigator.mediaDevices الذي ينفذ الواجهة MediaDevices. من خلال هذا الكائن، يمكننا تعداد جميع الأجهزة المتصلة، ومراقبة تغييرات الجهاز (عندما يكون الجهاز متصلاً أو غير متصل)، وفتح جهاز لاسترداد بث الوسائط (انظر أدناه).

والطريقة الأكثر شيوعًا لاستخدام هذه الطريقة هي باستخدام الدالة getUserMedia()، التي تعرض وعدًا سيتم استبداله بالرمز MediaStream لأجهزة الوسائط المطابقة. تستخدم هذه الدالة كائن MediaStreamConstraints واحدًا يحدد المتطلبات التي لدينا. على سبيل المثال، لفتح الميكروفون والكاميرا الافتراضيين، سنفعل ما يلي.

استخدام الوعود

const constraints = {
    'video': true,
    'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        console.log('Got MediaStream:', stream);
    })
    .catch(error => {
        console.error('Error accessing media devices.', error);
    });

استخدام غير متزامن/انتظار

const openMediaDevices = async (constraints) => {
    return await navigator.mediaDevices.getUserMedia(constraints);
}

try {
    const stream = openMediaDevices({'video':true,'audio':true});
    console.log('Got MediaStream:', stream);
} catch(error) {
    console.error('Error accessing media devices.', error);
}

سيؤدي الاستدعاء إلى getUserMedia() إلى تقديم طلب للحصول على أذونات. إذا وافق المستخدم على الإذن، يتم حلّ الوعد باستخدام MediaStream يحتوي على فيديو واحد ومقطع صوتي واحد. وفي حال رفض الإذن، يتم طرح PermissionDeniedError. في حال عدم وجود أجهزة مطابقة متصلة، سيتم طرح NotFoundError.

يتوفّر مرجع واجهة برمجة التطبيقات الكامل لواجهة MediaDevices في مستندات MDN على الويب.

الاستعلام عن أجهزة الوسائط

في التطبيقات الأكثر تعقيدًا، نرغب على الأرجح في فحص جميع الكاميرات والميكروفونات المتصلة وتقديم الملاحظات المناسبة للمستخدم. يمكن إجراء ذلك عن طريق استدعاء الدالة enumerateDevices(). وسيؤدي ذلك إلى عرض وعد يحلّ مصفوفة من MediaDevicesInfo تصف كل جهاز وسائط معروف. يمكننا استخدام هذا لتقديم واجهة مستخدم للمستخدم والتي دعه يختار الطريقة التي يفضلها. يحتوي كل MediaDevicesInfo على سمة باسم kind مع القيمة audioinput أو audiooutput أو videoinput، للإشارة إلى نوع جهاز الوسائط.

استخدام الوعود

function getConnectedDevices(type, callback) {
    navigator.mediaDevices.enumerateDevices()
        .then(devices => {
            const filtered = devices.filter(device => device.kind === type);
            callback(filtered);
        });
}

getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));

استخدام غير متزامن/انتظار

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);

جارٍ رصد تغييرات الأجهزة

تدعم معظم أجهزة الكمبيوتر توصيل أجهزة مختلفة أثناء وقت التشغيل. قد تكون كاميرا ويب متصلة عبر USB أو سماعة رأس بلوتوث أو مجموعة من مكبّرات الصوت الخارجية. ولدعم ذلك بشكل صحيح، يجب أن يستمع تطبيق الويب إلى تغييرات أجهزة الوسائط. ويمكن إجراء ذلك من خلال إضافة مستمع إلى navigator.mediaDevices لحدث devicechange.

// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
    const listElement = document.querySelector('select#availableCameras');
    listElement.innerHTML = '';
    cameras.map(camera => {
        const cameraOption = document.createElement('option');
        cameraOption.label = camera.label;
        cameraOption.value = camera.deviceId;
    }).forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('video');
    updateCameraList(newCameraList);
});

قيود الوسائط

إنّ كائن القيود، الذي يجب أن ينفّذ الواجهة MediaStreamConstraints، الذي نمرره كمَعلمة إلى getUserMedia()، يتيح لنا فتح جهاز وسائط يتطابق مع متطلبات معيّنة. يمكن أن يكون هذا الشرط محدّدًا جدًا (صوت و/أو فيديو) أو محدّدًا للغاية (الحد الأدنى لدقة الكاميرا أو رقم تعريف جهاز محدّد). ننصح بأن تتحقّق التطبيقات التي تستخدم واجهة برمجة التطبيقات getUserMedia() أولاً من الأجهزة الحالية، ثم تحدّد قيدًا يطابق الجهاز بالضبط باستخدام القيد deviceId. وستتم أيضًا تهيئة الأجهزة وفقًا للقيود، إن أمكن. ويمكننا تفعيل ميزة إلغاء الصدى على الميكروفونات أو ضبط حد أدنى لعرض وارتفاع الفيديو من الكاميرا.

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
    const constraints = {
        'audio': {'echoCancellation': true},
        'video': {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
        }

    return await navigator.mediaDevices.getUserMedia(constraints);
}

const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
    // Open first available video camera with a resolution of 1280x720 pixels
    const stream = openCamera(cameras[0].deviceId, 1280, 720);
}

يمكن الاطّلاع على المستندات الكاملة الخاصة بواجهة MediaStreamConstraints في مستندات الويب MDN.

تشغيل محلي

بعد فتح جهاز الوسائط وتوفُّر MediaStream، يمكننا تخصيصه لعنصر فيديو أو عنصر صوتي لتشغيل البث محليًا.

async function playVideoFromCamera() {
    try {
        const constraints = {'video': true, 'audio': true};
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const videoElement = document.querySelector('video#localVideo');
        videoElement.srcObject = stream;
    } catch(error) {
        console.error('Error opening video camera.', error);
    }
}

وسيحتوي عادةً رمز HTML المطلوب لعنصر فيديو عادي مستخدَم في getUserMedia() على السمتَين autoplay وplaysinline. ستؤدي السمة autoplay إلى تشغيل أحداث البث الجديدة تلقائيًا والمخصّصة للعنصر. تسمح السمة playsinline بتشغيل الفيديو بشكل مضمّن، بدلاً من تشغيل الفيديو في وضع ملء الشاشة فقط، على متصفّحات معيّنة متوافقة مع الأجهزة الجوّالة. ننصحك أيضًا باستخدام السمة controls="false" في أحداث البث المباشر، ما لم يتمكّن المستخدم من إيقافها مؤقتًا.

<html>
<head><title>Local video playback</title></head>
<body>
    <video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>