EnumerateDevices: примеры (JAVASCRIPT)

Получение списка устройств с помощью enumerateDevices
Раздел: Медиа, Устройства
enumerateDevices: Promise>

Основы функции enumerateDevices

Функция enumerateDevices() является методом интерфейса MediaDevices. Она используется для асинхронного запроса списка доступных медиаустройств на компьютере пользователя, таких как микрофоны, камеры, динамики и аудиовыходы.

Функция не принимает никаких аргументов. Она вызывается как navigator.mediaDevices.enumerateDevices().

Метод возвращает Promise, который разрешается в массив объектов MediaDeviceInfo. Каждый объект описывает одно устройство и содержит следующие свойства:

  • deviceId (String): уникальный идентификатор устройства. Может быть пустой строкой, если пользователь не предоставил разрешение на доступ к соответствующему типу устройства.
  • kind (String): тип устройства. Возможные значения: 'audioinput' (микрофон), 'videoinput' (камера), 'audiooutput' (динамик, аудиовыход).
  • label (String): человекочитаемое название устройства (например, 'Built-in Microphone'). Строка будет пустой, если разрешение на доступ не было предоставлено.
  • groupId (String): идентификатор группы, к которой принадлежит устройство. Устройства, физически расположенные в одном аппаратном блоке (например, камера и микрофон в ноутбуке), имеют одинаковый groupId.

Использование функции актуально при создании интерфейсов для выбора устройств в приложениях для видеозвонков, аудиозаписи, стриминга.

Простые примеры использования

Базовый пример получения списка всех устройств:

async function listAllDevices() {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    console.log('Найдено устройств:', devices.length);
    devices.forEach(device => {
      console.log(`${device.kind}: ${device.label || 'Название скрыто'} id=${device.deviceId}`);
    });
  } catch (err) {
    console.error('Ошибка при перечислении устройств:', err);
  }
}
listAllDevices();
// Вывод в консоль (пример):
// Найдено устройств: 4
// audioinput: Internal Microphone id=default
// videoinput: FaceTime HD Camera id=abc123...
// audiooutput: Internal Speakers id=default
// audioinput: USB Microphone id=def456...

Фильтрация только камер:

navigator.mediaDevices.enumerateDevices()
  .then(devices => {
    const cameras = devices.filter(device => device.kind === 'videoinput');
    console.log('Камеры:', cameras);
  });

Альтернативные методы в JavaScript

Прямых аналогов enumerateDevices() в JavaScript нет. Однако часто её используют в связке с другими методами MediaDevices API:

  • getUserMedia(constraints): Запрашивает доступ к конкретным медиаустройствам (камере, микрофону) и возвращает медиапоток. Косвенно, после успешного вызова getUserMedia, функция enumerateDevices() начинает возвращать непустые метки (label) устройств, так как пользователь дал разрешение. Эти функции решают разные задачи: одна запрашивает доступ, другая — только перечисляет доступное.
  • getDisplayMedia(constraints): Специализированный метод для захвата экрана или его части. Не является альтернативой для перечисления физических устройств ввода/вывода.

enumerateDevices() предпочтительнее использовать для построения списка выбора устройств в настройках приложения. getUserMedia() — для непосредственного начала захвата медиа.

Аналоги в других языках программирования

Концепция перечисления устройств существует и в других средах, но реализация сильно зависит от платформы и фреймворков.

Python (библиотека PyAudio):

import pyaudio

p = pyaudio.PyAudio()
# Получение количества и информации об аудиоустройствах
for i in range(p.get_device_count()):
    info = p.get_device_info_by_index(i)
    print(f"{i}: {info['name']} (in:{info['maxInputChannels']}, out:{info['maxOutputChannels']})")
p.terminate()
# Пример вывода:
# 0: HDA Intel PCH: ALC262 Analog (hw:0,0) (in:2, out:2)
# 1: HDA NVidia: HDMI 0 (hw:1,3) (in:0, out:8)

PHP (расширение OpenCV или комманда shell): Прямого стандартного способа нет. Часто используют вызов системных утилит.

// Пример для Linux с использованием shell
$cameras = shell_exec('v4l2-ctl --list-devices 2>/dev/null');
echo $cameras;

C/C++ (Windows, Media Foundation или DirectShow): Требуется сложная, многоэтапная работа с COM-объектами для создания перечислителя устройств.

Ключевое отличие веб-API JavaScript — его кроссплатформенность, асинхронность и сильная зависимость от модели разрешений браузера (метки устройств скрыты без разрешения).

Типичные ошибки

1. Игнорирование асинхронной природы функции. Попытка использовать результат сразу, без await или then.

// Неправильно
const devices = navigator.mediaDevices.enumerateDevices();
devices.forEach(d => console.log(d.label)); // Ошибка: devices.forEach is not a function

// Правильно
navigator.mediaDevices.enumerateDevices().then(devices => {
  devices.forEach(d => console.log(d.label));
});

2. Предположение, что label всегда будет заполнен. До получения разрешения через getUserMedia, метки устройств будут пустыми строками.

// label может быть пустой строкой
navigator.mediaDevices.enumerateDevices().then(devices => {
  const mic = devices.find(d => d.kind === 'audioinput');
  // mic.label может быть ''
  if (mic && mic.label) {
    console.log(`Микрофон: ${mic.label}`);
  } else {
    console.log('Доступ к названию микрофона запрещен или его нет.');
  }
});

3. Отсутствие проверки поддержки MediaDevices API.

if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
  console.error('Ваш браузер не поддерживает enumerateDevices');
  return;
}
// Дальнейший вызов функции...

История изменений

Спецификация Media Capture and Streams, частью которой является enumerateDevices(), развивается, но сама функция остается относительно стабильной. Основные изменения касаются контекста безопасности:

  • Функция изначально была доступна только в безопасных контекстах (HTTPS). В локальной разработке (localhost) она также работает.
  • Политика в отношении свойства label ужесточилась: для его заполнения теперь почти всегда требуется активное разрешение пользователя (через getUserMedia или Persistent Permission). Раньше в некоторых браузерах метки были доступны сразу.
  • Был добавлен тип устройства 'audiooutput' для устройств вывода звука, что позволило управлять, например, выбором колонок или гарнитуры.

Рекомендуется всегда работать с API, учитывая, что label может быть пустым, и обеспечивать корректную обработку таких случаев в интерфейсе.

Расширенные примеры

1. Динамическое построение select-списка для выбора камеры с обновлением при подключении нового устройства.

Пример javascript
async function populateCameraSelect(selectElementId) {
  const selectEl = document.getElementById(selectElementId);
  selectEl.innerHTML = '';
  
  const devices = await navigator.mediaDevices.enumerateDevices();
  const cameras = devices.filter(d => d.kind === 'videoinput');
  
  selectEl.innerHTML = '';
  cameras.forEach(camera => {
    const option = document.createElement('option');
    option.value = camera.deviceId;
    // Используем label или создаем generic-название
    option.text = camera.label || `Камера ${selectEl.length + 1}`;
    selectEl.appendChild(option);
  });
  
  // Слушаем событие изменения доступных устройств
  navigator.mediaDevices.ondevicechange = async () => {
    console.log('Список устройств изменился, обновляем...');
    await populateCameraSelect(selectElementId);
  };
}
// Вызов: populateCameraSelect('cameraSelect');

2. Получение полной информации о группировке устройств (например, найти микрофон и камеру одного硬件ного блока).

Пример javascript
async function getDeviceGroups() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const groups = {};
  
  devices.forEach(device => {
    if (!groups[device.groupId]) {
      groups[device.groupId] = [];
    }
    groups[device.groupId].push(device);
  });
  
  // Вывод групп, где более одного устройства
  Object.values(groups).forEach(group => {
    if (group.length > 1) {
      console.log('Устройства в одной группе:', group.map(d => `${d.kind}: ${d.label}`));
    }
  });
  return groups;
}

3. Комбинированный пример: запрос разрешения на камеру для разблокировки label и последующее перечисление.

Пример javascript
async function unlockLabelsAndList() {
  // Сначала запрашиваем доступ к медиа (любое устройство)
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
    // Останавливаем стрим, он нам больше не нужен, разрешение остаётся
    stream.getTracks().forEach(track => track.stop());
    
    // Теперь enumerateDevices вернет устройства с заполненными label
    const devices = await navigator.mediaDevices.enumerateDevices();
    const detailedDevices = devices.filter(d => d.label);
    console.log('Устройства с разрешенными метками:', detailedDevices);
  } catch (err) {
    console.error('Не удалось получить разрешение:', err);
  }
}

JS enumerateDevices function comments

En
EnumerateDevices Provides information about the media input/output devices available on the system