Introduction of the process

  • Through MediaDevices. GetUserMedia () for audio and video tracks.

  • Initiate a new WebRTC connection with the remote peer through createOffer().

  • Communicate upload errors with signaling and control to start or close the session.

  • Exchange media and client information

Initialize the action element

const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
const video1 = document.querySelector('video#video1');
const video2 = document.querySelector('video#video2');
const video3 = document.querySelector('video#video3');
callButton.disabled = true;
hangupButton.disabled = true;
startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;
let pc1Local;
let pc1Remote;
let pc2Local;
let pc2Remote;
const offerOptions = {
    offerToReceiveAudio: 1.offerToReceiveVideo: 1
};
Copy the code

Start collecting audio and video

MediaDevices is a Navigator read-only property that returns a mediaDevices object that provides connected access to media input devices such as cameras and microphones, as well as screen sharing.

The MediaDevices interface provides access to devices that connect to media input, such as cameras and microphones, as well as screen sharing. It allows you to access media data from any hardware resource.

MediaDevices. GetUserMedia () will prompt the user for permission to use the media input media input will produce a MediaStream, containing the request of the orbit of media types. This stream can contain A video track (from hardware or virtual video sources, such as cameras, video capture devices, screen sharing services, and so on), an audio track (also from hardware or virtual audio sources, such as microphones, A/D converters, and so on), or some other track type. It returns a Promise object, and on success resolve calls back a MediaStream object. If the user denies permission, or if the desired media source is not available, Promise calls back a PermissionDeniedError or NotFoundError.

function start() {
    startButton.disabled = true;
    navigator.mediaDevices
        .getUserMedia({
            audio: true.video: true
        })
        .then(stream= > {
            video1.srcObject = stream;
            window.localStream = stream;
            callButton.disabled = false;
        })
        .catch(e= > console.log(e));
}
Copy the code

The video is played remotely

The RTCPeerConnection() constructor returns a new instance of RTCPeerConnection that represents a connection between the local machine and the remote machine. The interface represents a WebRTC connection from the local machine to the remote machine. This interface provides implementations of methods to create, hold, monitor, and close connections.

The createOffer() method of the interface, RTCPeerConnection, initiates the creation of the SDP offer, with the purpose of starting a new WebRTC connection with the remote peer. SDP quotes include information about any objects that MediaStreamTrack has attached to WebRTC sessions, codecs, and browser supported options, as well as information about any candidates that the ICE agent has collected for the purpose of sending over the signaling channel to potential peers to request connections or update the configuration of existing connections, The return value is a Promise, which will be resolved using the RTCSessionDescription object containing the newly created offer after the offer is created.

function call() {
    callButton.disabled = true;
    hangupButton.disabled = false;
    const audioTracks = window.localStream.getAudioTracks();
    const videoTracks = window.localStream.getVideoTracks();
    if (audioTracks.length > 0) {
        console.log(`Using audio device: ${audioTracks[0].label}`);
    }
    if (videoTracks.length > 0) {
        console.log(`Using video device: ${videoTracks[0].label}`);
    }
    const servers = null;
    pc1Local = new RTCPeerConnection(servers);
    pc1Remote = new RTCPeerConnection(servers);
    pc1Remote.ontrack = gotRemoteStream1;
    pc1Local.onicecandidate = iceCallback1Local;
    pc1Remote.onicecandidate = iceCallback1Remote;

    pc2Local = new RTCPeerConnection(servers);
    pc2Remote = new RTCPeerConnection(servers);
    pc2Remote.ontrack = gotRemoteStream2;
    pc2Local.onicecandidate = iceCallback2Local;
    pc2Remote.onicecandidate = iceCallback2Remote;

    window.localStream.getTracks().forEach(track= > pc1Local.addTrack(track, window.localStream));
    pc1Local
        .createOffer(offerOptions)
        .then(gotDescription1Local, onCreateSessionDescriptionError);

    window.localStream.getTracks().forEach(track= > pc2Local.addTrack(track, window.localStream));
    pc2Local.createOffer(offerOptions)
        .then(gotDescription2Local, onCreateSessionDescriptionError);
}
Copy the code

Other methods

RTCPeerConnection. SetRemoteDescription associated with connect () method to change description, the description is mainly some description about the connection attributes, such as decoder for end use. Even accept the impact of this change and must be able to support both old and new descriptions. The method takes three parameters, the RTCSessionDescription object for setting, and then the successful callback method to change, and the failed callback method to change.

The RTCPeerConnection method setLocalDescription() changes the local description associated with the connection. This description specifies the properties of the local side of the connection, including the media format. This method takes one parameter — the session description — and it returns a Promise that will be completed asynchronously once the description is changed, indicating that renegotiation is under way (possibly to accommodate changing network conditions) if setLocalDescription() is called when the connection is already in place. Because descriptions are exchanged before the two peers agree on the configuration, setLocalDescription() submitted by call does not take effect immediately. Instead, the current connection configuration remains unchanged until the negotiation is complete. Only then will the agreed configuration take effect.

function onCreateSessionDescriptionError(error) {
    console.log(`Failed to create session description: ${error.toString()}`);
}

function gotDescription1Local(desc) {
    pc1Local.setLocalDescription(desc);
    pc1Remote.setRemoteDescription(desc);
    pc1Remote.createAnswer().then(gotDescription1Remote, onCreateSessionDescriptionError);
}

function gotDescription1Remote(desc) {
    pc1Remote.setLocalDescription(desc);
    console.log(`Answer from pc1Remote\n${desc.sdp}`);
    pc1Local.setRemoteDescription(desc);
}

function gotDescription2Local(desc) {
    pc2Local.setLocalDescription(desc);
    pc2Remote.setRemoteDescription(desc);
    pc2Remote.createAnswer().then(gotDescription2Remote, onCreateSessionDescriptionError);
}

function gotDescription2Remote(desc) {
    pc2Remote.setLocalDescription(desc);
    pc2Local.setRemoteDescription(desc);
}

function hangup() {
    console.log('Ending calls');
    pc1Local.close();
    pc1Remote.close();
    pc2Local.close();
    pc2Remote.close();
    pc1Local = pc1Remote = null;
    pc2Local = pc2Remote = null;
    hangupButton.disabled = true;
    callButton.disabled = false;
}

function gotRemoteStream1(e) {
    if(video2.srcObject ! == e.streams[0]) {
        video2.srcObject = e.streams[0];
        console.log('pc1: received remote stream'); }}function gotRemoteStream2(e) {
    if(video3.srcObject ! == e.streams[0]) {
        video3.srcObject = e.streams[0]; }}function iceCallback1Local(event) {
    handleCandidate(event.candidate, pc1Remote, 'pc1: '.'local');
}

function iceCallback1Remote(event) {
    handleCandidate(event.candidate, pc1Local, 'pc1: '.'remote');
}

function iceCallback2Local(event) {
    handleCandidate(event.candidate, pc2Remote, 'pc2: '.'local');
}

function iceCallback2Remote(event) {
    handleCandidate(event.candidate, pc2Local, 'pc2: '.'remote');
}

function handleCandidate(candidate, dest, prefix, type) {
    dest.addIceCandidate(candidate)
        .then(onAddIceCandidateSuccess, onAddIceCandidateError);
}

function onAddIceCandidateSuccess() {
    console.log('AddIceCandidate success.');
}

function onAddIceCandidateError(error) {
    console.log(`Failed to add ICE candidate: ${error.toString()}`);
}
Copy the code

HTML

<div id="container">
    <video id="video1" playsinline autoplay muted></video>
    <video id="video2" playsinline autoplay></video>
    <video id="video3" playsinline autoplay></video>
    
    <div>
        <button id="startButton">Start</button>
        <button id="callButton">Call</button>
        <button id="hangupButton">Hang Up</button>
    </div>
</div>
Copy the code

CSS

body {
    font-family: 'Roboto', sans-serif;
    font-weight: 300;
    margin: 0;
    padding: 1em;
    word-break: break-word;
}
button {
    background-color: #d84a38;
    border: none;
    border-radius: 2px;
    color: white;
    font-family: 'Roboto', sans-serif;
    font-size: 0.8 em;
    margin: 0 0 1em 0;
    padding: 0.5 em 0.7 em 0.6 em 0.7 em;
}
button:active {
    background-color: #cf402f;
}
button:hover {
    background-color: #cf402f;
}
button[disabled] {
    color: #ccc;
}
button[disabled]:hover {
    background-color: #d84a38;
}

div#container {
    margin: 0 auto 0 auto;
    max-width: 60em;
    padding: 1em 1.5 em 1.3 em 1.5 em;
}
video {
    background: # 222;
    margin: 0 0 20px 0;
    --width: 100%;
    width: var(--width);
    height: calc(var(--width) * 0.75);
}
button {
    margin: 0 20px 0 0;
    width: 83px;
}
button#hangupButton {
    margin: 0;
}
video {
    margin: 0 0 20px 0;
    --width: 40%;
    width: var(--width);
    height: calc(var(--width) * 0.75);
}
#video1 {
    margin: 0 20px 20px 0;
}
Copy the code