Перейти к содержанию

Выбор устройств и ошибки доступа

В первом уроке «Разговор» получил локальный поток с устройствами по умолчанию. Теперь пользователю нужно выбрать конкретную камеру и микрофон, например гарнитуру вместо встроенного устройства. В этом уроке добавим enumerateDevices, список источников и явное применение выбора. Смена устройства не должна молча уничтожать пригодный поток, если новое устройство недоступно или пользователь отказал в разрешении.

Состояние lesson-02 использует прежний MediaStream и дополняет страницу двумя списками. Перечень запрашивается после получения превью, когда браузер может раскрыть разрешённые сведения об устройствах. Метки и доступность различаются по браузеру и состоянию разрешения. Список не следует считать полным каталогом всего оборудования системы независимо от выбора пользователя.

Перечень устройств

enumerateDevices возвращает элементы с видом устройства, идентификатором и возможной меткой. В «Разговоре» нас интересуют videoinput и audioinput. Выходные динамики имеют другой вид и не нужны для выбора источника захвата. Разделение по kind позволяет строить отдельные списки, а не угадывать назначение устройства по его имени.

Метка может оказаться пустой. Код показывает нейтральное имя «Устройство» с порядковым номером, вместо того чтобы выдумывать название камеры. Идентификатор используется как непрозрачное значение выбора; его не нужно интерпретировать как серийный номер или постоянную личность устройства. Политика браузера может менять доступные сведения, особенно между профилями и состояниями разрешения.

Ниже дан полный listDevices из src/app.js. Переменная $ выбирает существующие элементы camera и microphone. Функция сохраняет предыдущий выбранный идентификатор, если он ещё присутствует в новом перечне. Текст метки назначается через textContent, поскольку имя устройства является данными, а не готовой HTML-разметкой.

async function listDevices(){
 const items=await navigator.mediaDevices.enumerateDevices();
 for(const [id,kind]of [['camera','videoinput'],['microphone','audioinput']]){
  const field=$(id),selected=field.value;field.replaceChildren();
  for(const device of items.filter(d=>d.kind===kind)){const option=document.createElement('option');option.value=device.deviceId;option.textContent=device.label || `Устройство ${field.length+1}`;field.append(option);}
  if([...field.options].some(x=>x.value===selected))field.value=selected;
 }
}

После самостоятельного разрешения превью списки должны содержать доступные разрешённые источники. Если подключена только одна камера, список не обязан показывать несколько вариантов. При отсутствии метки будет нейтральная подпись. Это ожидаемые ветви API; реальное оборудование при авторстве не исследовалось. Статья не содержит снимка предполагаемого меню устройств конкретной ОС.

Применение выбора

Новый запрос передаёт deviceId: { exact: ... } для выбранного источника. Здесь exact оправдан пользовательским действием: человек явно выбрал определённое устройство. Если оно исчезло, подставить другое без сообщения было бы неожиданно. Для размера кадра по-прежнему используется предпочтение, поскольку выбор разрешения и выбор физического источника имеют разную семантику.

Код сначала запрашивает новый поток, затем заменяет ссылку и останавливает старые дорожки. Если запрос отклонён до получения потока, старое превью остаётся доступным. Это проще понять, чем последовательность «остановить старое, попытаться открыть новое, остаться без обоих». Однако некоторые устройства или ОС ограничивают одновременное использование источников, поэтому будущая проверка должна учитывать конкретный браузер.

Смена двух источников в одном запросе также требует внимательного объяснения. Когда запрос включает аудио и видео, ошибка необходимого устройства может отвергнуть весь запрос. Если продукт хочет отдельно поменять только камеру, лучше запросить отдельную видеодорожку и определить, как она соединяется с уже имеющимся аудио. Наш учебный вариант обновляет полный поток ради понятного самостоятельного состояния.

Различимые ошибки

NotAllowedError связан с отсутствием допуска, NotFoundError — с невозможностью найти подходящий источник, NotReadableError — с невозможностью прочитать устройство даже после разрешения. OverconstrainedError указывает на неудовлетворённые ограничения. Эти имена полезны для диагностики, но пользовательский текст должен описывать доступное действие, а не обвинять человека в неверной настройке без доказательств.

Например, исчезнувшая USB-камера требует обновить список и выбрать доступный источник. Повторять тот же жёсткий deviceId бесконечно бесполезно. При отказе разрешения уместно объяснить, что запрос не выполнен, и дать возможность повторить действие осознанно. При занятом устройстве нужно исследовать условия ОС и других приложений; название ошибки не является доказательством конкретной программы, удерживающей камеру.

Мы не добавляем фиктивный сигнал «камера подключена» до завершения запроса. Список устройств сообщает наличие доступного кандидата, а полученная дорожка сообщает успешный захват. Это два разных наблюдения. Устройство способно исчезнуть между перечислением и запросом, поэтому даже корректный выбор в списке не отменяет обработку ошибки асинхронной операции.

Изменение оборудования

В более полном интерфейсе событие devicechange помогает обновлять перечень после подключения или отключения источников. Но поддержка и условия получения события зависят от браузера. В учебном состоянии список обновляется после успешного захвата; автоматическая политика смены устройств не реализована. Человек должен видеть, какой источник выбран, и решать, когда применять новый выбор.

При обновлении списка важно не сбрасывать выделение без причины. Если текущий идентификатор ещё доступен, код сохраняет его. Если исчез, браузерный список получает существующий вариант, а приложение должно сообщить о необходимости нового захвата. Само изменение выбранного option не меняет текущие дорожки. Захват остаётся отдельным действием кнопки, что делает интерфейс предсказуемее.

Подготовка к передаче

Когда появится RTCPeerConnection, смена дорожки потребует обновить sender, а не только локальный srcObject. В общем контроллере архивных состояний этот путь включается на последующих этапах. Там также есть ограничение: последовательная замена двух sender не является атомарной операцией. Если первая прошла, а вторая отказала, потребуется откат или отдельная смена источников; учебный код не скрывает этот крайний случай.

Для самостоятельного разбора выберите другую камеру, затем мысленно отключите её до нажатия применения. Объясните, почему старый поток должен оставаться пригодным, а новый запрос обязан сообщить отказ. Затем сравните выбор источника и выключение дорожки: первое получает ресурс, второе меняет использование существующего. Следующий урок введёт собственную разрешённую комнату, чтобы браузеры могли обменяться служебными сообщениями перед передачей медиа.

Ещё один полезный вариант — выбрать микрофон, который исчезает после изменения настроек ОС. До нового успешного запроса приложение всё ещё имеет старую дорожку, но её готовность тоже может измениться. Интерфейс будущего выпуска должен наблюдать завершение захвата и предлагать явную замену. Автоматическая запись имени устройства в список не гарантирует, что звук продолжает поступать. Читатель должен отличать сохранённый выбор от текущего действующего источника и обрабатывать оба состояния отдельно.

Оглавление серии · Исходные состояния проекта.

API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.