Een keer in één van de oude en inmiddels verlaten artikelen heb ik geschreven over hoe gemakkelijk en ongedwongen je video kunt streamen vanaf canvas via websockets. In dat artikel sprak ik oppervlakkig over hoe je video van de camera en geluid van de microfoon kunt vastleggen met behulp van , hoe je de verkregen stroom kunt coderen en deze via websockets naar de server kunt sturen. In werkelijkheid gebeurt dit echter niet zo. Voor uitzendingen worden ofwel speciale softwareoplossingen gebruikt die moeten worden geïnstalleerd en geconfigureerd: zomaar een voorbeeld is , of men maakt gebruik van WebRTC, dat direct out-of-the-box werkt, wat wil zeggen dat er geen installatie van plug-ins zoals Flash Player nodig is, die in december uit de Chromium-browser zal worden verwijderd.
Vandaag gaan we het hebben over WebRTC.
Web Real-Time Communication (WebRTC) is niet één protocol, maar een hele verzameling standaarden, protocollen en JavaScript API's die samen peer-to-peer video- en audio-communicatie in real-time mogelijk maken, en ook kunnen worden gebruikt voor de overdracht van willekeurige binaire gegevens. Meestal zijn de peers browsers, maar het kan ook een mobiele applicatie zijn, bijvoorbeeld. Om p2p-communicatie tussen clients mogelijk te maken, is ondersteuning van de browser voor verschillende soorten video- en audio-codering vereist, ondersteuning van verschillende netwerktechnologieën en aansluiting van hardware op de browser (via OS-lagen): webcams, geluidskaarten. Al deze technologieën zijn verborgen achter de abstractie van de JavaScript API voor het gemak van de ontwikkelaar.
Uiteindelijk komt alles neer op drie API's:
— die we de vorige keer hebben besproken, vandaag zal ik er nog wat over schrijven. Het dient voor het verkrijgen van video/audio-stromen van de 'hardware'
— zorgt voor communicatie tussen twee clients (p2p)
— dient voor de overdracht van willekeurige gegevens tussen twee clients
Voorbereiding van audio en video streams voor transmissie
Het begint allemaal met het 'vastleggen' van de mediastromen van de webcam en de microfoon. Rauwe stromen zijn natuurlijk niet geschikt voor het organiseren van een video-conferentie; elke stroom moet worden verwerkt: de kwaliteit verbeteren, audio en video synchroniseren, synchronisatiemarkers in de videostroom plaatsen en ervoor zorgen dat de bitrate altijd past bij de steeds veranderende bandbreedte van het kanaal. De browser neemt dit alles voor zijn rekening; de ontwikkelaar hoeft zich geen zorgen te maken over het coderen van mediatstreams. Binnenin een moderne browser zijn al softwarelagen aanwezig voor het vastleggen, het verbeteren van de kwaliteit (echo en ruis wegwerken, het beeld verbeteren), en het coderen van video en audio. Het schema van de lagen is weergegeven in fig. 1:
Fig. 1. Audio- en videobewerkingslagen in de browser
De gehele verwerking gebeurt rechtstreeks in de browser zelf, er zijn geen extra plug-ins nodig. Echter, in 2020 is het nog steeds niet helemaal rooskleurig. Er zijn browsers die nog niet volledig compatibel zijn; , je kunt de link volgen en onderaan de compatibiliteitstabel bekijken. Vooral IE is weer teleurstellend.
Met de verkregen stromen kunnen zeer interessante dingen worden gedaan: je kunt ze klonen, de resolutie van video wijzigen, de audio kwaliteit manipuleren, je kunt de Media Stream aan een
— jongens die realtime CV op Javascript doen. Ze hebben een hele aan verschillende js-bibliotheken voor het werken met videostromen op canvas: gezichtsdetectie, objectdetectie, het toepassen van filters (maskers, zoals in Instagram) en meer. Een uitstekend voorbeeld van hoe je zonder extra plug-ins video in realtime direct in de browser kunt verwerken.
— API-documentatie voor het vastleggen van videostromen van canvas. Dit wordt al ondersteund in Chrome, Opera en Firefox.
RTCPeerConnection
En we zijn aangekomen bij de vraag: hoe kunnen we video eigenlijk naar een andere gebruiker verzenden? De focus komt nu op . Kort gezegd, op deze stap moet je een RTCPeerConnection-object creëren:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Een van de opties is iceServers — dit is een server die helpt bij het tot stand brengen van een verbinding tussen twee browsers die zich achter een NAT bevinden. Met andere woorden, hier wordt het probleem opgelost: hoe kun je het IP-adres van je gesprekspartner achter de NAT van hun provider achterhalen? Het ICE-protocol komt ter hulp; eigenlijk heeft ICE niets te maken met WebRTC, maar daarover later meer.
Eerder hebben we de Usermedia-stromen verkregen:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// Usermedia-stromen, meestal video en audio
const tracks = stream.getTracks();
for (const track of tracks) {
// elke track voegen we toe aan peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);Vervolgens wordt er op de peerConnection de onnegotiationneeded gebeurtenis geactiveerd, en in de handler daarvan moeten we een aanbod creëren (in termen van SDP — Session Description Protocol) en deze toewijzen aan de peerConnection via de methode setLocalDescription. Over SDP — wat het is en over de formaten aanbod en antwoord — daar zullen we later op ingaan.
Na het toewijzen van de LocalDescription aan peerConnection komt de browser "ice-kandidaten" verzamelen, wat betekent dat verschillende paden voor communicatie via NAT worden gevonden. De onicegatheringstatechange gebeurtenis wordt geactiveerd. In de handler voor onicegatheringstatechange stellen we de verbinding met de webrtc-signaleringsserver stream in voor het uitwisselen van de Session Description tussen peers:
peerConnection.oniceconnectionstatechange = (event) => {
console.log('Connection state: ', peerConnection.iceConnectionState);
if (peerConnection.iceConnectionState === 'connected') {
// Можем активировать кнопку Start broadcast
setBroadcasting(true);
setBroadcastingBtnActive(true);
}
};
// Событие срабатывает сразу, как только добавился медаиапоток в peerConnection
peerConnection.onnegotiationneeded = (event) => {
// Создаем и назначаем SDP offer
peerConnection.createOffer().
then((offer) => peerConnection.setLocalDescription(offer)).
catch(console.error);
};
// Событие срабатывает каждый раз, как появляется ICE кандидат
peerConnection.onicegatheringstatechange = (ev) => {
let connection = ev.target;
// Now we can activate broadcast button
if (connection.iceGatheringState === 'complete') {
let delay = 50;
let tries = 0;
let maxTries = 3;
let timerId = setTimeout(function allowStreaming() {
if (isOnline) {
setBroadcastingBtnActive(true);
return;
}
if (tries < maxTries) {
tries += 1;
delay *= 2;
timerId = setTimeout(allowStreaming, delay);
} else {
// TODO: show user notification
console.error("Can't connect to server");
alert("Can't connect to server");
}
}, delay);
}
};de webrtc-signaleringsserver — dit is een server die nodig is voor het uitwisselen van session description tussen twee peers, dit kan een eenvoudige websocket of xhr-server zijn in een willekeurige programmeertaal. De taak hiervan is eenvoudig: de session description van de ene peer te ontvangen en naar de andere door te geven.
Na het uitwisselen van de Session descriptions zijn beide partijen klaar om video streams te verzenden en te ontvangen; aan de kant die de video stream ontvangt, wordt de ontrack gebeurtenis op peerConnection geactiveerd, in de handler daarvan kunnen de ontvangen tracks worden toegewezen aan <video> en kun je naar je favoriete gesprekspartner kijken. Daarna volgt theorie en details.
Links en literatuur:
— documentatie
— implementatie van WebRTC-protocollen in Go
— boekje van de makers van pion
— boek High Performance Browser Networking. Het behandelt in detail de kwesties die verband houden met het waarborgen van hoge prestaties van webapplicaties. Aan het einde wordt WebRTC besproken. Het boek is natuurlijk oud (2013), maar verliest zijn relevantie niet.
In het volgende deel wil ik nog wat theorie geven en in de praktijk behandelen hoe je videostreaming op de server kunt verwerken met pion, transcoderen naar HLS via ffmpeg voor een latere uitzending aan kijkers in de browser.
Voor de ongeduldigen: (dit is gewoon een experiment).
Bron: habr.com
