Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagejs
themeRDark
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
    let participant = remoteParticipants[e.info.nickName];
    if (!participant) {
        participant = {};
        participant.nickName = e.info.nickName;
        participant.tracks = [];
        participant.displays = [];
        remoteParticipants[participant.nickName] = participant;
    }
    participant.tracks.push.apply(participant.tracks, e.info.info);
    for (const pTrack of e.info.info) {
        if (pTrack.type === "VIDEO") {
            const displayObj = {
                display: createRemoteDisplay(participant.nickName, participant.nickName, null, pTrack),
                mediaStream: new MediaStream(),
                mids: {
                    audio: [],
                    video: undefined
                },
                audioStreams: {},
                audioElements: {}
            };
            const video = displayObj.display.getElementsByTagName("video")[0];
            video.srcObject = displayObj.mediaStream;
            displayObj.mids.video = pTrack.mid;
            participant.displays.push(displayObj);
        }
    }
}).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) {
    const participant = remoteParticipants[e.info.nickName];
    if (!participant) {
        return;
    }
    for (const rTrack of e.info.info) {
        for (let i = 0; i < participant.tracks.length; i++) {
            if (rTrack.mid === participant.tracks[i].mid) {
                participant.tracks.splice(i, 1);
                break;
            }
        }
        for (let i = 0; i < participant.displays.length; i++) {
            let found = false;
            const display = participant.displays[i];
            if (display.mids.audio.includes(rTrack.mid)) {
                //remove from mids array
                display.mids.audio.splice(display.mids.audio.indexOf(rTrack.mid), 1);
                //stop track and remove stream
                display.audioStreams[rTrack.mid].getAudioTracks()[0].stop();
                delete display.audioStreams[rTrack.mid];
                //remove audio element
                display.display.removeChild(display.audioElements[rTrack.mid]);
                delete display.audioElements[rTrack.mid];
                found = true;
            } else if (display.mids.video === rTrack.mid) {
                display.mids.video = undefined;
                display.mediaStream.getVideoTracks()[0].stop();
                found = true;
            }
            if (display.mids.audio.length === 0 && display.mids.video === undefined) {
                const video = display.display.getElementsByTagName("video")[0]
                video.pause();
                video.srcObject = null;
                display.display.remove();
                participant.displays.splice(i, 1);
            }
            if (found) {
                break;
            }
        }
    }
}).on(constants.SFU_ROOM_EVENT.LEFT, function(e) {
    let participant = remoteParticipants[e.name];
    if (!participant) {
        return;
    }
    delete remoteParticipants[e.name];
}).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){
    console.log("Received track quality state");
    const participant = remoteParticipants[e.info.nickName];
    if (!participant) {
        return;
    }

    for (const rTrack of e.info.tracks) {
        const mid = rTrack.mid;
        let vDisplay;
        for (let i = 0; i < participant.displays.length; i++) {
            const display = participant.displays[i];
            if (display.mids.video === mid) {
                vDisplay = display;
                break;
            }
        }
        //todo rework loops
        if (vDisplay) {
            for (const qualityInfo of rTrack.quality) {
                for (const child of vDisplay.display.childNodes) {
                    if (child.childNodes.length > 0) {
                        for (const cChild of child.childNodes) {
                            if (cChild.innerHTML === qualityInfo.quality) {
                                if (qualityInfo.available === true) {
                                    cChild.style.color = "gray";
                                } else {
                                    cChild.style.color = "red";
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
});

...


SFU_ROOM_EVENT.ADD_TRACKS

Find participant. If not found create a new one.

...

Code Block
languagejs
themeRDark
participant.tracks.push.apply(participant.tracks, e.info.info);


Create display for every video track that is beeing added.

Code Block
languagejs
themeRDark
for (const pTrack of e.info.info) {
    if (pTrack.type === "VIDEO") {
        const displayObj = {
            display: createRemoteDisplay(participant.nickName, participant.nickName, null, pTrack),
            mediaStream: new MediaStream(),
            mids: {
                audio: [],
                video: undefined
            },
            audioStreams: {},
            audioElements: {}
        };
        const video = displayObj.display.getElementsByTagName("video")[0];
        video.srcObject = displayObj.mediaStream;
        displayObj.mids.video = pTrack.mid;
        participant.displays.push(displayObj);
    }
}


SFU_ROOM_EVENT.REMOVE_TRACKS

Find remote participant. If not found return.

Code Block
languagejs
themeRDark
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
    return;
}


Walk through tracks

Code Block
languagejs
themeRDark
for (const rTrack of e.info.info) {


Find and remove participant's track that has the same mid as track that is beeing removed.

Code Block
languagejs
themeRDark
for (let i = 0; i < participant.tracks.length; i++) {
    if (rTrack.mid === participant.tracks[i].mid) {
        participant.tracks.splice(i, 1);
        break;
    }
}


Find display that corresponds to track and remove track from the display. If display has no active tracks remove display as well.

Code Block
languagejs
themeRDark
for (let i = 0; i < participant.displays.length; i++) {
    let found = false;
    const display = participant.displays[i];
    if (display.mids.audio.includes(rTrack.mid)) {
        //remove from mids array
        display.mids.audio.splice(display.mids.audio.indexOf(rTrack.mid), 1);
        //stop track and remove stream
        display.audioStreams[rTrack.mid].getAudioTracks()[0].stop();
        delete display.audioStreams[rTrack.mid];
        //remove audio element
        display.display.removeChild(display.audioElements[rTrack.mid]);
        delete display.audioElements[rTrack.mid];
        found = true;
    } else if (display.mids.video === rTrack.mid) {
        display.mids.video = undefined;
        display.mediaStream.getVideoTracks()[0].stop();
        found = true;
    }
    if (display.mids.audio.length === 0 && display.mids.video === undefined) {
        const video = display.display.getElementsByTagName("video")[0]
        video.pause();
        video.srcObject = null;
        display.display.remove();
        participant.displays.splice(i, 1);
    }
    if (found) {
        break;
    }
}


SFU_ROOM_EVENT.LEFT

Find and remove participant.

Code Block
languagejs
themeRDark
let participant = remoteParticipants[e.name];
if (!participant) {
    return;
}
delete remoteParticipants[e.name];


SFU_ROOM_EVENT.TRACK_QUALITY_STATE

Find participant. Return if not found.

Code Block
languagejs
themeRDark
console.log("Received track quality state");
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
    return;
}


Walk through tracks

Code Block
languagejs
themeRDark
for (const rTrack of e.info.tracks) {


Find corresponding display

Code Block
languagejs
themeRDark
const mid = rTrack.mid;
let vDisplay;
for (let i = 0; i < participant.displays.length; i++) {
    const display = participant.displays[i];
    if (display.mids.video === mid) {
        vDisplay = display;
        break;
    }
}


Update quality state

Code Block
languagejs
themeRDark
if (vDisplay) {
    for (const qualityInfo of rTrack.quality) {
        for (const child of vDisplay.display.childNodes) {
            if (child.childNodes.length > 0) {
                for (const cChild of child.childNodes) {
                    if (cChild.innerHTML === qualityInfo.quality) {
                        if (qualityInfo.available === true) {
                            cChild.style.color = "gray";
                        } else {
                            cChild.style.color = "red";
                        }
                        break;
                    }
                }
            }
        }
    }
}


Helper function which will create display based on stream and track info.

Code Block
languagejs
themeRDark
const createRemoteDisplay = function(id, name, stream, trackInfo) {
    const cell = document.createElement('div');
    cell.setAttribute("class", "grid-item");
    cell.id = id;
    mainDiv.appendChild(cell);
    const streamNameDisplay = document.createElement("div");
    streamNameDisplay.innerHTML = "Name: " + name;
    streamNameDisplay.setAttribute("style","width:auto; height:20px");
    cell.appendChild(streamNameDisplay);
    const qualityDisplay = document.createElement("div");
    qualityDisplay.setAttribute("style","width:auto; height:20px");
    cell.appendChild(qualityDisplay);
    const tidDisplay = document.createElement("div");
    tidDisplay.setAttribute("style","width:auto; height:20px");
    cell.appendChild(tidDisplay);
    const qualityDivs = [];
    const tidDivs = [];
    if (trackInfo && trackInfo.quality) {
        for (let i = 0; i < trackInfo.quality.length; i++) {
            const qualityDiv = document.createElement("button");
            qualityDivs.push(qualityDiv);
            qualityDiv.innerText = trackInfo.quality[i];
            qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
            qualityDiv.style.color = "red";
            qualityDiv.addEventListener('click', function(){
                console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
                if (qualityDiv.style.color === "red") {
                    return;
                }
                for (let c = 0; c < qualityDivs.length; c++) {
                    if (qualityDivs[c].style.color !== "red") {
                        qualityDivs[c].style.color = "gray";
                    }
                }
                qualityDiv.style.color = "blue";
                room.changeQuality(trackInfo.id, trackInfo.quality[i]);
            });
            qualityDisplay.appendChild(qualityDiv);
        }
        for (let i = 0; i < 3; i++) {
            const tidDiv = document.createElement("button");
            tidDivs.push(tidDiv);
            tidDiv.innerText = "TID"+i;
            tidDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
            tidDiv.style.color = "gray";
            tidDiv.addEventListener('click', function(){
                console.log("Clicked on TID " + i + " trackId " + trackInfo.id);
                for (let c = 0; c < tidDivs.length; c++) {
                    tidDivs[c].style.color = "gray";
                }
                tidDiv.style.color = "blue";
                room.changeQuality(trackInfo.id, null, i);
            });
            tidDisplay.appendChild(tidDiv);
        }
    }

    const rootDisplay = document.createElement('div');
    rootDisplay.setAttribute("style","width:auto; height:auto");
    cell.appendChild(rootDisplay);
    const streamDisplay = document.createElement('div');
    streamDisplay.setAttribute("style","width:auto; height:auto");
    rootDisplay.appendChild(streamDisplay);
    const video = document.createElement("video");
    streamDisplay.appendChild(video);
    video.srcObject = stream;
    video.onloadedmetadata = function (e) {
        video.play();
    };
    video.addEventListener("ended", function() {
        console.log("VIDEO ENDED");
    });
    video.addEventListener('resize', function (event) {
        streamNameDisplay.innerHTML = "Name: " + name + " " + video.videoWidth + "x" + video.videoHeight;
        resizeVideo(event.target);
    });
    return cell;
}


PeerConnection code


Subscribe to PeerConnection's "ontrack" event.

Code Block
languagejs
themeRDark
peerConnection.ontrack = ({transceiver}) => {
    let rParticipant;
    console.log("Attach remote track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);
    for (const [nickName, participant] of Object.entries(remoteParticipants)) {
        for (const pTrack of participant.tracks) {
            console.log("Participant " + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid);
            if (pTrack.mid === transceiver.mid) {
                rParticipant = participant;
                break;
            }
        }
        if (rParticipant) {
            break;
        }
    }
    if (rParticipant) {
        for (const display of rParticipant.displays) {
            if (transceiver.receiver.track.kind === "video") {
                if (display.mids.video === transceiver.mid) {
                    display.mediaStream.addTrack(transceiver.receiver.track);
                    display.display.getElementsByTagName("video")[0].play();
                    break;
                }
            } else if (transceiver.receiver.track.kind === "audio") {
                if (display.mids.audio.includes(transceiver.mid)) {
                    break;
                }
                display.mids.audio.push(transceiver.mid);
                let aStream = new MediaStream();
                aStream.addTrack(transceiver.receiver.track);
                display.audioStreams[transceiver.mid] = aStream;
                let audio = document.createElement("audio");
                audio.controls = "controls";
                display.audioElements[transceiver.mid] = audio;
                display.display.appendChild(audio);
                audio.srcObject = aStream;
                audio.play();
                break;
            }
        }
    } else {
        console.warn("Failed to find participant for track " + transceiver.receiver.track.id);
    }
}


Find participant based on track's mid

Code Block
languagejs
themeRDark
let rParticipant;
console.log("Attach remote track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);
for (const [nickName, participant] of Object.entries(remoteParticipants)) {
    for (const pTrack of participant.tracks) {
        console.log("Participant " + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid);
        if (pTrack.mid === transceiver.mid) {
            rParticipant = participant;
            break;
        }
    }
    if (rParticipant) {
        break;
    }
}



Find corresponding display among participant's displays and add track.

Code Block
languagejs
themeRDark
for (const display of rParticipant.displays) {
    if (transceiver.receiver.track.kind === "video") {
        if (display.mids.video === transceiver.mid) {
            display.mediaStream.addTrack(transceiver.receiver.track);
            display.display.getElementsByTagName("video")[0].play();
            break;
        }
    } else if (transceiver.receiver.track.kind === "audio") {
        if (display.mids.audio.includes(transceiver.mid)) {
            break;
        }
        display.mids.audio.push(transceiver.mid);
        let aStream = new MediaStream();
        aStream.addTrack(transceiver.receiver.track);
        display.audioStreams[transceiver.mid] = aStream;
        let audio = document.createElement("audio");
        audio.controls = "controls";
        display.audioElements[transceiver.mid] = audio;
        display.display.appendChild(audio);
        audio.srcObject = aStream;
        audio.play();
        break;
    }
}