Сигнализация и видеозвонки

WebRTC обеспечивает одноранговый обмен мультимедиа между двумя устройствами в режиме реального времени. Соединение устанавливается посредством процесса обнаружения и согласования, который называется signaling . Это руководство проведет вас через создание двустороннего видеозвонка.

WebRTC — это полностью одноранговая технология для обмена аудио, видео и данными в реальном времени с одним центральный нюанс. Как обсуждалось в другом месте, должна иметь место форма обнаружения и согласования медиа-формата, чтобы два устройства в разных сетях могли найти друг друга. Этот процесс называется сигнализацией и включает в себя подключение обоих устройств к третьему согласованному серверу. Через этот третий сервер два устройства могут определять местонахождение друг друга и обмениваться сообщениями о переговорах.

В этой статье мы продолжим улучшать чат WebSocket, впервые созданный как часть нашей документации по WebSocket (ссылка на эту статью готовится к выпуску; на самом деле он еще не в сети) для поддержки двустороннего видеозвонка между пользователями. Вы можете опробовать этот пример на Glitch, а также сделать ремикс, чтобы поэкспериментировать с ним. Вы также можете просмотреть весь проект на GitHub.

Примечание. Если вы попробуете пример на Glitch, обратите внимание, что любые изменения внесенный в код немедленно сбросит все соединения. Кроме того, существует короткий период ожидания; экземпляр Glitch предназначен только для быстрых экспериментов и тестирования.

Сервер сигнализации

Установление соединения WebRTC между два устройства требуют использования сигнального сервера , чтобы решить, как их соединить через Интернет. Задача сигнального сервера — служить посредником, позволяя двум одноранговым узлам находить и устанавливать соединение, при этом сводя к минимуму раскрытие потенциально частной информации в максимально возможной степени. Как мы создаем этот сервер и как на самом деле работает сигнальный процесс?

Сначала нам нужен сам сигнальный сервер. WebRTC не определяет транспортный механизм для сигнальной информации. Вы можете использовать все, что захотите, от WebSocket до XMLHttpRequest до почтовых голубей для обмена сигнальной информацией между двумя одноранговыми узлами.

Важно отметить, что сервер этого не делает. Нет необходимости понимать или интерпретировать содержимое данных сигнализации. Хотя это SDP, даже это не имеет большого значения: содержимое сообщения, проходящего через сервер сигнализации, по сути, является черным ящиком. Имеет значение, когда подсистема ICE инструктирует вас об отправке данных сигнализации другому партнеру, вы делаете это, а другой партнер знает, как получить эту информацию и доставить ее в свою подсистему ICE. Все, что вам нужно сделать, это направить информацию туда и обратно. Содержимое не имеет никакого значения для сигнального сервера.

Подготовка чат-сервера к передаче сигналов

Наш чат-сервер использует API WebSocket для отправки информации в виде строк JSON между каждым клиентом и сервер. Сервер поддерживает несколько типов сообщений для обработки таких задач, как регистрация новых пользователей, установка имен пользователей и отправка сообщений публичного чата.

Чтобы сервер мог поддерживать сигнализацию и согласование ICE, нам необходимо обновить код. Нам нужно будет разрешить направлять сообщения одному конкретному пользователю вместо широковещательной рассылки всем подключенным пользователям, а также обеспечить передачу и доставку нераспознанных типов сообщений, при этом серверу не нужно знать, что это такое. Это позволяет нам отправлять сигнальные сообщения, используя тот же сервер, вместо того, чтобы нуждаться в отдельном сервере.

Давайте посмотрим, какие изменения нам нужно внести в чат-сервер, поддерживающий сигнализацию WebRTC. Это находится в файле chatserver.js .

Сначала добавим функцию sendToOneUser () . Как следует из названия, это отправляет сообщение JSON в виде строки определенному имени пользователя.

  function sendToOneUser (target, msgString) {var isUnique = true;  var i;  для (я = 0; я  

Эта функция перебирает список подключенных пользователей до тех пор, пока не найдет одного, соответствующего указанному имени пользователя, а затем отправит сообщение этому пользователю. Параметр msgString представляет собой строковый объект JSON. Мы могли бы заставить его получать наш исходный объект сообщения, но в этом примере это более эффективно. Поскольку сообщение уже преобразовано в строку, мы можем отправить его без дальнейшей обработки. Каждая запись в connectionArray является объектом WebSocket , поэтому мы можем просто вызвать его метод send () напрямую.

Наша исходная демонстрация чата не поддерживала отправку сообщений определенному пользователю. Следующая задача — обновить основной обработчик сообщений WebSocket для поддержки этого. Это включает изменение ближе к концу обработчика сообщений "соединение" :

  if (sendToClients) {var msgString = JSON.stringify (  msg);  var i;  если (msg.target && msg.target.length! == 0) {sendToOneUser (msg.target, msgString);  } else {для (я = 0; я  

Теперь этот код просматривает ожидающее сообщение, чтобы узнать, есть ли у него свойство target . Если это свойство присутствует, оно указывает имя пользователя клиента, которому должно быть отправлено сообщение, и мы вызываем sendToOneUser () , чтобы отправить им сообщение. В противном случае сообщение транслируется всем пользователям путем итерации по списку подключений, отправляя сообщение каждому пользователю.

Поскольку существующий код позволяет отправлять сообщения произвольных типов, никаких дополнительных изменений не требуется.. Теперь наши клиенты могут отправлять сообщения неизвестного типа любому конкретному пользователю, позволяя им отправлять сигнальные сообщения туда и обратно по желанию.

Это все, что нам нужно изменить на стороне сервера. Теперь давайте рассмотрим протокол сигнализации, который мы реализуем.

Разработка протокола сигнализации

Теперь, когда мы создали механизм для обмениваясь сообщениями, нам нужен протокол, определяющий, как эти сообщения будут выглядеть. Это можно сделать несколькими способами; Здесь демонстрируется лишь один из возможных способов структурирования сигнальных сообщений.

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

Обмен описаниями сеансов

При запуске процесса сигнализации, предложение создается пользователем, инициирующим вызов. Это предложение включает описание сеанса в формате SDP и должно быть доставлено принимающему пользователю, которого мы назовем вызываемым . Вызываемый объект отвечает на предложение сообщением answer , также содержащим описание SDP. Наш сигнальный сервер будет использовать WebSocket для передачи сообщений предложения с типом "видео-предложение" и ответа на сообщения с типом "видео-ответ" . Эти сообщения имеют следующие поля:

type
Тип сообщения; либо "видео-предложение" , либо "видео-ответ" .
name
Имя пользователя отправителя.
target
Имя пользователя человека, который получит описание (если вызывающий абонент отправляет сообщение, это указывает вызываемого, и наоборот).
sdp
SDP (Протокол описания сеанса) строка, описывающая локальный конец соединения с точки зрения отправителя (или удаленный конец соединения с точки зрения получателя).

При этом точки, два участника знают, какие кодеки и параметры кодека должны использоваться для этого вызова. Однако они до сих пор не знают, как передавать сами медиаданные. Здесь на помощь приходит Interactive Connectivity Establishment (ICE).

Обмен кандидатами ICE

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

Событие icecandidate отправляется в RTCPeerConnection для завершения процесса добавления локального описания с помощью pc.setLocalDescription (предложение) .

После того, как два одноранговых узла согласуют взаимно совместимого кандидата, каждый одноранговый узел использует SDP этого кандидата для создания и открытия соединения, через которое медиа потом начинает течь. Если позже они согласятся с лучшим (обычно более производительным) кандидатом, поток может изменить форматы по мере необходимости.

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

Каждый кандидат ICE отправляется другому узлу путем отправки сообщения JSON типа "new-ice-кандидата" через сервер сигнализации к удаленному узлу. Каждое сообщение кандидата включает следующие поля:

type
Тип сообщения: " новый-лед-кандидат ".
target
Имя пользователя человека, с которым ведутся переговоры; сервер направит сообщение только этому пользователю.
candidate
Строка кандидата SDP, описывающая предлагаемый метод подключения. Обычно вам не нужно просматривать содержимое этой строки. Все, что нужно вашему коду, — это направить его к удаленному узлу с помощью сервера сигнализации.

Каждое сообщение ICE предлагает протокол связи (TCP или UDP), IP-адрес, номер порта. , тип соединения (например, является ли указанный IP-адрес самим узлом или сервером ретрансляции), а также другую информацию, необходимую для соединения двух компьютеров. Это включает в себя NAT или другие сетевые сложности.

Примечание. Важно отметить следующее: единственное, за что ваш код отвечает во время Согласование ICE принимает исходящих кандидатов от уровня ICE и отправляет их через сигнальное соединение другому одноранговому узлу, когда выполняется ваш обработчик onicecandidate , и принимает сообщения кандидатов ICE от сервера сигнализации (когда сообщение "new-ice-кандидата" получено) и доставляет их на уровень ICE, вызывая RTCPeerConnection.addIceCandidate () . Вот и все.

Содержимое SDP не имеет отношения к вам практически во всех случаях. Избегайте соблазна попытаться усложнить задачу, пока вы действительно не поймете, что делаете. В этом и заключается безумие.

Все, что теперь нужно вашему серверу сигнализации, — это отправлять сообщения, которые он запрашивает. Ваш рабочий процесс также может требовать функциональности входа/аутентификации, но такие детали могут отличаться.

Примечание: onicecandidate Event и createAnswer () Promise — это асинхронные вызовы, которые обрабатываются отдельно.. Будьте уверены, что ваша сигнализация не меняет порядок! Например, addIceCandidate () с ледяными кандидатами сервера должен вызываться после установки ответа с помощью setRemoteDescription().

Поток транзакции сигнализации

Процесс сигнализации включает в себя обмен сообщениями между двумя одноранговыми узлами с использованием посредника, сервера сигнализации. Точный процесс, конечно, будет варьироваться, но в целом есть несколько ключевых точек, в которых обрабатываются сообщения сигнализации:

Процесс сигнализации включает в себя обмен сообщениями между несколькими точками:

  • Клиент каждого пользователя, работающий в веб-браузере
  • Веб-браузер каждого пользователя
  • Сервер сигнализации
  • Веб-сервер, на котором размещена служба чата.

Представьте, что Наоми и Прия обсуждают с помощью программы для чата, и Наоми решает начать видеозвонок между ними. . Вот ожидаемая последовательность событий:

Посмотрим подробнее об этом в ходе этой статьи.

Процесс обмена кандидатами ICE

Когда уровень ICE каждого партнера начинает отправлять кандидатов , он вступает в обмен между различными точками в цепочке, который выглядит следующим образом:

Каждая сторона отправляет кандидатов другой, поскольку она получает их от своего локального уровня ICE; нет по очереди или группировки кандидатов. Как только два партнера согласуют одного кандидата, которого они оба могут использовать для обмена медиа, медиа начинает течь. Каждый одноранговый узел продолжает отправлять кандидатов до тех пор, пока у него не закончатся варианты, даже после того, как медиа уже начал течь. Это делается в надежде определить даже лучшие варианты, чем изначально выбранный.

При изменении условий — например, ухудшение сетевого соединения — один или оба узла могут предложить переключиться на разрешение мультимедиа с более низкой пропускной способностью. , или альтернативный кодек. Это запускает новый обмен кандидатами, после которого может произойти изменение другого медиа-формата и/или кодека. Вы можете узнать больше о кодеках, которые WebRTC требует поддержки браузерами, какие дополнительные кодеки поддерживаются какими браузерами и как выбрать лучшие кодеки для использования, в руководстве Кодеки, используемые WebRTC.

Необязательно. см. RFC 8445: Установление интерактивного подключения, раздел 2.3 («Согласование пар кандидатов и завершение ICE»), если вы хотите лучше понять, как этот процесс завершается на уровне ICE. Вы должны отметить, что кандидаты обмениваются и медиа начинают течь, как только уровень ICE удовлетворяется. Все это делается за кулисами. Наша роль состоит в том, чтобы отправлять кандидатов туда и обратно через сервер сигнализации.

Клиентское приложение

Ядро для любой процесс сигнализации — это его обработка сообщений. Не обязательно использовать WebSockets для сигнализации, но это обычное решение. Разумеется, вам следует выбрать механизм обмена сигнальной информацией, подходящий для вашего приложения.

Давайте обновим клиент чата для поддержки видеозвонков.

Обновление HTML

В HTML для нашего клиента требуется место для отображения видео. Для этого требуются видеоэлементы и кнопка для завершения вызова:

  

Определенная здесь структура страницы использует элементы

, давая нам полный контроль над макетом страницы, позволяя использовать CSS. Мы пропустим детали макета в этом руководстве, но взглянем на CSS на Github, чтобы увидеть, как мы с этим справились. Обратите внимание на два элемента : один для вашего собственного просмотра, один для соединения и элемент .

Элемент с id « Received_video » будет представлять видео, полученное от подключенный пользователь. Мы указываем атрибут autoplay , гарантирующий, что, как только видео начнет поступать, оно будет немедленно воспроизведено. Это избавляет от необходимости явно обрабатывать воспроизведение в нашем коде. Элемент « local_video » представляет предварительный просмотр камеры пользователя; указав атрибут отключено , так как нам не нужно слышать локальный звук на этой панели предварительного просмотра.

Наконец, кнопка "" " для отключения от вызова определен и настроен таким образом, чтобы запуск был отключен (установка этого значения по умолчанию, когда вызов не подключен) и применяется функция hangUpCall () при нажатии. Роль этой функции заключается в том, чтобы закрыть вызов и отправить уведомление сервера сигнализации другому партнеру, запросив его также закрыть.

Код JavaScript

Мы разделим этот код на функциональные области, чтобы было легче описать, как он работает. Основная часть этого кода находится в функции connect () : она открывает сервер WebSocket на порту 6503 и устанавливает обработчик для приема сообщений. в формате объекта JSON. Этот код обычно обрабатывает сообщения текстового чата, как и раньше.

Отправка сообщений на сервер сигнализации

В нашем коде мы вызываем sendToServer () для отправки сообщений на сервер сигнализации. Эта функция использует соединение WebSocket для выполнения своей работы:

  function sendToServer (msg) {var msgJSON = JSON. Stringify (сообщение);  connection.send (msgJSON);}  

Объект сообщения, переданный в эту функцию, преобразуется в строку JSON путем вызова JSON.stringify () , затем мы вызываем функцию send () соединения WebSocket для передачи сообщения на сервер.

Пользовательский интерфейс для запуска вызова

Код, обрабатывающий сообщение "userlist" , вызывает handleUserlistMsg () . Здесь мы настраиваем обработчик для каждого подключенного пользователя в списке пользователей, отображаемом слева от панели чата. Эта функция получает объект сообщения, свойство users которого представляет собой массив строк, определяющих имена каждого подключенного пользователя.

  function handleUserlistMsg (msg  ) {var i;  var listElem = document.querySelector (". userlistbox");  в то время как (listElem.firstChild) {listElem.removeChild (listElem.firstChild);  } msg.users.forEach (функция (имя пользователя) {var item = document.createElement ("li"); item.appendChild (document.createTextNode (имя пользователя)); item.addEventListener ("щелчок", приглашение, ложь); listElem  .appendChild (item);});}  

После получения ссылки на

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

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

Затем мы перебираем массив имен пользователей, используя forEach () . Для каждого имени мы создаем новый элемент

  • , а затем создаем новый текстовый узел, содержащий имя пользователя, с помощью createTextNode () . Этот текстовый узел добавляется как дочерний элемент

  • элемента. Затем мы устанавливаем обработчик для события click в элементе списка, который при нажатии на имя пользователя вызывает наш метод Invite () , который мы посмотрите в следующем разделе.

    Наконец, мы добавляем новый элемент в

    , который содержит все имена пользователей.

    Запуск вызова

    Когда пользователь щелкает имя пользователя, которое он хочет вызвать, функция Invite () вызывается как обработчик события для этого click событие:

      var mediaConstraints = {audio: true,//Нам нужна звуковая дорожка, видео: true//... и  нам нужна видеодорожка}; function приглашение (evt) {if (myPeerConnection) {alert («Вы не можете начать звонок, потому что он уже открыт!»);  } еще {var clickedUsername = evt.target.textContent;  if (clickedUsername === myUsername) {alert («Боюсь, я не могу позволить вам говорить с самим собой. Это было бы странно.»);  возвращение;  } targetUsername = clickedUsername;  createPeerConnection ();  navigator.mediaDevices.getUserMedia (mediaConstraints). затем (функция (localStream) {document.getElementById ("local_video"). srcObject = localStream; localStream.getTracks (). forEach (track => myPeerConnection.addTrack (track, localStream));}) .catch (handleGetUserMediaError);  }}  

    Это начинается с базовой проверки работоспособности: подключен ли пользователь? Если уже существует RTCPeerConnection , они, очевидно, не могут позвонить. Затем имя пользователя, на которого был выполнен щелчок, получается из свойства цели события textContent , и мы проверяем, чтобы убедиться, что это не тот пользователь, который пытается начать вызов.

    Затем мы копируем имя вызываемого пользователя в переменную targetUsername и вызываем createPeerConnection () , функцию, которая будет создать и выполнить базовую настройку RTCPeerConnection .

    После создания RTCPeerConnection мы запрашиваем доступ к камере пользователя и микрофон, вызвав MediaDevices.getUserMedia () , который предоставляется нам через свойство Navigator.mediaDevices.getUserMedia . Когда это успешно, выполняется возвращенное обещание, и выполняется наш обработчик then . В качестве входных данных он получает объект MediaStream , представляющий поток со звуком с микрофона пользователя и видео с его веб-камеры.

    Примечание. Мы могли бы ограничить набор разрешенных медиа-входов определенным устройством или набором устройств, вызвав navigator.mediaDevices.enumerateDevices () , чтобы получить список устройств, фильтрация результирующего списка на основе наших желаемых критериев, затем использование значений deviceId выбранных устройств в поле deviceId в mediaConstraints передан в getUserMedia () . На практике это бывает редко, если вообще когда-либо необходимо, поскольку большую часть этой работы выполняет за вас getUserMedia().

    Мы прикрепляем входящие поток в локальный элемент предварительного просмотра , установив свойство srcObject элемента. Поскольку элемент настроен на автоматическое воспроизведение входящего видео, поток начинает воспроизводиться в нашем локальном окне предварительного просмотра.

    Затем мы перебираем дорожки в потоке, вызывая addTrack () , чтобы добавить каждую дорожку в RTCPeerConnection . Даже если соединение еще не полностью установлено, вы можете начать отправку данных, когда сочтете это целесообразным. Медиа, полученные до завершения переговоров ICE, могут использоваться, чтобы помочь ICE выбрать лучший подход к подключению, тем самым помогая в процессе переговоров..

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

    Как только медиафайл будет подключен к RTCPeerConnection , событие gotiationneeded запускается в соединении, чтобы можно было начать согласование ICE.

    Если при попытке получить локальный медиапоток возникает ошибка, наше предложение catch вызывает handleGetUserMediaError () , который отображает соответствующую ошибку пользователю по мере необходимости.

    Обработка ошибок getUserMedia ()

    Если обещание, возвращаемое getUserMedia () завершается ошибкой, наша функция handleGetUserMediaError () выполняет.

      function handleGetUserMediaError (e) {switch (e.name  ) {case "NotFoundError": alert ("Невозможно начать звонок, потому что нет камеры и/или микрофона" +  "были найдены.");  сломать;  case "SecurityError": case "PermissionDeniedError"://Ничего не делать;  это то же самое, что пользователь отменяет вызов.  сломать;  по умолчанию: alert ("Ошибка при открытии камеры и/или микрофона:" + e.message);  сломать;  } closeVideoCall ();}  

    Сообщение об ошибке отображается во всех случаях, кроме одного. В этом примере мы игнорируем результаты "SecurityError" и "PermissionDeniedError" , обрабатывая отказ в предоставлении разрешения на использование мультимедийного оборудования так же, как и пользователь, отменяющий вызов.

    Независимо от того, почему попытка получить поток не удалась, мы вызываем нашу функцию closeVideoCall () , чтобы закрыть RTCPeerConnection и освободить все ресурсы, уже выделенные в процессе попытки вызова. Этот код предназначен для безопасной обработки частично запущенных вызовов.

    Создание однорангового соединения

    Используется функция createPeerConnection () как вызывающим, так и вызываемым пользователем для создания своих объектов RTCPeerConnection , соответствующих концам соединения WebRTC. Он вызывается с помощью Invite () , когда вызывающий пытается начать вызов, и с помощью handleVideoOfferMsg () , когда вызываемый получает сообщение с предложением от вызывающего.

      function createPeerConnection () {myPeerConnection = new RTCPeerConnection ({iceServers: [//Информация о серверах ICE - используйте свои собственные! {urls: "stun: stun.stunprotocol.org"  }]});  myPeerConnection.onicecandidate = handleICECandidateEvent;  myPeerConnection.ontrack = handleTrackEvent;  myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;  myPeerConnection.onremovetrack = handleRemoveTrackEvent;  myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;  myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;  myPeerConnection. onsignalingstatechange = handleSignalingStateChangeEvent;}  

    При использовании конструктора RTCPeerConnection () мы укажем RTCConfiguration - совместимый объект, предоставляющий параметры конфигурации для соединения. В этом примере мы используем только один из них: iceServers . Это массив объектов, описывающих серверы STUN и/или TURN для уровня ICE, который используется при попытке установить маршрут между вызывающим и вызываемым. Эти серверы используются для определения наилучшего маршрута и протоколов для обмена данными между одноранговыми узлами, даже если они находятся за брандмауэром или используют NAT.

    Примечание. Вы всегда должны использовать серверы STUN/TURN, которыми вы владеете или которые у вас есть на использование. В этом примере используется известный общедоступный сервер STUN, но злоупотребление им является плохим тоном.

    Каждый объект в iceServers содержит как минимум urls , содержащее URL-адреса, по которым можно связаться с указанным сервером. Он также может предоставлять значения username и credential , чтобы разрешить аутентификацию, если это необходимо.

    После создания RTCPeerConnection , мы настраиваем обработчики для важных для нас событий.

    Первые три из этих обработчиков событий обязательны; вы должны обрабатывать их, чтобы делать что-либо, связанное с потоковым мультимедиа с WebRTC. Остальное не обязательно, но может быть полезно, и мы изучим их. Доступно несколько других событий, которые мы также не используем в этом примере. Вот краткое описание каждого из обработчиков событий, которые мы будем реализовывать:

    RTCPeerConnection.onicecandidate
    локальный уровень ICE вызывает ваш обработчик событий icecandidate , когда вам нужно передать кандидата ICE другому партнеру через ваш сервер сигнализации. См. Раздел «Отправка кандидатов ICE» для получения дополнительной информации и просмотра кода для этого примера.
    RTCPeerConnection.ontrack
    Этот обработчик для Событие track вызывается локальным слоем WebRTC, когда трек добавляется к соединению. Это позволяет вам подключать входящие мультимедийные данные к элементу, например, для его отображения. Подробнее см. В разделе «Получение новых потоков».
    RTCPeerConnection.onnegotiationneeded
    Эта функция вызывается всякий раз, когда инфраструктуре WebRTC требуется, чтобы вы запустили процесс согласования сеанса заново. Его задача - создать и отправить предложение вызываемому с просьбой связаться с нами. См. Начало переговоров, чтобы узнать, как мы с этим справимся.
    RTCPeerConnection.onremovetrack
    Этот аналог ontrack вызывается для обработки события removetrack ; он отправляется в RTCPeerConnection , когда удаленный узел удаляет дорожку из отправляемого мультимедиа. См. Раздел Обработка удаления дорожек.
    RTCPeerConnection.oniceconnectionstatechange
    Событие iceconnectionstatechange отправляется уровнем ICE, чтобы позволить вы знаете об изменениях состояния соединения ICE. Это может помочь вам узнать, когда соединение не удалось или было потеряно. Мы рассмотрим код этого примера в состоянии подключения ICE ниже.
    RTCPeerConnection.onicegatheringstatechange
    Уровень ICE отправляет вы - событие icegatheringstatechange , когда процесс сбора кандидатов агентом ICE переходит из одного состояния в другое (например, начало сбора кандидатов или завершение переговоров). См. Состояние сбора ICE ниже.
    RTCPeerConnection.onsignalingstatechange
    Инфраструктура WebRTC отправляет вам signalingstatechange сообщение при изменении состояния процесса сигнализации (или при изменении соединения с сервером сигнализации). См. Состояние сигнализации, чтобы увидеть наш код.

    Начало согласования

    После того, как вызывающий объект создал свое RTCPeerConnection , создается медиапоток и добавил его дорожки к соединению, как показано в разделе «Запуск вызова», браузер доставит событие gotiationneeded в RTCPeerConnection , чтобы указать, что он готов начать переговоры с другим партнером. Вот наш код для обработки события gotiationneeded :

      function handleNegotiationNeededEvent () {myPeerConnection.createOffer (). Then (function (offer) {  return myPeerConnection.setLocalDescription (предложение);}) .then (function () {sendToServer ({name: myUsername, target: targetUsername, type: "video-offer", sdp: myPeerConnection.localDescription});}) .catch (reportError  );}  

    Чтобы начать процесс переговоров, нам нужно создать и отправить предложение SDP партнеру, к которому мы хотим подключиться. Это предложение включает в себя список поддерживаемых конфигураций для подключения, включая информацию о медиапотоке, который мы добавили к подключению локально (то есть видео, которое мы хотим отправить на другой конец вызова), и о любых собранных кандидатах ICE. слоем ICE уже. Мы создаем это предложение, вызывая myPeerConnection.createOffer () .

    Когда createOffer () завершается успешно (выполняя обещание), мы передаем созданную информацию о предложении в myPeerConnection.setLocalDescription () , который настраивает состояние соединения и конфигурации мультимедиа для вызывающей стороны соединения.

    Примечание. С технической точки зрения, строка, возвращаемая createOffer () , является предложением RFC 3264.

    Мы знаем, что описание допустимо и было установлено, когда выполняется обещание, возвращаемое setLocalDescription () .. Это когда мы отправляем наше предложение другому партнеру, создавая новое сообщение "video-offer" , содержащее локальное описание (теперь такое же, как предложение), а затем отправляем его через наш сервер сигнализации. вызываемому. В предложении есть следующие участники:

    type
    Тип сообщения: " видео-предложение ».
    name
    Имя пользователя звонящего.
    target
    Имя пользователя, которого мы хотим вызвать.
    sdp
    Строка SDP, описывающая предложение.

    В случае возникновения ошибки либо в начальном createOffer () , либо в любой из последующих обработчиков выполнения, об ошибке сообщается путем вызова нашей функции reportError () .

    После setLocalDescription () обработчик выполнения, агент ICE начинает отправлять события icecandidate в RTCPeerConnection , по одному для каждой потенциальной конфигурации, которую он обнаруживает. Наш обработчик события icecandidate отвечает за передачу кандидатов другому партнеру.

    Согласование сеанса

    Теперь, когда мы ' Если вы начали переговоры с другим одноранговым узлом и передали предложение, давайте посмотрим, что происходит на стороне вызываемого соединения в течение некоторого времени. Вызываемый получает предложение и вызывает функцию handleVideoOfferMsg () для его обработки. Давайте посмотрим, как вызываемый абонент обрабатывает сообщение "video-offer" .

    Обработка приглашения

    Когда поступает предложение, Функция handleVideoOfferMsg () вызываемого объекта вызывается с полученным сообщением "video-offer" . Эта функция должна делать две вещи. Во-первых, ему нужно создать собственное RTCPeerConnection и добавить к нему дорожки, содержащие аудио и видео с его микрофона и веб-камеры. Во-вторых, ему необходимо обработать полученное предложение, построить и отправить ответ.

      function handleVideoOfferMsg (msg) {var localStream = null;  targetUsername = msg.name;  createPeerConnection ();  var desc = new RTCSessionDescription (msg.sdp);  myPeerConnection.setRemoteDescription (desc) .then (function () {return navigator.mediaDevices.getUserMedia (mediaConstraints);}) .then (функция (поток) {localStream = stream; document.getElementById ("local_video"). srcObject = localStream;  localStream.getTracks (). forEach (track => myPeerConnection.addTrack (track, localStream));}) .then (function () {return myPeerConnection.createAnswer ();}) .then (function (answer) {return myPeerConnection.  setLocalDescription (answer);}) .then (function () {var msg = {name: myUsername, target: targetUsername, type: "video-answer", sdp: myPeerConnection.localDescription}; sendToServer (msg);}). catch (handleGetUserMediaError);}  

    Этот код очень похож на то, что мы делали в функции Invite () в разделе "Запуск вызова". Он начинается с создания и настройки RTCPeerConnection с помощью нашей функции createPeerConnection () . Затем он берет предложение SDP из полученного сообщения "video-offer" и использует его для создания нового объекта RTCSessionDescription , представляющего описание сеанса вызывающего абонента.

    Это описание сеанса затем передается в myPeerConnection.setRemoteDescription () . Это устанавливает полученное предложение как описание удаленного (вызывающего) конца соединения. В случае успеха обработчик выполнения обещания (в предложении then () ) запускает процесс получения доступа к камере и микрофону вызываемого абонента с помощью getUserMedia () , добавление треков к соединению и т. д., как мы видели ранее в Invite () .

    После того, как ответ был создан с помощью myPeerConnection.createAnswer () , описание локального конца соединения устанавливается на SDP ответа путем вызова myPeerConnection.setLocalDescription () , затем ответ передается через сервер сигнализации вызывающему, чтобы сообщить им, каков ответ.

    Любые ошибки перехватываются и передаются в handleGetUserMediaError () , как описано в разделе Обработка ошибок getUserMedia () .

    Примечание. Как и в случае с вызывающим, когда обработчик выполнения setLocalDescription () запущен, браузер начинает запускать события icecandidate , которые Вызываемый должен обработать, по одному для каждого кандидата, который должен быть передан удаленному партнеру.

    Отправка кандидатов ICE

    Процесс согласования ICE включает каждого одноранговый узел многократно отправляет кандидатов другому, пока не закончатся возможные способы поддержки потребностей RTCPeerConnection в передаче мультимедиа. Поскольку ICE не знает о вашем сервере сигнализации, ваш код обрабатывает передачу каждого кандидата в вашем обработчике для события icecandidate .

    Ваш onicecandidate обработчик получает событие, свойство кандидата которого является SDP, описывающим кандидата (или null , чтобы указать, что уровень ICE исчерпал возможные варианты конфигурации). Содержимое кандидата - это то, что вам нужно передать с помощью вашего сервера сигнализации. Вот реализация нашего примера:

      function handleICECandidateEvent (event) {if (event.candidate) {sendToServer ({type: "new-ice-кандидата", target: targetUsername, кандидат  : событие. кандидат});  }}  

    Создает объект, содержащий кандидата, затем отправляет его другому одноранговому узлу с помощью функции sendToServer () , ранее описанной в разделе «Отправка сообщений». к серверу сигнализации. Свойства сообщения:

    type
    Тип сообщения: "new- ледяной кандидат ".
    target
    Имя пользователя, которому должен быть доставлен кандидат ICE. Это позволяет серверу сигнализации маршрутизировать сообщение.
    candidate
    SDP, представляющий кандидата, которого уровень ICE хочет передать на другой партнер.

    Формат этого сообщения (как и в случае со всем, что вы делаете при обработке сигналов) полностью зависит от вас, в зависимости от ваших потребностей; при необходимости вы можете предоставить другую информацию.

    Примечание: важно помнить, что icecandidate событие не отправляется, когда кандидаты ICE прибывают с другого конца вызова. Вместо этого они отправляются вашим собственным концом вызова, чтобы вы могли взять на себя работу по передаче данных по любому выбранному вами каналу. Это может сбивать с толку, если вы новичок в WebRTC.

    Получение кандидатов ICE

    Сервер сигнализации доставляет каждого кандидата ICE к узлу назначения, используя что угодно метод, который он выбирает; в нашем примере это объекты JSON со свойством type , содержащим строку «new-ice-кандидата» . Наша функция handleNewICECandidateMsg () вызывается нашим основным кодом входящего сообщения WebSocket для обработки этих сообщений:

      function handleNewICECandidateMsg (msg) {var кандидата  = новый RTCIceCandidate (msg.candidate);  myPeerConnection.addIceCandidate (кандидат) .catch (reportError);}  

    Эта функция создает объект RTCIceCandidate , передавая полученный SDP в его конструктор, затем доставляет кандидата на уровень ICE, передавая его в myPeerConnection.addIceCandidate () . Это передает нового кандидата ICE на локальный уровень ICE, и, наконец, наша роль в процессе обработки этого кандидата завершена.

    Каждый одноранговый узел отправляет другому одноранговому узлу кандидата для каждой возможной конфигурации транспорта. которые, по его мнению, могут быть полезны для обмена СМИ. В какой-то момент два коллеги соглашаются, что данный кандидат является хорошим выбором, и они открывают соединение и начинают обмениваться медиа. Однако важно отметить, что согласование ICE не прекращается при прохождении медиа. Вместо этого кандидаты могут по-прежнему обмениваться после начала разговора, либо при попытке найти лучший метод подключения, либо потому, что они уже были в транспортном режиме, когда одноранговые узлы успешно установили свое соединение..

    Кроме того, если что-то произойдет, чтобы вызвать изменение в сценарии потоковой передачи, переговоры начнутся снова, а событие gotiationneeded будет отправлено в RTCPeerConnection , и весь процесс начнется заново, как описано ранее. Это может произойти в различных ситуациях, в том числе:

    • Изменения статуса сети, такие как изменение полосы пропускания, переход от Wi-Fi к подключению к сотовой связи и т. Д.
    • Переключение между передней и задней камерами на телефоне.
    • Изменение конфигурации потока, например его разрешения или частоты кадров.
    Получение новых потоков

    Когда новые треки добавляются в RTCPeerConnection - либо путем вызова его addTrack () , или из-за повторного согласования формата потока - событие track устанавливается на RTCPeerConnection для каждой дорожки, добавленной к соединению. Для использования недавно добавленных мультимедийных файлов требуется реализация обработчика события track . Обычно требуется прикрепить входящие мультимедийные данные к соответствующему элементу HTML. В нашем примере мы добавляем поток трека к элементу , который отображает входящее видео:

      function handleTrackEvent (event) {  document.getElementById ("полученное_видео"). srcObject = event.streams [0];  document.getElementById ("hangup-button"). disabled = false;}  

    Входящий поток прикрепляется к "полученное_видео" , а элемент "Hang Up" включен, чтобы пользователь мог положить трубку.

    После завершения этого кода видео, отправленное другим партнером, отображается в окне локального браузера!

    Обработка удаления дорожек

    Ваш код получает событие removetrack , когда удаленный узел удаляет дорожку из соединения, вызывая RTCPeerConnection.removeTrack () . Наш обработчик для "removetrack" :

      function handleRemoveTrackEvent (event) {var stream = document.getElementById ("receive_video"). SrcObject  ;  var trackList = stream.getTracks ();  если (trackList.length == 0) {closeVideoCall ();  }}  

    Этот код извлекает входящее видео MediaStream из «Received_video» Атрибут элемента srcobject , затем вызывает метод потока getTracks () для получения массива треков потока.

    Если длина массива равна нулю, что означает, что в потоке не осталось дорожек, мы завершаем вызов, вызывая closeVideoCall () . Это полностью восстанавливает наше приложение до состояния, в котором оно готово к запуску или получению другого вызова. См. Раздел Завершение вызова, чтобы узнать, как работает closeVideoCall () ..

    Завершение вызова

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

    Разрыв связи

    Когда пользователь нажимает кнопку «Положить трубку», чтобы завершить звонок, hangUpCall () вызывается:

      function hangUpCall () {closeVideoCall ();  sendToServer ({name: myUsername, target: targetUsername, type: "hang-up"});}  

    hangUpCall () выполняет closeVideoCall () , чтобы закрыть и сбросить соединение и освободить ресурсы. Затем он формирует сообщение "зависание" и отправляет его на другой конец вызова, чтобы сообщить другому партнеру о необходимости аккуратно выключиться.

    Окончание вызов

    Функция closeVideoCall () , показанная ниже, отвечает за остановку потоков, очистку и удаление RTCPeerConnection объект:

      function closeVideoCall () {var remoteVideo = document.getElementById ("Received_video");  var localVideo = document.getElementById ("local_video");  если (myPeerConnection) {myPeerConnection.ontrack = null;  myPeerConnection.onremovetrack = null;  myPeerConnection.onremovestream = null;  myPeerConnection.onicecandidate = null;  myPeerConnection.oniceconnectionstatechange = ноль;  myPeerConnection.onsignalingstatechange = ноль;  myPeerConnection.onicegatheringstatechange = ноль;  myPeerConnection.onnegotiationneeded = ноль;  если (remoteVideo.srcObject) {remoteVideo.srcObject.getTracks (). forEach (track => track.stop ());  } если (localVideo.srcObject) {localVideo.srcObject.getTracks (). forEach (track => track.stop ());  } myPeerConnection.close ();  myPeerConnection = null;  } remoteVideo.removeAttribute ("src");  remoteVideo.removeAttribute ("srcObject");  localVideo.removeAttribute ("src");  remoteVideo.removeAttribute ("srcObject");  document.getElementById ("кнопка зависания"). disabled = true;  targetUsername = null;}  

    После получения ссылок на два элемента мы проверяем, существует ли соединение WebRTC; если это так, мы переходим к отключению и закрытию вызова:

    1. Все обработчики событий удаляются. Это предотвращает запуск случайных обработчиков событий, пока соединение находится в процессе закрытия, что может вызвать ошибки.
    2. И для удаленных, и для локальных видеопотоков мы перебираем каждую дорожку, вызывая MediaStreamTrack.stop () , чтобы закрыть каждый из них.
    3. Закройте RTCPeerConnection , вызвав myPeerConnection.close () .
    4. Установите для myPeerConnection значение null , чтобы наш код узнал, что текущего вызова нет; это полезно, когда пользователь щелкает имя в списке пользователей.

    Затем для входящих и исходящих элементов мы удаляем их атрибуты src и srcobject , используя их методы removeAttribute () . Это завершает отделение потоков от элементов видео.

    Наконец, мы устанавливаем для свойства disabled значение true на Кнопка "Положить трубку", что делает ее неактивной, пока нет разговора; затем мы устанавливаем для targetUsername значение null , поскольку мы больше ни с кем не разговариваем. Это позволяет пользователю позвонить другому пользователю или принять входящий вызов.

    Работа с изменениями состояния

    Существует ряд дополнительных событий, для которых вы можете установить слушателей который уведомляет ваш код о различных изменениях состояния. Мы используем три из них: iceconnectionstatechange , icegatheringstatechange и signalingstatechange .

    ICE состояние соединения

    iceconnectionstatechange , события отправляются в RTCPeerConnection уровнем ICE при изменении состояния соединения (например, когда вызов завершается с другого конца).

      дескриптор функцииICEConnectionStateChangeEvent (событие) {переключатель (myPeerConnection.iceConnectionState) {case "closed": case "failed": closeVideoCall ();  сломать;  }}  

    Здесь мы применяем нашу функцию closeVideoCall () , когда состояние соединения ICE изменяется на "закрыто" или "не удалось" . Это обрабатывает завершение нашего конца соединения, чтобы мы были готовы начать или снова принять вызов.

    Примечание. Не следите за состоянием сигнализации отключено , поскольку это может указывать на временные проблемы и может вернуться в состояние connected через некоторое время. Наблюдение за ним закроет видеозвонок при любой временной проблеме с сетью.

    Состояние сигнализации ICE

    Точно так же мы наблюдаем за signalingstatechange код> события. Если состояние сигнализации изменяется на closed , мы аналогичным образом закрываем вызов.

      function handleSignalingStateChangeEvent (event) {switch (myPeerConnection.signalingState)  {case "закрыто": closeVideoCall ();  сломать;  }};  

    Примечание. Состояние сигнализации closed устарело в пользу из closed iceConnectionState . Мы следим за этим здесь, чтобы добавить немного обратной совместимости.

    Состояние сбора ICE

    icegatheringstatechange events используются, чтобы сообщить вам об изменении состояния процесса сбора кандидатов ICE. В нашем примере это ни для чего не используется, но может быть полезно наблюдать за этими событиями в целях отладки, а также для определения того, когда сбор кандидатов завершился.

      function handleICEGatheringStateChangeEvent  (event) {//В нашем примере информация просто записывается в консоль,//но вы можете делать все, что вам нужно.}  

    Следующие шаги

    Теперь вы можете опробовать этот пример на Glitch, чтобы увидеть его в действии. Откройте веб-консоль на обоих устройствах и просмотрите зарегистрированный вывод - хотя вы не видите его в коде, как показано выше, код на сервере (и на GitHub) имеет много вывода консоли, поэтому вы можете видеть сигналы и процессы подключения на работе.

    Другим очевидным улучшением было бы добавление функции "звонка", чтобы вместо простого запроса у пользователя разрешения на использование камеры и микрофона, "Пользователь X был звоню. Хотите ответить? " сначала появляется приглашение.

    См. также

    • WebRTC API
    • Интернет мультимедийные технологии
    • Руководство по типам и форматам мультимедиа в Интернете
    • Media Capture and Streams API
    • Media Capabilities API
    • MediaStream Recording API
    • Идеальный шаблон согласования


    High End Системы

    Устранение неполадок сервера

    Обзор

    Цифровое освещение HES Все серверные компьютеры построены с использованием стандартных отраслевых компонентов с использованием индивидуализированной операционной системы XP Embedded, которая позволяет нам заблокировать систему и устранить многие проблемы, с которыми сталкивается стандартный настольный компьютер. Однако, как и на любом компьютере, компоненты могут выходить из строя и могут возникнуть проблемы со стабильностью.

    Понимание и способность сообщать о различных типах ошибок поможет персоналу службы поддержки HES быстро решить проблемы, с которыми вы сталкиваетесь. Мы часто получаем отчет о том, что у пользователя был сервер «синий экран» или «BSOD» (синий экран смерти), при дальнейшем исследовании мы обнаруживаем, что произошел сбой другого типа или что сервер просто не загрузился должным образом. Пути устранения неполадок для каждого из этих событий очень различаются, поэтому понимание и сообщение о различных типах потенциальных проблем имеет решающее значение для решения проблемы.

    Здесь мы рассмотрим несколько категорий проблем:

    • События синего экрана или BSOD
    • Ошибки приложения
    • Общие проблемы загрузки

    События синего экрана

    Сбой "синего экрана", или BSOD, часто является одним из событий, о котором чаще всего неправильно сообщают. Важно называть его таковым только в том случае, если он отображает настоящий синий экран с белым текстом на нем, как показано ниже:

    Любой другой тип ошибки или проблемы технически не считается "синим экраном"..

    Причины

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

    • Аппаратный сбой или ошибка, чаще всего неисправная видеокарта, материнская плата или, возможно, ОЗУ.
    • Видеокарта. Драйвер или другая проблема с драйвером.
    • В некоторых случаях поврежденный файл на жестком диске.
    • Проблемы с качеством электроэнергии, например, работа от генератора.
    Устранение неполадок

    Мгновенная загрузка в синий экран каждый раз
    Если сервер загружается в синий экран ошибка каждый раз мгновенно, значит, у вас, скорее всего, неисправное оборудование. Часто бывает сложно определить, какое именно оборудование вышло из строя, без другого сервера для тестирования компонентов.

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

    Это также может быть результатом повреждения O/S файл. Повторное создание образа сервера с текущим диском восстановления решит проблему, если это является причиной.

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

    Мы разработали тест, который можно использовать, чтобы определить, является ли это проблемой.

    Тест при повышенной температуре нажмите здесь

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

    Ошибки приложения

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

    Важно отметить, что это НЕ «синий экран» или ошибка BSOD, а ошибка приложения.

    Причины

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

    • Проблемы с кодировкой содержимого.
    • Поврежденные файлы содержимого
    • Программные ошибки
    Устранение неполадок

    Самое главное Чтобы найти причину, нужно понять, что выполнялось, когда это произошло.

    • В случае файлов с плохим содержимым проблему обычно легко воспроизвести, воспроизведя тот же файл и проверив, проблема возникает снова. Если да, то, скорее всего, это сам файл содержимого. Это можно проверить, оставив все остальные параметры в вашей реплике такими же и переключившись на файл стандартного содержимого для воспроизведения.
    • Если проблема по-прежнему возникает со стандартным содержимым, при выполнении определенного действия, тогда это может быть программный дефект. Обратитесь в службу поддержки HES, чтобы предупредить их о проблеме вместе с файлом демонстрации или действиями по воспроизведению проблемы, и, скорее всего, можно будет найти обходной путь.
    • Если проблема не может быть воспроизведена или является случайной, она может быть более тонкой проблемой программного обеспечения, связанной с памятью или синхронизацией ... или гремлинов.
    • Если проблема случайная или постоянная, то это может быть проблема с файлом приложения или параметром реестра. Повторная загрузка того же программного обеспечения в прибор обычно решает эти проблемы.

    Проблемы с загрузкой

    Третий сценарий, который мы рассмотрим, - это сервер, который не загружается полностью. Хотя это приводит к той же проблеме отсутствия вывода графического движка, важно понимать, что этот сценарий сильно отличается от других случаев, рассмотренных выше.

    Причины

    Существует ряд различных причин, которые мы будем устранять поэтапно в процессе загрузки. Ниже приведены наиболее частые причины проблем с загрузкой:

    • Неисправное оборудование
    • Незакрепленные соединители
    Устранение неполадок

    Появляется ли логотип HES на дисплее при включении питания?
    Логотип HES, который отображается, когда устройство Первое включение - это заставка BIOS для материнской платы. Если вы вообще не видите экран-заставку BIOS, то это указывает либо на неплотное соединение, либо на проблему с оборудованием.

    • Откройте устройство и проверьте все видео соединения, чтобы убедиться, что они плотные.
    • Откройте крышку сервера, когда вы включаете устройство, вентилятор ЦП включается и остается включенным?

    Если вентилятор включается только на мгновение, то останавливается или не включается в все, то есть проблема с оборудованием. Обычно этот тип сбоя указывает на неисправную материнскую плату или, возможно, на редкую проблему с источником питания.

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

    Отображает ли сервер текст «Вставить загрузочный носитель»?
    Если вы видите, что устройство начинает загружаться, и на нем отображается экран, содержащий фраза «Вставьте загрузочный носитель», то это, скорее всего, указывает на следующее:

    • Слабое соединение с жестким диском
    • Плохой кабель SATA
    • Неисправный жесткий диск

    Чаще всего это ненадежное соединение, хотя в некоторых случаях это может быть неисправный кабель SATA. Хотя это случается редко, это также может быть результатом неисправности жесткого диска. Если это действительно сбой жесткого диска, обратитесь в службу поддержки HES за заменой диска, на котором предварительно установлен образ с программным обеспечением и стандартным содержимым для вашего сервера.

    Оцените статью
    logicle.ru
    Добавить комментарий