Рубрики
Без рубрики

Создайте групповой видеочат веб-приложение

Привет всем, сегодня я хочу пройтись по созданию простого веб -приложения для видеочата группового видео, очень похоже … Tagged с HTML, JavaScript, Agoraio.

Привет всем, сегодня я хочу пройти через как создать простое веб -приложение для видеочата группы, очень похожее на Google Hangouts, Skype или любую другую платформу видеочата, которую вы предпочитаете. Учитывая сегодняшний фрагментированный ландшафт JS, я хотел написать этот урок, используя самые основные версии HTML, CSS и JS. Прежде чем вы скажете это, я знаю, что знаю, что JQuery не ваниль JS, но ваниль JS может быть немного условно для определенных задач DOM, я решил использовать jQuery, чтобы упростить несколько вещей. Мы собираемся разрезать несколько углов и использовать начальную загрузку, чтобы нам не приходилось беспокоиться о написании слишком большого количества пользовательских CSS.

Для толпы TLDR: проверьте Демонстрация кода в действии на страницах GitHub.

Предварительные условия

  • A Простой веб -сервер – Мне нравится использовать Живой сервер
  • Сертификат SSL или способ иметь подключение к HTTPS (я использую ngrok )
  • Учетная запись разработчика с Agora.io
  • Понимание HTML/CSS/JS
  • Понимание того, как функция начальной загрузки и jQuery (минимальные знания необходимы)

Основная структура (HTML)

Давайте начнем с избавления нашей основной структуры HTML. Есть несколько элементов пользовательского интерфейса, таких как локальный видеопоток, удаленные видеопотоки, панель инструментов, которые будут содержать кнопки для переключения аудио/видеопотоков, кнопка, чтобы поделиться нашим экраном с группой, и, наконец, способ для Оставьте чат (Мы добавим кнопки чуть позже) Анкет


  
    Agora Group Video Chat Demo
    
    
  
  
    

Добавление в CSS и JS

Теперь, когда у нас есть наша база, мы можем начать расширяться. Используя Bootstrap для нашего CSS, мы можем быстро придумать наш HTML с несколькими простыми классами. В приведенном выше коде давайте добавим ссылки CSS (показано ниже) в код, где мы видим блок комментариев Анкет





Хотя Boostrap великолепен, но это не целостное решение, поэтому я добавил несколько дополнительных блоков CSS в пользовательском файле CSS (Мы доберемся до этого чуть позже) . Это поможет отрегулировать несколько элементов, которые мы не получим идеального из коробки с начальной загрузкой. Я также добавил Font Awesome CSS Структура, потому что нам нужно будет включить значки для различных кнопок, а FA делает их действительно простыми.

Как я уже упоминал, Bootstrap отличная, но иногда вам все еще нужно немного индивидуальных CSS. Вот блоки стиля для упомянутого вышеупомянутого style.css Анкет

#buttons-container {
  position: absolute;
  z-index: 2;  
  width: 100vw;
}

#full-screen-video {
  position: absolute;
  width: 100vw;
  height: 100vh;
}

#lower-video-bar {
  height: 20vh;
}

#local-stream-container { 
  position: relative; 
  display: inline-block;
}

.remote-stream-container { 
  display: inline-block;
}

#remote-streams {
  height: 100%;
}

#local-video {
  position: absolute;
  z-index: 1;
  height: 20vh;
  max-width: 100%;
}

.remote-video {
  position: absolute;
  z-index: 1;
  height: 100% !important;
  width: 80%;
  max-width: 500px;
}

#mute-overlay {
  position: absolute;
  z-index: 2;
  bottom: 0;
  left: 0;
  color: #d9d9d9;
  font-size: 2em;
  padding: 0 0 3px 3px;
  display: none;
} 

.mute-overlay {
  position: absolute;
  z-index: 2;
  top: 2px;
  color: #d9d9d9;
  font-size: 1.5em;
  padding: 2px 0 0 2px;
  display: none;
}

#no-local-video, .no-video-overlay {
  position: absolute;
  z-index: 3;
  width: 100%;
  top: 40%;
  color: #cccccc;
  font-size: 2.5em;
  margin: 0 auto;
  display: none;
}

.no-video-overlay {
  width: 80%;
}

#screen-share-btn-container {
  z-index: 99;
}

Добавление элементов пользовательского интерфейса

Теперь давайте добавим несколько кнопок, чтобы управлять переключением микрофона, видео или покинуть канал и закончить последние оставшиеся биты нашего пользовательского интерфейса. Вот где Font Awesome и Bootstrap действительно делает вещи простыми. Мы будем использовать Элемент и некоторые иконки Fontawesome.

Приведенные ниже разделы соответствуют приведенному выше коду, заменив комментарии и

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

// UI buttons
function enableUiControls(localStream) {

  $("#mic-btn").prop("disabled", false);
  $("#video-btn").prop("disabled", false);
  $("#screen-share-btn").prop("disabled", false);
  $("#exit-btn").prop("disabled", false);

  $("#mic-btn").click(function(){
    toggleMic(localStream);
  });

  $("#video-btn").click(function(){
    toggleVideo(localStream);
  });

  $("#screen-share-btn").click(function(){
    toggleScreenShareBtn(); // set screen share button icon
    $("#screen-share-btn").prop("disabled",true); // disable the button on click
    if(screenShareActive){
      stopScreenShare();
    } else {
      initScreenShare(); 
    }
  });

  $("#exit-btn").click(function(){
    console.log("so sad to see you leave the channel");
    leaveChannel(); 
  });

  // keyboard listeners 
  $(document).keypress(function(e) {
    switch (e.key) {
      case "m":
        console.log("squick toggle the mic");
        toggleMic(localStream);
        break;
      case "v":
        console.log("quick toggle the video");
        toggleVideo(localStream);
        break; 
      case "s":
        console.log("initializing screen share");
        toggleScreenShareBtn(); // set screen share button icon
        $("#screen-share-btn").prop("disabled",true); // disable the button on click
        if(screenShareActive){
          stopScreenShare();
        } else {
          initScreenShare(); 
        }
        break;  
      case "q":
        console.log("so sad to see you quit the channel");
        leaveChannel(); 
        break;   
      default:  // do nothing
    }

    // (for testing) 
    if(e.key === "r") { 
      window.history.back(); // quick reset
    }
  });
}

function toggleBtn(btn){
  btn.toggleClass('btn-dark').toggleClass('btn-danger');
}

function toggleScreenShareBtn() {
  $('#screen-share-btn').toggleClass('btn-danger');
  $('#screen-share-icon').toggleClass('fa-share-square').toggleClass('fa-times-circle');
}

function toggleVisibility(elementID, visible) {
  if (visible) {
    $(elementID).attr("style", "display:block");
  } else {
    $(elementID).attr("style", "display:none");
  }
}

function toggleMic(localStream) {
  toggleBtn($("#mic-btn")); // toggle button colors
  $("#mic-icon").toggleClass('fa-microphone').toggleClass('fa-microphone-slash'); // toggle the mic icon
  if ($("#mic-icon").hasClass('fa-microphone')) {
    localStream.enableAudio(); // enable the local mic
    toggleVisibility("#mute-overlay", false); // hide the muted mic icon
  } else {
    localStream.disableAudio(); // mute the local mic
    toggleVisibility("#mute-overlay", true); // show the muted mic icon
  }
}

function toggleVideo(localStream) {
  toggleBtn($("#video-btn")); // toggle button colors
  $("#video-icon").toggleClass('fa-video').toggleClass('fa-video-slash'); // toggle the video icon
  if ($("#video-icon").hasClass('fa-video')) {
    localStream.enableVideo(); // enable the local video
    toggleVisibility("#no-local-video", false); // hide the user icon when video is enabled
  } else {
    localStream.disableVideo(); // disable the local video
    toggleVisibility("#no-local-video", true); // show the user icon when video is disabled
  }
}

Как видите, есть дополнительная логика для управления клавиатурой. Во время тестирования я обнаружил, что сочетания клавиш заставили вещи двигаться быстрее. В фрагменте выше у нас есть поддержка m , V , s , Q Переключить микрофон, видео и распределение экрана и оставить звонок (соответственно) Анкет

Я сохранил приведенный выше код в файл Ui.js Чтобы держать его отдельно от основной логики видеочата, которую мы будем писать. Также давайте обязательно включим Ui.js Файл в нашем HTML -файле (Используя фрагмент ниже) Анкет


Основная структура (JS)

Теперь, когда мы, структура HTML/DOM, мы можем добавить в JS. Я решил использовать Agora.io, чтобы упростить тяжелую задачу интерфейса WEBRTC. Я написал Короткий пост о том, как получить настройку с Agora.io Для любого нового на платформе Agora.io. В приведенном ниже коде мы начнем с объявления и инициализации объекта клиента. Как только у нас есть клиент -объект, мы сможем присоединиться/покинуть канал, но мы также добавим слушателей для различных событий двигателя.

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

// app / channel settings
var agoraAppId = ""; // Set your Agora App ID
var channelName = 'agora-web-docs-demo';

// video profile settings
var cameraVideoProfile = '480_4'; // 640 × 480 @ 30fps  & 750kbs
var screenVideoProfile = '480_2'; // 640 × 480 @ 30fps

// create client instances for camera (client) and screen share (screenClient)
var client = AgoraRTC.createClient({mode: 'rtc', codec: "h264"}); // h264 better detail at a higher motion
var screenClient = AgoraRTC.createClient({mode: 'rtc', codec: 'vp8'}); // use the vp8 for better detail in low motion

// stream references (keep track of active streams) 
var remoteStreams = {}; // remote streams obj struct [id : stream] 

var localStreams = {
  camera: {
    id: "",
    stream: {}
  },
  screen: {
    id: "",
    stream: {}
  }
};

var mainStreamId; // reference to main stream
var screenShareActive = false; // flag for screen share 

// init Agora SDK
client.init(agoraAppId, function () {
  console.log("AgoraRTC client initialized");
  joinChannel(); // join channel upon successfull init
}, function (err) {
  console.log("[ERROR] : AgoraRTC client init failed", err);
});

client.on('stream-published', function (evt) {
  console.log("Publish local stream successfully");
});

// connect remote streams
client.on('stream-added', function (evt) {
  console.log("new stream added: " + streamId);
  // Check if the stream is local
  if (streamId != localStreams.screen.id) {
    console.log('subscribe to remote stream:' + streamId);
    // Subscribe to the stream.
    client.subscribe(stream, function (err) {
      console.log("[ERROR] : subscribe stream failed", err);
    });
  }
});

client.on('stream-subscribed', function (evt) {
  console.log("Subscribe remote stream successfully: " + evt.stream.getId());
});

// remove the remote-container when a user leaves the channel
client.on("peer-leave", function(evt) {
  console.log("Remote stream: " + evt.stream.getId() + "has left");
});

// show mute icon whenever a remote has muted their mic
client.on("mute-audio", function (evt) {
  console.log("Remote stream: " +  evt.uid + "has muted audio");
});

client.on("unmute-audio", function (evt) {
  console.log("Remote stream: " +  evt.uid + "has muted audio");
});

// show user icon whenever a remote has disabled their video
client.on("mute-video", function (evt) {
  console.log("Remote stream: " +  evt.uid + "has muted video");
});

client.on("unmute-video", function (evt) {
  console.log("Remote stream: " +  evt.uid + "has un-muted video");
});

// join a channel
function joinChannel() {
  var token = generateToken();
  var userID = null; // set to null to auto generate uid on successfull connection
  client.join(token, channelName, userID, function(uid) {
      console.log("User " + uid + " join channel successfully");
      createCameraStream(uid);
      localStreams.camera.id = uid; // keep track of the stream uid 
  }, function(err) {
      console.log("[ERROR] : join channel failed", err);
  });
}

// video streams for channel
function createCameraStream(uid) {
  var localStream = AgoraRTC.createStream({
    streamID: uid,
    audio: true,
    video: true,
    screen: false
  });
  localStream.setVideoProfile(cameraVideoProfile);
  localStream.init(function() {
    console.log("getUserMedia successfully");
    // TODO: add check for other streams. play local stream full size if alone in channel
    localStream.play('local-video'); // play the given stream within the local-video div
    // publish local stream
    client.publish(localStream, function (err) {
      console.log("[ERROR] : publish local stream error: " + err);
    });

    enableUiControls(localStream); // move after testing
    localStreams.camera.stream = localStream; // keep track of the camera stream for later
  }, function (err) {
    console.log("[ERROR] : getUserMedia failed", err);
  });
}

function leaveChannel() {
  client.leave(function() {
    console.log("client leaves channel");
  }, function(err) {
    console.log("client leave failed ", err); //error handling
  });
}

// use tokens for added security
function generateToken() {
  return null; // TODO: add a token generation
}

Одна вещь, которую следует отметить, все слушатели событий Agora.io SDK должны быть на верхнем уровне, пожалуйста, не совершайте ошибку, гнездя их на обратный вызов, присоединившись к каналу. Я сделал эту ошибку, и это заставило меня иметь доступ только к потокам, которые присоединились к каналу после меня.

Как вы можете видеть в приведенном выше коде, у нас есть 'Add-Advalt' Callback, именно здесь мы добавим логику, чтобы обработать настройку первого удаленного потока в полное экрановое видео и каждый последующий поток в новый контейнер для Div в Div дивизий с удаленными потоками, который даст нам функциональность группы выше всего 1-1 видео. Ниже приведена функция, которую мы бы называли каждый раз, когда добавляется новый удаленный поток, и мы хотим, чтобы он динамически добавил себя в DOM.

// REMOTE STREAMS UI
function addRemoteStreamMiniView(remoteStream){
  var streamId = remoteStream.getId();
  // append the remote stream template to #remote-streams
  $('#remote-streams').append(
    $('
', {'id': streamId + '_container', 'class': 'remote-stream-container col'}).append( $('
', {'id': streamId + '_mute', 'class': 'mute-overlay'}).append( $('', {'class': 'fas fa-microphone-slash'}) ), $('
', {'id': streamId + '_no-video', 'class': 'no-video-overlay text-center'}).append( $('', {'class': 'fas fa-user'}) ), $('
', {'id': 'agora_remote_' + streamId, 'class': 'remote-video'}) ) ); remoteStream.play('agora_remote_' + streamId); }

Последнее примечание для этого раздела у нас есть кнопки, которые переключают микрофон и видеопотоки, но мы должны предоставить отзыв удаленным пользователям, подписанным на приглушенные потоки. Не волнуйтесь Agora’s SDK предоставляет некоторые обратные вызовы специально для этих ситуаций. Выше вы можете видеть, что эти случаи рассматриваются такими событиями, как Mute-Audio или Морет-видео (а также их обращение для обеспечения соответствующих потоков) Анкет

Улучшение пользовательского интерфейса путем управления действиями удаленного потока

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

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

// show mute icon whenever a remote has muted their mic
client.on("mute-audio", function (evt) {
  toggleVisibility('#' + evt.uid + '_mute', true);
});

client.on("unmute-audio", function (evt) {
  toggleVisibility('#' + evt.uid + '_mute', false);
});

// show user icon whenever a remote has disabled their video
client.on("mute-video", function (evt) {
  var remoteId = evt.uid;
  // if the main user stops their video select a random user from the list
  if (remoteId != mainStreamId) {
    // if not the main vidiel then show the user icon
    toggleVisibility('#' + remoteId + '_no-video', true);
  }
});

client.on("unmute-video", function (evt) {
  toggleVisibility('#' + evt.uid + '_no-video', false);
});

Больше излишеств

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

var containerId = '#' + streamId + '_container';
$(containerId).dblclick(function() {
  // play selected container as full screen - swap out current full screen stream
  remoteStreams[mainStreamId].stop(); // stop the main video stream playback
  addRemoteStreamMiniView(remoteStreams[mainStreamId]); // send the main video stream to a container
  $(containerId).empty().remove(); // remove the stream's miniView container
  remoteStreams[streamId].stop() // stop the container's video stream playback
  remoteStreams[streamId].play('full-screen-video'); // play the remote stream as the full screen video
  mainStreamId = streamId; // set the container stream id as the new main stream id
});

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

// remove the remote-container when a user leaves the channel
client.on("peer-leave", function(evt) {
  var streamId = evt.stream.getId(); // the the stream id
  if(remoteStreams[streamId] != undefined) {
    remoteStreams[streamId].stop(); // stop playing the feed
    delete remoteStreams[streamId]; // remove stream from list
    if (streamId == mainStreamId) {
      var streamIds = Object.keys(remoteStreams);
      var randomId = streamIds[Math.floor(Math.random()*streamIds.length)]; // select from the remaining streams
      remoteStreams[randomId].stop(); // stop the stream's existing playback
      var remoteContainerID = '#' + randomId + '_container';
      $(remoteContainerID).empty().remove(); // remove the stream's miniView container
      remoteStreams[randomId].play('full-screen-video'); // play the random stream as the main stream
      mainStreamId = randomId; // set the new main remote stream
    } else {
      var remoteContainerID = '#' + streamId + '_container';
      $(remoteContainerID).empty().remove(); // 
    }
  }
});

Я добавил некоторую рандомизацию, поэтому, когда полноэкранный дистанционный поток покидает канал, один из других удаленных потоков выбирается случайным образом и настроен на воспроизведение в полноэкранном Div.

Сделать все это вместе

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

// simple JS interface for Agora.io web SDK

// app / channel settings
var agoraAppId = " "; // Set your Agora App ID
var channelName = 'agora-web-docs-demo';

// video profile settings
var cameraVideoProfile = '480_4'; // 640 × 480 @ 30fps  & 750kbs
var screenVideoProfile = '480_2'; // 640 × 480 @ 30fps

// create client instances for camera (client) and screen share (screenClient)
var client = AgoraRTC.createClient({mode: 'rtc', codec: "h264"}); // h264 better detail at a higher motion
var screenClient = AgoraRTC.createClient({mode: 'rtc', codec: 'vp8'}); // use the vp8 for better detail in low motion

// stream references (keep track of active streams) 
var remoteStreams = {}; // remote streams obj struct [id : stream] 

var localStreams = {
  camera: {
    id: "",
    stream: {}
  },
  screen: {
    id: "",
    stream: {}
  }
};

var mainStreamId; // reference to main stream
var screenShareActive = false; // flag for screen share 

// init Agora SDK
client.init(agoraAppId, function () {
  console.log("AgoraRTC client initialized");
  joinChannel(); // join channel upon successfull init
}, function (err) {
  console.log("[ERROR] : AgoraRTC client init failed", err);
});

client.on('stream-published', function (evt) {
  console.log("Publish local stream successfully");
});

// connect remote streams
client.on('stream-added', function (evt) {
  var stream = evt.stream;
  var streamId = stream.getId();
  console.log("new stream added: " + streamId);
  // Check if the stream is local
  if (streamId != localStreams.screen.id) {
    console.log('subscribe to remote stream:' + streamId);
    // Subscribe to the stream.
    client.subscribe(stream, function (err) {
      console.log("[ERROR] : subscribe stream failed", err);
    });
  }
});

client.on('stream-subscribed', function (evt) {
  var remoteStream = evt.stream;
  var remoteId = remoteStream.getId();
  remoteStreams[remoteId] = remoteStream;
  console.log("Subscribe remote stream successfully: " + remoteId);
  if( $('#full-screen-video').is(':empty') ) { 
    mainStreamId = remoteId;
    remoteStream.play('full-screen-video');
  } else {
    addRemoteStreamMiniView(remoteStream);
  }
});

// remove the remote-container when a user leaves the channel
client.on("peer-leave", function(evt) {
  var streamId = evt.stream.getId(); // the the stream id
  if(remoteStreams[streamId] != undefined) {
    remoteStreams[streamId].stop(); // stop playing the feed
    delete remoteStreams[streamId]; // remove stream from list
    if (streamId == mainStreamId) {
      var streamIds = Object.keys(remoteStreams);
      var randomId = streamIds[Math.floor(Math.random()*streamIds.length)]; // select from the remaining streams
      remoteStreams[randomId].stop(); // stop the stream's existing playback
      var remoteContainerID = '#' + randomId + '_container';
      $(remoteContainerID).empty().remove(); // remove the stream's miniView container
      remoteStreams[randomId].play('full-screen-video'); // play the random stream as the main stream
      mainStreamId = randomId; // set the new main remote stream
    } else {
      var remoteContainerID = '#' + streamId + '_container';
      $(remoteContainerID).empty().remove(); // 
    }
  }
});

// show mute icon whenever a remote has muted their mic
client.on("mute-audio", function (evt) {
  toggleVisibility('#' + evt.uid + '_mute', true);
});

client.on("unmute-audio", function (evt) {
  toggleVisibility('#' + evt.uid + '_mute', false);
});

// show user icon whenever a remote has disabled their video
client.on("mute-video", function (evt) {
  var remoteId = evt.uid;
  // if the main user stops their video select a random user from the list
  if (remoteId != mainStreamId) {
    // if not the main vidiel then show the user icon
    toggleVisibility('#' + remoteId + '_no-video', true);
  }
});

client.on("unmute-video", function (evt) {
  toggleVisibility('#' + evt.uid + '_no-video', false);
});

// join a channel
function joinChannel() {
  var token = generateToken();
  var userID = null; // set to null to auto generate uid on successfull connection
  client.join(token, channelName, userID, function(uid) {
      console.log("User " + uid + " join channel successfully");
      createCameraStream(uid);
      localStreams.camera.id = uid; // keep track of the stream uid 
  }, function(err) {
      console.log("[ERROR] : join channel failed", err);
  });
}

// video streams for channel
function createCameraStream(uid) {
  var localStream = AgoraRTC.createStream({
    streamID: uid,
    audio: true,
    video: true,
    screen: false
  });
  localStream.setVideoProfile(cameraVideoProfile);
  localStream.init(function() {
    console.log("getUserMedia successfully");
    // TODO: add check for other streams. play local stream full size if alone in channel
    localStream.play('local-video'); // play the given stream within the local-video div

    // publish local stream
    client.publish(localStream, function (err) {
      console.log("[ERROR] : publish local stream error: " + err);
    });

    enableUiControls(localStream); // move after testing
    localStreams.camera.stream = localStream; // keep track of the camera stream for later
  }, function (err) {
    console.log("[ERROR] : getUserMedia failed", err);
  });
}

// SCREEN SHARING
function initScreenShare() {
  screenClient.init(agoraAppId, function () {
    console.log("AgoraRTC screenClient initialized");
    joinChannelAsScreenShare();
    screenShareActive = true;
    // TODO: add logic to swap button
  }, function (err) {
    console.log("[ERROR] : AgoraRTC screenClient init failed", err);
  });  
}

function joinChannelAsScreenShare() {
  var token = generateToken();
  var userID = null; // set to null to auto generate uid on successfull connection
  screenClient.join(token, channelName, userID, function(uid) { 
    localStreams.screen.id = uid;  // keep track of the uid of the screen stream.

    // Create the stream for screen sharing.
    var screenStream = AgoraRTC.createStream({
      streamID: uid,
      audio: false, // Set the audio attribute as false to avoid any echo during the call.
      video: false,
      screen: true, // screen stream
      extensionId: 'minllpmhdgpndnkomcoccfekfegnlikg', // Google Chrome:
      mediaSource:  'screen', // Firefox: 'screen', 'application', 'window' (select one)
    });
    screenStream.setScreenProfile(screenVideoProfile); // set the profile of the screen
    screenStream.init(function(){
      console.log("getScreen successful");
      localStreams.screen.stream = screenStream; // keep track of the screen stream
      $("#screen-share-btn").prop("disabled",false); // enable button
      screenClient.publish(screenStream, function (err) {
        console.log("[ERROR] : publish screen stream error: " + err);
      });
    }, function (err) {
      console.log("[ERROR] : getScreen failed", err);
      localStreams.screen.id = ""; // reset screen stream id
      localStreams.screen.stream = {}; // reset the screen stream
      screenShareActive = false; // resest screenShare
      toggleScreenShareBtn(); // toggle the button icon back (will appear disabled)
    });
  }, function(err) {
    console.log("[ERROR] : join channel as screen-share failed", err);
  });

  screenClient.on('stream-published', function (evt) {
    console.log("Publish screen stream successfully");
    localStreams.camera.stream.disableVideo(); // disable the local video stream (will send a mute signal)
    localStreams.camera.stream.stop(); // stop playing the local stream
    // TODO: add logic to swap main video feed back from container
    remoteStreams[mainStreamId].stop(); // stop the main video stream playback
    addRemoteStreamMiniView(remoteStreams[mainStreamId]); // send the main video stream to a container
    // localStreams.screen.stream.play('full-screen-video'); // play the screen share as full-screen-video (vortext effect?)
    $("#video-btn").prop("disabled",true); // disable the video button (as cameara video stream is disabled)
  });

  screenClient.on('stopScreenSharing', function (evt) {
    console.log("screen sharing stopped", err);
  });
}

function stopScreenShare() {
  localStreams.screen.stream.disableVideo(); // disable the local video stream (will send a mute signal)
  localStreams.screen.stream.stop(); // stop playing the local stream
  localStreams.camera.stream.enableVideo(); // enable the camera feed
  localStreams.camera.stream.play('local-video'); // play the camera within the full-screen-video div
  $("#video-btn").prop("disabled",false);
  screenClient.leave(function() {
    screenShareActive = false; 
    console.log("screen client leaves channel");
    $("#screen-share-btn").prop("disabled",false); // enable button
    screenClient.unpublish(localStreams.screen.stream); // unpublish the screen client
    localStreams.screen.stream.close(); // close the screen client stream
    localStreams.screen.id = ""; // reset the screen id
    localStreams.screen.stream = {}; // reset the stream obj
  }, function(err) {
    console.log("client leave failed ", err); //error handling
  }); 
}

// REMOTE STREAMS UI
function addRemoteStreamMiniView(remoteStream){
  var streamId = remoteStream.getId();
  // append the remote stream template to #remote-streams
  $('#remote-streams').append(
    $('
', {'id': streamId + '_container', 'class': 'remote-stream-container col'}).append( $('
', {'id': streamId + '_mute', 'class': 'mute-overlay'}).append( $('', {'class': 'fas fa-microphone-slash'}) ), $('
', {'id': streamId + '_no-video', 'class': 'no-video-overlay text-center'}).append( $('', {'class': 'fas fa-user'}) ), $('
', {'id': 'agora_remote_' + streamId, 'class': 'remote-video'}) ) ); remoteStream.play('agora_remote_' + streamId); var containerId = '#' + streamId + '_container'; $(containerId).dblclick(function() { // play selected container as full screen - swap out current full screen stream remoteStreams[mainStreamId].stop(); // stop the main video stream playback addRemoteStreamMiniView(remoteStreams[mainStreamId]); // send the main video stream to a container $(containerId).empty().remove(); // remove the stream's miniView container remoteStreams[streamId].stop() // stop the container's video stream playback remoteStreams[streamId].play('full-screen-video'); // play the remote stream as the full screen video mainStreamId = streamId; // set the container stream id as the new main stream id }); } function leaveChannel() { if(screenShareActive) { stopScreenShare(); } client.leave(function() { console.log("client leaves channel"); localStreams.camera.stream.stop() // stop the camera stream playback client.unpublish(localStreams.camera.stream); // unpublish the camera stream localStreams.camera.stream.close(); // clean up and close the camera stream $("#remote-streams").empty() // clean up the remote feeds //disable the UI elements $("#mic-btn").prop("disabled", true); $("#video-btn").prop("disabled", true); $("#screen-share-btn").prop("disabled", true); $("#exit-btn").prop("disabled", true); // hide the mute/no-video overlays toggleVisibility("#mute-overlay", false); toggleVisibility("#no-local-video", false); }, function(err) { console.log("client leave failed ", err); //error handling }); } // use tokens for added security function generateToken() { return null; // TODO: add a token generation }

Обратите внимание: обмен видео не поддерживается Safari. К счастью, Chrome и Firefox не имеют таких ограничений.

Давайте бросим наш JS включить в нашу HTML -страницу, чтобы установить окончательные соединения. Приведенный ниже фрагмент вписывается в основной HTML (выше), заменив комментарий






Настройка тестирования (WebServer/Https)

Поскольку разрешения камеры требуют безопасного (HTTPS) соединения, прежде чем мы сможем проверить наше приложение для видеочата Мы должны раскрутить Простой веб -сервер С подключением HTTPS. Браузеры в белом списке Localhost URL Таким образом, вы можете использовать это для тестирования.

Чтобы все было просто, мне нравится использовать Живой сервер Но вы можете использовать любой метод.

Если вы хотите проверить это с друзьями, вы можете запустить его локально в сочетании с Ngrok , сервис, который создает туннель из вашей локальной машины и предоставляет URL HTTPS для использования. По моему опыту, это один из самых простых способов запустить общедоступный https Защищенный веб -сервер на вашей местной машине.

Как только сервер будет готов, мы можем запустить наш тест.

ПРИМЕЧАНИЕ. Используйте две (или более) вкладки браузера для моделирования локального хоста и одного/нескольких удаленных хостов.

Плавник.

И так же, как мы закончили! В любом случае вы не кодировали или хотите увидеть готовый продукт вместе, я разместил Код проекта на GitHub Анкет

Если вы хотите увидеть демонстрацию в действии, проверьте Демонстрация кода в действии на страницах GitHub.

Обратите внимание: что из -за высокого спроса я обновил сборку, поэтому вам нужно будет зарегистрироваться на бесплатную учетную запись Agora.io, чтобы получить Appid для проверки демонстрации. Если вам нужна помощь, у меня есть быстрый 3 -ступенчатый гид

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

Оригинал: “https://dev.to/hermes_f/build-a-group-video-chat-web-app-2jdb”