Versions Compared

Key

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

...

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        dispose: function () {
            for (const player of videoPlayers.values()) {
                player.dispose();
            }
            videoPlayers.clear();
            for (const element of audioElements.values()) {
                element.remove();
            }
            audioElements.clear();
        },
        ...
    }
}

11.

...

 Participant view object creation for one-to-many meeting room

createOneToManyParticipantView() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {

    const participantDiv = createContainer(null);

    const audioDisplay = createContainer(participantDiv);

    const participantNicknameDisplay = createInfoDisplay(participantDiv, "Name: ")

    const audioElements = new Map();
    const player = createVideoPlayer(participantDiv);

    return {
        rootDiv: participantDiv,
        currentTrack: null,
        dispose: function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            ...
        },
        removeVideoSource: function (track) {
            ...
        },
        showVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track, audioTrack, show) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setNickname: function (userId, nickname) {
            ...
        },
        updateQuality: function (track, qualityName, available) {
            ...
        },
        addQuality: function (track, qualityName, available, onQualityPick) {
            ...
        },
        clearQualityState: function (track) {
            ...
        },
        pickQuality: function (track, qualityName) {
            ...
        }
    }
}

11.1.

...

 Adding video track to play

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addVideoTrack: function (track, requestVideoTrack) {
            player.addVideoTrack(track, async () => {
                return requestVideoTrack();
            });
        },
        ...
    }
}

11.2.

...

 Removing video track playing

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        removeVideoTrack: function (track) {
            player.removeVideoTrack(track);
        },
        ...
    }
}

11.3.

...

 Adding media source to video tag

addVideoSource() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            this.currentTrack = track;
            player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
        },
        ...
    }
}

11.4.

...

 Removing media source from video tag

removeVideoSource() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        removeVideoSource: function (track) {
            if (this.currentTrack && this.currentTrack.mid === track.mid) {
                player.removeVideoSource();
            }
        },
        ...
    }
}

11.5.

...

 Show video track

showVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        showVideoTrack: function (track) {
            player.showVideoTrack(track);
        },
        ...
    }
}

11.6.

...

 Adding audio track

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addAudioTrack: function (track, audioTrack, show) {
            const stream = new MediaStream();
            stream.addTrack(audioTrack);
            const audioElement = document.createElement("audio");
            if (!show) {
                hideItem(audioElement);
            }
            audioElement.controls = "controls";
            audioElement.muted = true;
            audioElement.autoplay = true;
            audioElement.onloadedmetadata = function (e) {
                audioElement.play().then(function () {
                    if (Browser().isSafariWebRTC() && Browser().isiOS()) {
                        console.warn("Audio track should be manually unmuted in iOS Safari");
                    } else {
                        audioElement.muted = false;
                    }
                });
            };
            audioElements.set(track.mid, audioElement);
            audioDisplay.appendChild(audioElement);
            audioElement.srcObject = stream;
        },
        ...
    }
}

11.7.

...

 Removing audio track

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        removeAudioTrack: function (track) {
            const audioElement = audioElements.get(track.mid);
            if (audioElement) {
                audioElement.remove();
                audioElements.delete(track.mid);
            }
        },
        ...
    }
}

11.8.

...

 Set participant nickname to display

setNickname() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        setNickname: function (userId, nickname) {
            const additionalUserId = userId ? "#" + getShortUserId(userId) : "";
            participantNicknameDisplay.innerText = "Name: " + nickname + additionalUserId;
        },
        ...
    }
}

11.9.

...

 Update track quality info

updateQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        updateQuality: function (track, qualityName, available) {
            player.updateQuality(qualityName, available);
        },
        ...
    }
}

11.10.

...

 Add track quality info

addQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addQuality: function (track, qualityName, available, onQualityPick) {
            player.addQuality(qualityName, available, onQualityPick);
        },
        ...
    }
}

11.11.

...

 Pick a quality to display

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        pickQuality: function (track, qualityName) {
            player.pickQuality(qualityName);
        }
        ...
    }
}

11.12.

...

Clear quality state displayed

clearQualityState() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        clearQualityState: function (track) {
            player.clearQualityState();
        },
        ...
    }
}

11.13.

...

Dispose the object

dispose() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        dispose: function () {
            player.dispose();
            for (const element of audioElements.values()) {
                element.remove();
            }
            audioElements.clear();
        },
        ...
    }
}

12.

...

Video player object creation

createVideoPlayer() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {

    const streamDisplay = createContainer(participantDiv);

    const resolutionLabel = createInfoDisplay(streamDisplay, "0x0");
    hideItem(resolutionLabel);

    const trackNameDisplay = createInfoDisplay(streamDisplay, "track not set");
    hideItem(trackNameDisplay);

    const videoMuteDisplay = createContainer(streamDisplay);

    const qualityDisplay = createContainer(streamDisplay);

    const trackDisplay = createContainer(streamDisplay);

    let videoElement;

    const trackButtons = new Map();
    const qualityButtons = new Map();

    const lock = function () {
        ...
    }

    const unlock = function () {
        ...
    }

    const setWebkitEventHandlers = function (video) {
        ...
    }
    const setEventHandlers = function (video) {
        ...
    }

    const repickQuality = function (qualityName) {
        ...
    }

    return {
        rootDiv: streamDisplay,
        muteButton: null,
        autoButton: null,
        dispose: function () {
            ...
        },
        clearQualityState: function () {
            ...
        },
        addVideoTrack: function (track, asyncCallback) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        setVideoSource: function (remoteVideoTrack, onResize, onMute) {
            ...
        },
        removeVideoSource: function () {
            ...
        },
        showVideoTrack: function (track) {
            ...
        },
        updateQuality: function (qualityName, available) {
            ...
        },
        addQuality: function (qualityName, available, onPickQuality) {
            ...
        },
        pickQuality: function (qualityName) {
            ...
        }
    }
}

12.1.

...

Lock and unlock player buttons to wait for asynchronous operations

lock(), unlock() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const lock = function () {
        for (const btn of trackButtons.values()) {
            btn.disabled = true;
        }
        for (const state of qualityButtons.values()) {
            state.btn.disabled = true;
        }
    }

    const unlock = function () {
        for (const btn of trackButtons.values()) {
            btn.disabled = false;
        }
        for (const state of qualityButtons.values()) {
            state.btn.disabled = false;
        }
    }
    ...
    return {
        ...
    }
}

12.2.

...

Safari event handles set up

setWebkitEventHandlers() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const setWebkitEventHandlers = function (video) {
        let needRestart = false;
        let isFullscreen = false;
        // Use webkitbeginfullscreen event to detect full screen mode in iOS Safari
        video.addEventListener("webkitbeginfullscreen", function () {
            isFullscreen = true;
        });
        video.addEventListener("pause", function () {
            if (needRestart) {
                console.log("Media paused after fullscreen, continue...");
                video.play();
                needRestart = false;
            } else {
                console.log("Media paused by click, continue...");
                video.play();
            }
        });
        video.addEventListener("webkitendfullscreen", function () {
            video.play();
            needRestart = true;
            isFullscreen = false;
        });
    }
    ...
    return {
        ...
    }
}

12.3.

...

Other browsers event handlers set up

setEventHandlers() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const setEventHandlers = function (video) {
        // Ignore play/pause button
        video.addEventListener("pause", function () {
            console.log("Media paused by click, continue...");
            video.play();
        });
    }
    ...
    return {
        ...
    }
}

12.4.

...

Change quality buttons colors when picking a quality

repickQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const repickQuality = function (qualityName) {
        for (const [quality, state] of qualityButtons.entries()) {
            if (quality === qualityName) {
                state.btn.style.color = QUALITY_COLORS.SELECTED;
            } else if (state.btn.style.color === QUALITY_COLORS.SELECTED) {
                if (state.available) {
                    state.btn.style.color = QUALITY_COLORS.AVAILABLE;
                } else {
                    state.btn.style.color = QUALITY_COLORS.UNAVAILABLE;
                }
            }
        }
    }
    ...
    return {
        ...
    }
}

12.5.

...

Remove quality buttons

clearQualityState() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        clearQualityState: function () {
            qualityButtons.forEach((state, qName) => {
                state.btn.remove();
            });
            qualityButtons.clear();
        },
        ...
    }
}

12.6.

...

Adding a video track to play

addVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        addVideoTrack: function (track, asyncCallback) {
            const trackButton = document.createElement("button");
            trackButtons.set(track.mid, trackButton);
            trackButton.innerText = "Track №" + track.mid + ": " + track.contentType;
            trackButton.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
            trackButton.style.color = QUALITY_COLORS.AVAILABLE;
            const self = this;
            trackButton.addEventListener('click', async function () {
                console.log("Clicked on track button track.mid " + track.mid);
                if (trackButton.style.color === QUALITY_COLORS.SELECTED) {
                    return
                }

                lock();
                asyncCallback().then(() => {
                    self.showVideoTrack(track);
                }).finally(() => {
                    unlock();
                });
            });
            trackDisplay.appendChild(trackButton);
        },
        ...
    }
}

12.7.

...

Removing video track played

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        removeVideoTrack: function (track) {
            const trackButton = trackButtons.get(track.mid);
            if (trackButton) {
                trackButton.remove();
                trackButtons.delete(track.mid);
            }
        },
        ...
    }
}

12.8.

...

Adding a video tag and setting video track as a source

setVideoSource() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        setVideoSource: function (remoteVideoTrack, onResize, onMute) {
            if (!this.muteButton) {
                const newVideoMuteBtn = document.createElement("button");
                this.muteButton = newVideoMuteBtn;
                newVideoMuteBtn.innerText = "mute";
                newVideoMuteBtn.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
                newVideoMuteBtn.addEventListener('click', async function () {
                    newVideoMuteBtn.disabled = true;
                    try {
                        if (newVideoMuteBtn.innerText === "mute") {
                            await onMute(true);
                            newVideoMuteBtn.innerText = "unmute";
                        } else if (newVideoMuteBtn.innerText === "unmute") {
                            await onMute(false);
                            newVideoMuteBtn.innerText = "mute";
                        }
                    } finally {
                        newVideoMuteBtn.disabled = false;
                    }
                });
                videoMuteDisplay.appendChild(newVideoMuteBtn);
            }

            if (videoElement) {
                videoElement.remove();
                videoElement = null;
            }

            if (!remoteVideoTrack) {
                return;
            }

            videoElement = document.createElement("video");
            hideItem(videoElement);
            videoElement.setAttribute("style", "display:none; border: solid; border-width: 1px");

            const stream = new MediaStream();

            streamDisplay.appendChild(videoElement);
            videoElement.srcObject = stream;
            videoElement.onloadedmetadata = function (e) {
                videoElement.play();
            };
            videoElement.addEventListener("resize", function (event) {
                showItem(resolutionLabel);
                if (videoElement) {
                    resolutionLabel.innerText = videoElement.videoWidth + "x" + videoElement.videoHeight;
                    resizeVideo(event.target);
                    onResize();
                }
            });
            stream.addTrack(remoteVideoTrack);
            if (Browser().isSafariWebRTC()) {
                videoElement.setAttribute("playsinline", "");
                videoElement.setAttribute("webkit-playsinline", "");
                setWebkitEventHandlers(videoElement);
            } else {
                setEventHandlers(videoElement);
            }
        },
        ...
    }
}

12.9.

...

Removing the video tag

removeVideoSource() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        removeVideoSource: function () {
            if (videoElement) {
                videoElement.remove();
                videoElement = null;
            }
            if (this.muteButton) {
                this.muteButton.remove();
                this.muteButton = null;
            }
            hideItem(resolutionLabel);
            trackNameDisplay.innerText = "track not set";
        },
        ...
    }
}

12.10.

...

Video tag and track info displaying

showVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        showVideoTrack: function (track) {
            if (videoElement) {
                showItem(videoElement);
            }
            for (const [mid, btn] of trackButtons.entries()) {
                if (mid === track.mid) {
                    btn.style.color = QUALITY_COLORS.SELECTED;
                } else if (btn.style.color === QUALITY_COLORS.SELECTED) {
                    btn.style.color = QUALITY_COLORS.AVAILABLE;
                }
            }
            trackNameDisplay.innerText = "Current video track: " + track.mid;
            showItem(trackNameDisplay);
        },
        ...
    }
}

12.11.

...

Update available auality info

updateQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        updateQuality: function (qualityName, available) {
            const value = qualityButtons.get(qualityName);
            if (value) {
                const qualityButton = value.btn;
                value.available = available;
                if (qualityButton.style.color === QUALITY_COLORS.SELECTED) {
                    return;
                }
                if (available) {
                    qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
                } else {
                    qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
                }
            }
        },
        ...
    }
}

12.12.

...

Add quality button

addQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        addQuality: function (qualityName, available, onPickQuality) {
            const qualityButton = document.createElement("button");
            qualityButtons.set(qualityName, {btn: qualityButton, available: available});
            qualityButton.innerText = qualityName;
            qualityButton.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
            if (available) {
                qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
            } else {
                qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
            }
            qualityDisplay.appendChild(qualityButton);
            qualityButton.addEventListener('click', async function () {
                console.log("Clicked on quality button " + qualityName);
                if (qualityButton.style.color === QUALITY_COLORS.SELECTED || qualityButton.style.color === QUALITY_COLORS.UNAVAILABLE || !videoElement) {
                    return;
                }
                lock();
                onPickQuality().finally(() => unlock());
            });
        },
        ...
    }
}

12.13.

...

Handle a quality button click

pickQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        pickQuality: function (qualityName) {
            repickQuality(qualityName);
        }
        ...
    }
}

12.14.

...

Dispose the object

dispose() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        ...
        dispose: function () {
            streamDisplay.remove();
        },
        ...
    }
}

13.

...

Get a track to display from the meeting room

remoteTrackProvider() code

Code Block
languagejs
themeRDark
const remoteTrackProvider = function (room) {
    return {
        getVideoTrack: async function () {
            return await room.getRemoteTrack("VIDEO", false);
        },
        getAudioTrack: async function () {
            return await room.getRemoteTrack("AUDIO", true);
        }
    }
}

14.

...

Helper functions

14.1.

...

Re-scale video to player video tag size

resizeVideo(), downScaleToFitSize() code

Code Block
languagejs
themeRDark
const resizeVideo = function (video, width, height) {
    // TODO: fix
    if (video) {
        return;
    }
    if (!video.parentNode) {
        return;
    }
    if (video instanceof HTMLCanvasElement) {
        video.videoWidth = video.width;
        video.videoHeight = video.height;
    }
    const display = video.parentNode;
    const parentSize = {
        w: display.parentNode.clientWidth,
        h: display.parentNode.clientHeight
    };
    let newSize;
    if (width && height) {
        newSize = downScaleToFitSize(width, height, parentSize.w, parentSize.h);
    } else {
        newSize = downScaleToFitSize(video.videoWidth, video.videoHeight, parentSize.w, parentSize.h);
    }
    display.style.width = newSize.w + "px";
    display.style.height = newSize.h + "px";

    //vertical align
    let margin = 0;
    if (parentSize.h - newSize.h > 1) {
        margin = Math.floor((parentSize.h - newSize.h) / 2);
    }
    display.style.margin = margin + "px auto";
    console.log("Resize from " + video.videoWidth + "x" + video.videoHeight + " to " + display.offsetWidth + "x" + display.offsetHeight);
}

const downScaleToFitSize = function (videoWidth, videoHeight, dstWidth, dstHeight) {
    var newWidth, newHeight;
    var videoRatio = videoWidth / videoHeight;
    var dstRatio = dstWidth / dstHeight;
    if (dstRatio > videoRatio) {
        newHeight = dstHeight;
        newWidth = Math.floor(videoRatio * dstHeight);
    } else {
        newWidth = dstWidth;
        newHeight = Math.floor(dstWidth / videoRatio);
    }
    return {
        w: newWidth,
        h: newHeight
    };
}

14.2.

...

Create a tag to display a textual info

createInfoDisplay() code

Code Block
languagejs
themeRDark
const createInfoDisplay = function (parent, text) {
    const div = document.createElement("div");
    if (text) {
        div.innerHTML = text;
    }
    div.setAttribute("style", "width:auto; height:30px;");
    div.setAttribute("class", "text-center");
    if (parent) {
        parent.appendChild(div);
    }
    return div;
}

14.3.

...

Create a container tag

createContainer() code

Code Block
languagejs
themeRDark
const createContainer = function (parent) {
    const div = document.createElement("div");
    div.setAttribute("style", "width:auto; height:auto;");
    div.setAttribute("class", "text-center");
    if (parent) {
        parent.appendChild(div);
    }
    return div;
}

14.4.

...

Show and hide the tag on the page

showItem(), hideItem() code

...