Real-Time Backend
Media Streams
Google Meet запускается прямо в браузере: нажал ссылку - и ты уже в звонке с видео и звуком. Никаких установок. Как браузер получает доступ к камере и микрофону, и как это видео попадает к другому участнику?
- **Google Meet** использует adaptive constraints: при хорошей сети 1080p/30fps, при деградации - applyConstraints() снижает до 360p/15fps без пересоздания соединения. Это позволяет поддерживать 300M+ встреч в день с адаптацией к реальным условиям сети.
- **Zoom Web Client** (без установки) строится на getUserMedia + getDisplayMedia. Screen sharing поддерживается в Chrome, Firefox, Edge через стандартный API. Safari добавил поддержку getDisplayMedia только в 2022 году - до этого Zoom Web не поддерживал Safari screen share.
- **Figma** использует getDisplayMedia для функции FigJam screen sharing: участник сессии может показать свой экран прямо в браузере. track.onended отслеживает когда пользователь нажимает 'Stop Sharing' и автоматически убирает превью у всех участников.
- **Loom** (screen recorder) строится целиком на getDisplayMedia + getUserMedia: захват экрана, веб-камеры и микрофона одновременно, микширование через MediaRecorder API. 14M+ пользователей записывают видео прямо в браузере.
getUserMedia
getUserMedia() - браузерный API для захвата медиа с камеры и микрофона. Возвращает Promise<MediaStream> после того, как пользователь явно разрешил доступ. Разрешение хранится per-origin и запрашивается только один раз; последующие вызовы используют кэшированное разрешение до сброса.
getUserMedia работает только в secure context (HTTPS или localhost). Google Meet запрашивает камеру и микрофон раздельно и gracefully degrading - если камера недоступна, аудио-звонок всё равно работает. Zoom Web требует HTTPS и использует getUserMedia как основу, добавляя собственные noise suppression алгоритмы через WebAudio API.
Пользователь открыл страницу с видеозвонком по HTTP (не HTTPS). Что произойдёт при вызове getUserMedia()?
MediaStreamTrack
MediaStream состоит из MediaStreamTrack объектов - каждый track представляет один поток данных (видео или аудио) от конкретного устройства. Tracks можно включать/выключать, заменять (e.g. смена камеры), клонировать, или применять к ним WebAudio обработку.
track.enabled = false vs track.stop() - принципиальная разница. enabled=false продолжает захват (индикатор камеры горит), но отправляет пустой сигнал. stop() полностью освобождает устройство. Google Meet использует enabled=false для mute - это позволяет быстро unmute без задержки повторного захвата устройства.
Пользователь нажал кнопку 'выключить камеру'. Что правильно: videoTrack.enabled = false или videoTrack.stop()?
MediaTrackConstraints
Constraints позволяют задать требования к захватываемому медиа: разрешение, частоту кадров, выбор конкретного устройства, эхоподавление, noise cancellation. Браузер старается выполнить exact constraints, ideal constraints - по возможности.
Zoom и Google Meet адаптивно меняют constraints в runtime: при деградации сети снижают frameRate через applyConstraints() вместо пересоздания. Agora SDK при инициализации собирает getCapabilities() для определения поддерживаемых разрешений конкретного устройства, и только потом устанавливает constraints.
getUserMedia вызван с video: { width: { exact: 4096 } } (4K), но камера поддерживает максимум 1920px. Что произойдёт?
Screen Sharing
getDisplayMedia() - отдельный API для захвата экрана, окна или вкладки. В отличие от getUserMedia, пользователь сам выбирает источник из системного диалога - приложение не может программно указать конкретное окно. Возвращает MediaStream с video track.
Google Meet переключает camera/screen через replaceTrack() без пересоздания PeerConnection - это zero-downtime смена источника. Zoom Web использует getDisplayMedia с contentHint: 'detail' для экрана (лучше шарпнесс текста) vs contentHint: 'motion' для камеры (лучше плавность видео). Figma позволяет шарить экран через браузер без приложения именно благодаря getDisplayMedia API.
Screen sharing через getDisplayMedia захватывает всё, включая другие вкладки и приложения, без ведома пользователя
Пользователь явно выбирает источник в системном диалоге - конкретную вкладку, окно или экран. Приложение видит только выбранный источник.
getDisplayMedia спроектирован с privacy-first подходом. Диалог выбора управляется браузером/OS, а не страницей. Браузер также показывает постоянный индикатор записи экрана и кнопку 'Stop Sharing', которую пользователь может нажать в любой момент.
Пользователь шарит экран через getDisplayMedia. Приложение хочет автоматически переключиться на конкретное окно по нажатию кнопки, без диалога. Возможно ли это?
Итоги
- **getUserMedia()** запрашивает доступ к камере/микрофону, работает только на HTTPS. Возвращает MediaStream с независимыми tracks.
- **track.enabled = false** - mute без остановки устройства (быстрый toggling). **track.stop()** - полное освобождение устройства (нужен новый getUserMedia для включения).
- **Constraints** позволяют управлять разрешением, fps, AEC/NS/AGC. exact - строгое требование (ошибка если невозможно), ideal - желаемое.
- **getDisplayMedia()** - отдельный API для screen sharing с обязательным пользовательским диалогом. replaceTrack() меняет источник видео без пересоздания PeerConnection.
Связанные темы
Media Streams - исходный материал для WebRTC пайплайна. Они поступают в PeerConnection и обрабатываются на пути к получателю:
- STUN и TURN — MediaStream добавляется в RTCPeerConnection через addTrack(); ICE (STUN/TURN) устанавливает канал, по которому эти треки будут переданы
- SFU архитектура — SFU получает MediaStream от каждого участника и избирательно пересылает нужные треки подписчикам; simulcast использует несколько LayerStream от одного getUserMedia
- Data Channels — MediaStream tracks (audio/video) и DataChannels мультиплексируются в одном RTCPeerConnection; DataChannel используется для metadata о медиа (mute state, активный спикер)
Вопросы для размышления
- Почему браузер показывает постоянный индикатор (красная точка) при активном getUserMedia, и как это связано с track.stop()?
- Zoom позволяет выбирать виртуальный фон. На каком этапе медиапайплайна применяется такой фильтр, и как это сделать через стандартные Web API?
- В мобильном браузере facingMode: 'environment' даёт заднюю камеру. Как переключить камеру в runtime без пересоздания PeerConnection?