Versions Compared

Key

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

...

2.15. Сбросить таймер использования текущего качества

2. Обработка событий комнаты

2.1. ADD_TRACKS

initRemoteDisplay() code

Здесь:

...

abr.stopKeeping() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) (room, abrOptions) {
    return {
        console.log("Received ADD_TRACKS");createAbrManager: function () {
            let participantabr = remoteParticipants[e.info.nickName];
{
             if  (!participant) { ...
            participant = {};
  stopKeeping: function () {
       participant.nickName = e.info.nickName;
           if participant(abr.tracks = [];keepGoodTimer) {
            participant.displays = [];
          clearTimeout(abr.keepGoodTimer);
  remoteParticipants[participant.nickName] = participant;
        }
        participant.tracks.push.apply(participant.tracks, e.info.info);
    abr.keepGoodTimer = null;
               for (const pTrack of e.info.info) { }
            let  createDisplay = true;},
             for (let i = 0; i < participant.displays.length; i++) {
...
            }
          let display = participant.displays[i]return abr;
        }
    }
}

2.16. Переключиться на более высокое качество на заданное время

abr.tryUpper() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function if (pTrack.type === "VIDEO"(room, abrOptions) {
    return {
        createAbrManager: function () {
    if (display.hasVideo()) {
      let abr = {
                continue;...
                tryUpper: function ()  }{
                    display.videoMidlet quality = pTrack.midabr.getUpperQuality(abr.currentQualityName);
                    if (abr.tryUpperTimeout && display.setTrackInfo(pTrack);!abr.tryUpperTimer && quality) {
                    createDisplay = false;
  abr.tryUpperTimer = setTimeout(() => {
              break;
              abr.setQualityGood(quality.name, true);
 } else if (pTrack.type === "AUDIO") {
                    if (displayabr.hasAudiostopTrying()) {;
                        continue}, abr.tryUpperTimeout);
                    }
                    display.audioMid = pTrack.mid;},
                ...
    display.setTrackInfo(pTrack);
        }
            createDisplayreturn = falseabr;
        }
    }
}

2.17. Остановить таймер тестирования более высокого качества

abr.stopTrying() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room,    break;abrOptions) {
    return {
        createAbrManager: function () }{
            let abr = }{
            if (!createDisplay) {
  ...
              continue;
  stopTrying: function ()  {
      }
            let display =if createRemoteDisplay(participantabr.nickName, participant.nickName, mainDiv, displayOptions);tryUpperTimer) {
            participant.displays.push(display);
            if clearTimeout(pTrack.type === "VIDEO") {abr.tryUpperTimer);
                display.videoMid = pTrack.mid;
      abr.tryUpperTimer = null;
        display.setTrackInfo(pTrack);
            } else if (pTrack.type === "AUDIO") {
                display.audioMid = pTrack.mid;},
                display.setTrackInfo(pTrack);...
            }
            return }abr;
        ...}
    });
}

2.

...

18. Переключиться на указанное качество

abr.setQuality() code

Здесь:

  • удаляются элементы, в которых проигрывались потоки
  • информация о потоках удаляется из списка
Code Block
language
Code Block
languagejs
themeRDark
const abrManagerFactory = function room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e(room, abrOptions) {
    return {
    ...
    }).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS,createAbrManager: function (e) {
        console.log("Received REMOVE_TRACKS");    let abr = {
        const      participant = remoteParticipants[e..info.nickName];

                ifsetQuality: async function (!participantname) {
            return;
        }
  console.log("set quality name");
      for (const rTrack of e.info.info) {
         // Pause switching foruntil (leta inew =quality 0;is ireceived
 < participant.tracks.length; i++) {
                if (rTrack.mid === participant.tracks[i].mid) {abr.pause();
                    participant.tracks.splice(i, 1)abr.currentQualityName = name;
                    breakabr.track.setPreferredQuality(abr.currentQualityName);
                }
            }
            for (let i = 0; i < participant.displays.length; i++) {return abr;
        }
    }
}

3. Создание объекта для управления комнатой

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    ...

  let found =return false;{
        stop: stop
       const display = participant.displays[i];
   }
}

3.1. Обработка события PARTICIPANT_LIST

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
      if (display.audioMid === rTrack.midroom.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        for (const idName of e.participants) {
            displaymeetingModel.setAudio(nulladdParticipant(idName.userId, idName.name);
        }
        ...
    found = true});
    ...
}

3.2. Обработка события JOINED

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
      } else if (display.videoMid === rTrack.mid) {const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    display}).setVideo(null);
    on(constants.SFU_ROOM_EVENT.JOINED, async function (e) {
                found = truemeetingModel.addParticipant(e.userId, e.name);
            ...
    });
    ...
}

3.3. Обработка события LEFT

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function         if (found(room, meetingModel) {
    const constants = SFU.constants;
             if (!display.hasAudio() && !display.hasVideo())room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.LEFT, function (e) {
         displaymeetingModel.disposeremoveParticipant(e.userId);
        ...
    });
            participant.displays.splice(i, 1);
        ...
}

3.4. Обработка события ADD_TRACKS

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
     }
   room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.ADD_TRACKS, async function   break;(e) {
                }meetingModel.addTracks(e.info.userId, e.info.info);
            }
 ...
       });
        ...
    });

...

3.

...

4. Обработка события REMOVE_TRACKS

createDefaultMeetingController() codeЗдесь:

  • участник удаляется из списка
  • удаляются элементы, в которых проигрывались потоки
Code Block
languagejs
themeRDark
const createDefaultMeetingController = function room.on((room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.ADDPARTICIPANT_TRACKSLIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.LEFTREMOVE_TRACKS, async function (e) {
        consolemeetingModel.log("Received LEFT"removeTracks(e.info.userId, e.info.info);
        ...
 let participant = remoteParticipants[e.name] });
    ...
}

3.4. Обработка события TRACK_QUALITY_STATE

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function if (!participant(room, meetingModel) {
    const constants       return= SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) }{
        participant..displays.forEach(function(display){
    }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, async       display.dispose();function (e) {
        }) meetingModel.updateQualityInfo(e.info.userId, e.info.tracks);
        delete remoteParticipants[e.name];...
    });
    ...
    });

...

3.

...

5. Обработка события ENDED

createDefaultMeetingController() code

Здесь:

...

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.ADDPARTICIPANT_TRACKSLIST, async function (e) {
        ... 
    }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATEENDED, function (e) {
        consolemeetingModel.log("Received track quality state"end();
    });
    ...
}

3.6. Остановка комнаты

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
 const participant = remoteParticipants[e..info.nickName];
    const stop =  iffunction (!participant) {
        meetingModel.end();
    return};

    return {
    }

    stop: stop
   for (const rTrack of e.info.tracks }
}

4. Создание модели комнаты

createDefaultMeetingModel() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    ...
}

4.1. Добавление участника

addParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
  const mid = rTrack.mid;return {
        ...
    for (let i = 0;addParticipant: ifunction < participant.displays.length; i++(userId, participantName) {
            if (this.participants.get(userId)) {
    const display = participant.displays[i];
         return;
       if (display.videoMid === mid) {    }
            const [participantModel, participantView, participant] = participantFactory.createParticipant(userId, participantName,  display.updateQualityInfo(rTrack.qualitydisplayOptions, abrFactory);
                    breakthis.participants.set(userId, participant);
            meetingView.addParticipant(userId,    }participantName, participantView.rootDiv);
            },
        }...
    }
});

3. Создание элементов для отображения потоков участников

3.1. Создание контейнера

createRemoteDisplay() code

Здесь:

  • настраиваются параметры отображения
  • создается контейнер для потоков участника
  • создается контейнер для конкретного потока
  • создается контейнер для кнопок переключения качества

4.2. Удаление участника

removeParticipant() code

Code Block
languagejs
themeRDark
        const cellcreateDefaultMeetingModel = function document.createElement("div");
    (meetingView, participantFactory, displayOptions, abrFactory) {
    cell.setAttribute("class", "text-center");return {
        cell.id = id;...
        mainDiv.appendChild(cell);
removeParticipant: function (userId) {
            const participant let publisherNameDisplay= this.participants.get(userId);
         let currentQualityDisplay;
  if (participant) {
    let videoTypeDisplay;
        let abrQualityCheckPeriod = ABR_QUALITY_CHECK_PERIOD;
 this.participants.delete(userId);
            let  abrKeepOnGoodQuality = ABR_KEEP_ON_QUALITY meetingView.removeParticipant(userId);
        let abrTryForUpperQuality = ABR_TRY_UPPER_QUALITY;
     participant.dispose();
   if (displayOptions.abrQualityCheckPeriod !== undefined) {
     }
       abrQualityCheckPeriod = displayOptions.abrQualityCheckPeriod; },
        }...
    }
}

4.3. Переименование участника

renameParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, ifparticipantFactory, (displayOptions.abrKeepOnGoodQuality !== undefined, abrFactory) {
    return {
       abrKeepOnGoodQuality = displayOptions.abrKeepOnGoodQuality;...
        }
        if (displayOptions.abrTryForUpperQuality !== undefinedrenameParticipant: function (userId, newNickname) {
            const abrTryForUpperQualityparticipant = displayOptions.abrTryForUpperQualitythis.participants.get(userId);
        }
        if (!displayOptions.abrparticipant) {
            abrQualityCheckPeriod   = 0 participant.setNickname(newNickname);
            abrKeepOnGoodQuality}
 = 0;
      },
      abrTryForUpperQuality = 0;...
    }
}

4.4. Добавление треков участника для отображения

addTracks() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function }
    (meetingView, participantFactory, displayOptions, abrFactory) {
    if (displayOptions.publisher)return {
        ...
    publisherNameDisplay = createInfoDisplay(cell, "Published byaddTracks: "function +(userId, nametracks); {
        }
    const participant   if (displayOptions.quality) {= this.participants.get(userId);
            currentQualityDisplayif = createInfoDisplay(cell, "");(!participant) {
        }
        if (displayOptions.type) {return;
            videoTypeDisplay = createInfoDisplay(cell, "");
  }

      }
      for  (const qualitySwitchDisplaytrack = createInfoDisplay(cell, "");
of tracks) {
        let qualityDivs = [];
     if   let contentType (track.type === "VIDEO";
) {
        const rootDisplay = createContainer(cell);
        const streamDisplay = createContainer(rootDisplay participant.addVideoTrack(track);
        const audioDisplay = createContainer(rootDisplay);
     } else if const audioTypeDisplay = createInfoDisplay(audioDisplay);(track.type === "AUDIO") {
        const audioTrackDisplay = createContainer(audioDisplay);
        const audioStateButton = AudioStateButton( participant.addAudioTrack(track);

        hideItem(streamDisplay);
        hideItem(audioDisplay);}
        hideItem(publisherNameDisplay);
    }
    hideItem(currentQualityDisplay);
    },
    hideItem(videoTypeDisplay);
    ...
    hideItem(qualitySwitchDisplay);

3.2. Инициализация ABR

createRemoteDisplay() code

...

}
}

4.5. Удаление отображаемых треков участника

removeTracks() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
 const abr = ABR(abrQualityCheckPeriod, [
return {
        ...
        {parameterremoveTracks: "nackCount" function (userId, maxLeap: 10},tracks) {
            {parameter: "freezeCount", maxLeap: 10},const participant = this.participants.get(userId);
            if {parameter: "packetsLost", maxLeap: 10}
(!participant) {
            ], abrKeepOnGoodQuality, abrTryForUpperQuality);

3.3. Добавление видео элемента

setVideo() code

Code Block
languagejs
themeRDark
    return;
          setVideo: function(stream) {}
            for (const track of if (videotracks) {
                if    video.remove();
(track.type === "VIDEO") {
                   }

 participant.removeVideoTrack(track);
                } else if (streamtrack.type === null"AUDIO") {
                    video = null;
participant.removeAudioTrack(track);
                }
          this.videoMid = undefined;}
        },
        ...
    qualityDivs.forEach(function(div}
}

4.6. Обновление информации о качестве треков участника

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        updateQualityInfo: function  div.remove();(userId, tracksInfo) {
            const participant       }= this.participants.get(userId);
            if (!participant) {
      qualityDivs = [];
        return;
            return;}
            participant.updateQualityInfo(tracksInfo);
    }
    },
        ...
    showItem(streamDisplay);
          }
}

4.7. Завершение отображения участников при остановке комнаты

end() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
      video = document.createElement("video");...
        end: function () {
     video.controls = "controls";
     console.log("Meeting " + this.meetingName + " ended")
     video.muted = true;
     meetingView.end();
           video.autoplay this.participants.forEach((participant, id) => true;{
                if (Browser().isSafariWebRTC()) {
participant.dispose();
            });
            videothis.participants.setAttribute("playsinline", ""clear();
        },
        ...
    video.setAttribute("webkit-playsinline", "");
       }
}

4.8. Переименование комнаты

setMeetingName() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        this.setWebkitEventHandlers(video);...
        setMeetingName: function (id) {
     } else {
     this.meetingName = id;
             thismeetingView.setEventHandlerssetMeetingName(videoid);
        }
    }
}

5. Создание объекта для отображения потоков в комнате

createDefaultMeetingView() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) }{
    ...
}

5.1. Инициализация HTML5 элементов

createDefaultMeetingView() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    const rootDiv = streamDisplaydocument.appendChildcreateElement(video"div");
    rootDiv.setAttribute("class", "grid-item");
     entryPoint.appendChild(rootDiv);
    const  video.srcObjecttitle = streamdocument.createElement("label");
    title.setAttribute("style", "display:block; border: solid; border-width: 1px");
       this.setResizeHandler(videorootDiv.appendChild(title);
    return {
           abr.start();...
            },

3.4. Добавление аудио элемента

...

}
}

5.2. Инициализация списка элементов для отображения участников

participantViews() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
   setAudio: function(stream) return {
        participantViews:        if (audio) {
new Map(),
        ...
    }
}

5.3. Отображение имени комнаты

setMeetingName() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    audio.remove();...
    return {
           }...
                if (!streamsetMeetingName: function (id) {
            title.innerText = "Meeting: "     audio = null+ id;
        },
        ...
    this.audioMid}
}

5.4. Добавление элементов для отображения участника

addParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = undefined;
  function (entryPoint) {
    ...
    return {
         return;...
        addParticipant: function (userId, participantName,     }cell) {
            const participantDiv =  showItemcreateContainer(audioDisplayrootDiv);
                audio = document.createElement("audio"participantDiv.appendChild(cell);
                audio.controls = "controls"this.participantViews.set(userId, participantDiv);
        },
        audio.muted = true;...
                audio.autoplay = true;
                if (Browser().isSafariWebRTC())}
}

5.5. Удаление элементов участника

removeParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
           audio.setAttribute("playsinline", "");...
                    audio.setAttribute("webkit-playsinline", "");removeParticipant: function (userId) {
            const cell     =  this.participantViews.setWebkitEventHandlersget(audiouserId);
            if    } else (cell) {
                    this.setEventHandlersparticipantViews.delete(audiouserId);
                }
                audioTrackDisplay.appendChild(audiocell.remove();
                audioStateButton.makeButton(audioTypeDisplay, audio);}
        },
        audio.srcObject = stream;...
          }
}

5.5. Удаление корневого элемента отображения

end() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
      audio.onloadedmetadata = function (e)...
    return {
        ...
        end:    audio.play().then(functionfunction () {
            rootDiv.remove();
        }
    if (Browser().isSafariWebRTC() && Browser().isiOS())}
}

6. Создание фабрики объектов участников

createParticipantFactory() code

Здесь содаются объекты модели, управления и отображения участника комнаты

Code Block
languagejs
themeRDark
const createParticipantFactory = function (remoteTrackFactory, createParticipantView, createParticipantModel) {
    return {
        displayOptions: null,
        abrFactory: null,
     console.warn("Audio track should becreateParticipant: manuallyfunction unmuted in iOS Safari");(userId, nickname) {
            const view = createParticipantView();
         } else {
 const model = createParticipantModel(userId, nickname, view, remoteTrackFactory, this.abrFactory, this.displayOptions);
                   audio.mutedconst controller = falsecreateParticipantController(model);
            return [model, view, controller];
        }
     audioStateButton.setButtonState();
     }
}

7. Создание объекта управления участником

createParticipantController() code

Объект вызывает соответствующие методы модели участника

Code Block
languagejs
themeRDark
const createParticipantController = function (model) {
    return {
        addVideoTrack: function     }(track) {
            model.addVideoTrack(track);
        });,
        removeVideoTrack: function (track) {
     };
       model.removeVideoTrack(track);
     },

3.5. Настройка обработчиков событий для аудио и видео элементов

setResizeHandler(), setEventHandlers(), setWebkitEventHandlers() code

Code Block
languagejs
themeRDark
    },
        setEventHandlersaddAudioTrack: function (videotrack) {
                // Ignore play/pause buttonmodel.addAudioTrack(track);
        },
        video.addEventListener("pause",removeAudioTrack: function (track) {
            model.removeAudioTrack(track);
        console.log("Media paused by click, continue...");
},
        updateQualityInfo: function (qualityInfo) {
            videomodel.playupdateQualityInfo(qualityInfo);
        },
        setNickname: function }(nickname); {
            },
model.setNickname(nickname);
        },
        setWebkitEventHandlersdispose: function (video) {
            model.dispose();
     let needRestart = false;}
    }
}

8. Создание объекта модели участника в комнате с двумя участниками

createOneToOneParticipantModel() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    letconst isFullscreeninstance = false;{
        userId: userId,
        nickname: nickname,
  // Use webkitbeginfullscreen event to detect fullremoteVideoTracks: screen mode in iOS Safari
new Map(),
        remoteAudioTracks: new Map(),
         video.addEventListener("webkitbeginfullscreen", function () {audioTracks: new Map(),
        videoTracks: new Map(),
        abrManagers: new isFullscreenMap(),
 = true;
      disposed: false,
        dispose: });async function () {
             ...
        },
        video.addEventListener("pause",addVideoTrack: function (track) {
            ...
        if (needRestart) {},
        removeVideoTrack: function (track) {
             console.log("Media paused after fullscreen, continue...");
        },
        addAudioTrack: function (track) {
     video.play();
       ...
        },
        removeAudioTrack: needRestartfunction = false;(track) {
            ...
        },
   else {
    setUserId: function (userId) {
            ...
     console.log("Media paused by click, continue...");},
        setNickname: function (nickname) {
             video.play();...
        },
        updateQualityInfo: function (remoteTracks)  }{
            ...
    });
    },
        requestVideoTrack: async  function video.addEventListener("webkitendfullscreen"(track, function (remoteTrack) {
            ...
        video.play();
},
        pickQuality: async function (track, qualityName) {
          needRestart = true;...
        },
        muteVideo: async function  isFullscreen = false;
(track) {
            ...
        });,
        unmuteVideo: async   },

3.6. Добавление информации о дорожке в ABR

setVideoABRTrack() code

Code Block
languagejs
themeRDark
 function (track) {
           setVideoABRTrack: function(track) { ...
        }
    };
    abrinstance.setTracksetUserId(trackuserId);
    instance.setNickname(nickname);
        },

3.7. Настройка переключения качества

setTrackInfo() code

Здесь:

  • настраиваются кнопки переключения качества
  • информация о заявленных качествах публикации добавляется в ABR
  • скрываются или отображаются элементы для показа текущего качества и источника видео/аудио
return instance;
}

8.1. Добавление видео дорожки для отображения

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const setTrackInfo: function(trackInfo)instance = {
        ...
        addVideoTrack: iffunction (trackInfotrack) {
    
        this.videoTracks.set(track.mid, track);
            if (trackInfo!track.quality) {
                track.quality        showItem(qualitySwitchDisplay)= [];
            }
            if (abr.isEnabled()) {participantView.addVideoTrack(track);
            const self = this;
             const autoDiv = createQualityButton("Auto", qualityDivs, qualitySwitchDisplay);
remoteTrackFactory.getVideoTrack().then((remoteTrack) => {
                if (remoteTrack) {
                   autoDiv.style.color = QUALITY_COLORS.SELECTED;
   if (self.disposed || !self.videoTracks.get(track.mid)) {
                         autoDivremoteTrack.addEventListener('click', function() {dispose();
                        return;
        setQualityButtonsColor(qualityDivs);
            }

                    autoDivparticipantView.style.coloraddVideoSource(remoteTrack.track, track, () = QUALITY_COLORS.SELECTED;> {
                        const abrManager       abr.setAuto(= self.abrManagers.get(track.id);
                        if (!abrManager) {
    });
                        }return;
                        }
   for (let i = 0; i < trackInfo.quality.length; i++) {
            if (abrManager.isAuto()) {
              abr.addQuality(trackInfo.quality[i]);
                abrManager.resume();
            const qualityDiv = createQualityButton(trackInfo.quality[i], qualityDivs, qualitySwitchDisplay);
        }
                    qualityDiv.addEventListener('click'}, function(mute) => {
                        if (mute) {
      console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
           return self.muteVideo(track);
                    if (qualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE)    } else {
                            return self.unmuteVideo(track);
        return;
                }
                }
    });
                    self.requestVideoTrack(track, remoteTrack).then(() => {
     setQualityButtonsColor(qualityDivs);
                   participantView.showVideoTrack(track);
             qualityDiv.style.color = QUALITY_COLORS.SELECTED;
     }, (ex) => {
                        abrparticipantView.setManualremoveVideoSource(track);
                                abr.setQuality(trackInfo.quality[i]remoteTrack.dispose();
                            });
                }
        }
    }, (ex) => {
             } else {
         console.log("Failed to get remote track " + ex);
                hideItem(qualitySwitchDisplay});
        },
        ...
    };
    ...
    return instance;
}

8.2. Удаление отображаемой видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory,    if (trackInfo.typeabrFactory, displayOptions) {
    const instance = {
        ...
        removeVideoTrack: function contentType = trackInfo.contentType || "";
(track) {
            if (this.videoTracks.delete(track.mid)) {
             if (trackInfo.type == "VIDEO" && displayOptions.type && contentType !== "") {
 const remoteTrack = this.remoteVideoTracks.get(track.mid);
                if (remoteTrack) {
                    showItem(videoTypeDisplaythis.remoteVideoTracks.delete(track.mid);
                    remoteTrack.dispose();
        videoTypeDisplay.innerHTML = contentType;
      }
                participantView.removeVideoTrack(track);

  }
              const abrManager = this.abrManagers.get(track.id);
       if (trackInfo.type == "AUDIO") {
      if (abrManager) {
                    audioStateButtonthis.abrManagers.setContentTypedelete(contentTypetrack.id);
                        }abrManager.clearQualityState();
                    }abrManager.stop();
                }
            },

3.8. Добавление информации о доступных качествах видео в ABR

updateQualityInfo() code

Code Block
languagejs
themeRDark

        },
    updateQualityInfo: function(videoQuality) {
   ...
    };
    ...
     showItem(qualitySwitchDisplay);
                for (const qualityInfo of videoQuality) return instance;
}

8.3. Добавление аудио дорожки для отображения

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        addAudioTrack: function   let qualityColor = QUALITY_COLORS.UNAVAILABLE;(track) {
                    if (qualityInfo.available === true) {this.audioTracks.set(track.mid, track);
            const self = this;
         qualityColor  = QUALITY_COLORS.AVAILABLE;
   remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
                if (remoteTrack) }{
                    forif (const qualityDiv of qualityDivsself.disposed || !self.audioTracks.get(track.mid)) {
                        if (qualityDiv.innerText === qualityInfo.quality){
remoteTrack.dispose();
                        return;
           qualityDiv.style.color = qualityColor;
       }
                     breakthis.remoteAudioTracks.set(track.mid, remoteTrack);
                    remoteTrack.demandTrack(track.id).then(() => {
     }
                    }
if (!self.audioTracks.get(track.mid)) {
                      abr.setQualityAvailable(qualityInfo.quality, qualityInfo.available      remoteTrack.dispose();
                }
            },

3.9. Удаление контейнера с видео и аудио

dispose() code

Code Block
languagejs
themeRDark
self.remoteAudioTracks.delete(track.mid);
                          dispose: function() {return;
                 abr.stop();
       }
            cell.remove();
            },

4. Подписка на добавление дорожек в WebRTC соединение

PeerConnection.ontrack(), setAudio(), setVideo(), setVideoABRTrack() code

Здесь:

  • при получении видео или аудио потока, добавляется элемент для его проигрывания
Code Block
languagejs
themeRDark
    peerConnection.ontrack = ({transceiver}participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
                    }, (ex) => {
           let rParticipant;
             console.log("AttachFailed remotedemand track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);
ex);
                    for (const [nickName, participant] of ObjectremoteTrack.entriesdispose(remoteParticipants)) {;
            for (const pTrack of participant.tracks) {
       self.remoteAudioTracks.delete(track.mid);
         console.log("Participant " + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid });
                if (pTrack.mid === transceiver.mid) {}
            }, (ex) => {
     rParticipant = participant;
         console.log("Failed to get audio track " + ex);
    break;
        });
        },
        ...
    };
    ...
    return instance;
}

8.4. Удаление отображаемой аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = iffunction (rParticipantuserId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
         break;...
        removeAudioTrack: function (track)  }
    {
    }
        if (rParticipant!this.audioTracks.delete(track.mid)) {
            for (const display of rParticipant.displays) { return
            }

    if (transceiver.receiver.track.kind === "video") {
    participantView.removeAudioTrack(track);
            const remoteTrack   if (display.videoMid === transceiver= this.remoteAudioTracks.get(track.mid);
 {
           if (remoteTrack) {
            let stream = new MediaStream(this.remoteAudioTracks.delete(track.mid);
                        stream.addTrack(transceiver.receiver.trackremoteTrack.dispose();
            }
        },
      display  .setVideoABRTrack(transceiver.receiver.track);
    };
    ...
    return instance;
}

8.5. Запрос видео дорожки для отображения

requestVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory,   display.setVideo(stream);displayOptions) {
    const instance = {
        ...
        requestVideoTrack: break;
async function (track,  remoteTrack) {
            return new  }
  Promise((resolve, reject) => {
              } else if (transceiver.receiver.track.kind === "audio"!remoteTrack || !track) {
                    ifreject(new (display.audioMid === transceiver.mid) {
Error("Remote and local track must be defined"));
                    return;
            let stream = new MediaStream();}
                const self = this;
                streamremoteTrack.addTrackdemandTrack(transceivertrack.receiver.track);
  id).then(() => {
                      display.setAudio(stream);
if (!self.videoTracks.get(track.mid)) {
                        reject(new Error("Video track already removed from breakmodel"));
                       } return;
                    }
            }
        let }abrManager else {
            console.warn("Failed to find participant for track " + transceiver.receiver.track.id);
        }
    }

5. Остановка воспроизведения

...

= self.abrManagers.get(track.id);

                    if (abrManager) {
                        abrManager.clearQualityState();
                    } else if (abrFactory) {
                        abrManager = abrFactory.createAbrManager();
                        self.abrManagers.set(track.id, abrManager);
                    }

                    if (abrManager) {
                        abrManager.setTrack(remoteTrack);
                        abrManager.stop();
                        if (track.quality.length > 0) {
                            participantView.addQuality(track, "Auto", true, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (!manager) {
                                    return;
                                }
                                manager.start();
                                manager.setAuto();
                                participantView.pickQuality(track, "Auto");
                            });
                            if (displayOptions.autoAbr) {
                                abrManager.setAuto();
                                abrManager.start();
                                participantView.pickQuality(track, "Auto");
                            }
                        }
                    }
                    for (const qualityDescriptor of track.quality) {
                        if (abrManager) {
                            abrManager.addQuality(qualityDescriptor.quality);
                            abrManager.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
                        }
                        if (displayOptions.quality) {
                            participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (manager) {
                                    manager.setManual();
                                    manager.setQuality(qualityDescriptor.quality);
                                }
                                return self.pickQuality(track, qualityDescriptor.quality);
                            });
                        }
                    }
                    self.remoteVideoTracks.delete(track.mid);
                    self.remoteVideoTracks.set(track.mid, remoteTrack);
                    resolve();
                }, (ex) => {
                    reject(ex);
                });
            });
        },
        ...
    };
    ...
    return instance;
}

8.6. Обновление информации о доступных качествах потока

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        updateQualityInfo: function (remoteTracks) {
            for (const remoteTrackQuality of remoteTracks) {
                const track = this.videoTracks.get(remoteTrackQuality.mid);
                if (!track) {
                    continue;
                }
                if (!this.remoteVideoTracks.get(track.mid)) {
                    // update model and return, view not changed
                    for (const remoteQualityInfo of remoteTrackQuality.quality) {
                        const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                        if (quality) {
                            quality.available = remoteQualityInfo.available;
                        } else {
                            track.quality.push(remoteQualityInfo);
                        }
                    }
                    return;
                }
                let abrManager = this.abrManagers.get(track.id);
                if (abrManager && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
                    const self = this;
                    participantView.addQuality(track, "Auto", true, async () => {
                        const manager = self.abrManagers.get(track.id);
                        if (!manager) {
                            return;
                        }
                        manager.start();
                        manager.setAuto();
                        participantView.pickQuality(track, "Auto");
                    })
                    if (displayOptions.autoAbr) {
                        abrManager.setAuto();
                        abrManager.start();
                        participantView.pickQuality(track, "Auto");
                    }
                }
                for (const remoteQualityInfo of remoteTrackQuality.quality) {
                    const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                    if (localQuality) {
                        localQuality.available = remoteQualityInfo.available;
                        if (abrManager) {
                            abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available);
                        }
                        if (displayOptions.quality) {
                            participantView.updateQuality(track, localQuality.quality, localQuality.available);
                        }
                    } else {
                        track.quality.push(remoteQualityInfo);
                        if (abrManager) {
                            abrManager.addQuality(remoteQualityInfo.quality);
                            abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            const self = this;
                            participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (manager) {
                                    manager.setManual();
                                    manager.setQuality(remoteQualityInfo.quality);
                                }
                                return self.pickQuality(track, remoteQualityInfo.quality);
                            });
                        }
                    }
                }
            }

        },
        ...
    };
    ...
    return instance;
}

8.7. Выбор качества для проигрывания

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        pickQuality: async function (track, qualityName) {
            let remoteVideoTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteVideoTrack) {
                return remoteVideoTrack.setPreferredQuality(qualityName).then(() => {
                    participantView.pickQuality(track, qualityName);
                });
            }
        },
        ...
    };
    ...
    return instance;
}

8.8. Заглушить видео участника

muteVideo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        muteVideo: async function (track) {
            const remoteTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteTrack) {
                return remoteTrack.mute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        },
        ...
    };
    ...
    return instance;
}

8.9. Возобновить видео участника

unmuteVideo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        unmuteVideo: async function (track) {
            const remoteTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteTrack) {
                return remoteTrack.unmute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        }
    };
    ...
    return instance;
}

8.10. Завершение работы

dispose() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        dispose: async function () {
            this.disposed = true;
            participantView.dispose();
            this.remoteVideoTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteVideoTracks.clear();

            this.remoteAudioTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteAudioTracks.clear();

            this.abrManagers.forEach((abrManager, id) => {
                abrManager.stop();
            })
            this.abrManagers.clear();

        },
    };
    ...
    return instance;
}

9. Создание объекта модели участника в комнате со многими участниками

createOneToManyParticipantModel() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        userId: userId,
        nickname: nickname,
        videoEnabled: false,
        currentTrack: null,
        remoteVideoTrack: null,
        remoteAudioTracks: new Map(),
        audioTracks: new Map(),
        videoTracks: new Map(),
        abr: null,
        disposed: false,
        dispose: async function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setUserId: function (userId) {
            ...
        },
        setNickname: function (nickname) {
            ...
        },
        updateQualityInfo: function (remoteTracks) {
            ...
        },
        requestVideoTrack: async function (track, remoteTrack) {
            ...
        },
        pickQuality: async function (track, qualityName) {
            ...
        },
        muteVideo: async function (track) {
            ...
        },
        unmuteVideo: async function (track) {
            ...
        }
    };
    instance.setUserId(userId);
    instance.setNickname(nickname);
    if (abrFactory) {
        instance.abr = abrFactory.createAbrManager();
    }
    return instance;
}

9.1. Добавление видео дорожки для отображения

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        addVideoTrack: function (track) {
            this.videoTracks.set(track.mid, track);
            if (!track.quality) {
                track.quality = [];
            }
            const self = this;
            participantView.addVideoTrack(track, () => {
                if (self.disposed) {
                    return new Promise((resolve, reject) => {
                        reject(new Error("Model disposed"));
                    });
                }

                if (self.remoteVideoTrack) {
                    return new Promise((resolve, reject) => {
                        self.requestVideoTrack(track, self.remoteVideoTrack).then(() => {
                            resolve();
                        }, (ex) => {
                            reject(ex);
                        });
                    });
                } else {
                    return new Promise((resolve, reject) => {
                        reject(new Error("Remote track is null"));
                        requestTrackAndPick(self, track);
                    });
                }
            });
            requestTrackAndPick(this, track);
        },
        ...
    };
    ...
    return instance;
}

9.2. Удаление отображаемой видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        removeVideoTrack: function (track) {
            this.videoTracks.delete(track.mid);
            participantView.removeVideoTrack(track);
            if (this.currentTrack && this.currentTrack.mid === track.mid) {
                repickTrack(this, track);
            }
        },
        ...
    };
    ...
    return instance;
}

9.3. Добавление аудио дорожки для отображения

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        addAudioTrack: async function (track) {
            this.audioTracks.set(track.mid, track);
            const self = this;
            remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
                if (!remoteTrack) {
                    return;
                }
                if (self.disposed || !self.audioTracks.get(track.mid)) {
                    remoteTrack.dispose();
                    return;
                }
                this.remoteAudioTracks.set(track.mid, remoteTrack);
                remoteTrack.demandTrack(track.id).then(() => {
                    if (!self.audioTracks.get(track.mid)) {
                        remoteTrack.dispose();
                        self.remoteAudioTracks.delete(track.mid);
                        return;
                    }
                    participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
                }, (ex) => {
                    console.log("Failed demand track " + ex);
                    remoteTrack.dispose();
                    self.remoteAudioTracks.delete(track.mid);
                });
            }, (ex) => {
                console.log("Failed to get audio track " + ex);
            });

        },
        ...
    };
    ...
    return instance;
}

9.4. Удаление отображаемой аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        removeAudioTrack: function (track) {
            if (!this.audioTracks.delete(track.mid)) {
                return
            }

            participantView.removeAudioTrack(track);
            const remoteTrack = this.remoteAudioTracks.get(track.mid);
            if (remoteTrack) {
                this.remoteAudioTracks.delete(track.mid);
                remoteTrack.dispose();
            }

        },
        ...
    };
    ...
    return instance;
}

9.5. Запрос видео дорожки для отображения

requestVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        requestVideoTrack: async function (track, remoteTrack) {
            return new Promise((resolve, reject) => {
                if (!remoteTrack || !track) {
                    reject(new Error("Remote and local track must be defined"));
                    return;
                }
                const self = this;
                remoteTrack.demandTrack(track.id).then(() => {
                    // channels reordering case, must be removed after channels unification
                    if (!self.videoTracks.get(track.mid)) {
                        reject(new Error("Video track already removed from model"));
                        return;
                    }
                    self.currentTrack = track;
                    participantView.clearQualityState(track);
                    if (self.abr) {
                        self.abr.stop();
                        self.abr.clearQualityState();
                        self.abr.setTrack(remoteTrack);

                        if (track.quality.length > 0) {
                            participantView.addQuality(track, "Auto", true, async () => {
                                if (!self.abr) {
                                    return;
                                }
                                self.abr.start();
                                self.abr.setAuto();
                                participantView.pickQuality(track, "Auto");
                            })
                        }
                        if (displayOptions.autoAbr) {
                            self.abr.setAuto();
                            self.abr.start();
                            participantView.pickQuality(track, "Auto");
                        }
                    }
                    for (const qualityDescriptor of track.quality) {
                        if (self.abr) {
                            self.abr.addQuality(qualityDescriptor.quality);
                            self.abr.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
                        }
                        if (displayOptions.quality) {
                            participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
                                if (self.abr) {
                                    self.abr.setManual();
                                    self.abr.setQuality(qualityDescriptor.quality);
                                }
                                return self.pickQuality(track, qualityDescriptor.quality);
                            });
                        }
                    }
                    resolve();
                }, (ex) => reject(ex));
            });
        },
        ...
    };
    ...
    return instance;
}

9.6. Обновление информации о доступных качествах потока

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        updateQualityInfo: function (remoteTracks) {
            for (const remoteTrackQuality of remoteTracks) {
                const track = this.videoTracks.get(remoteTrackQuality.mid);
                if (!track) {
                    continue;
                }
                if (!this.currentTrack || this.currentTrack.mid !== track.mid) {
                    // update model and return, view not changed
                    for (const remoteQualityInfo of remoteTrackQuality.quality) {
                        const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                        if (quality) {
                            quality.available = remoteQualityInfo.available;
                        } else {
                            track.quality.push(remoteQualityInfo);
                        }
                    }
                    return;
                }
                if (this.abr && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
                    const self = this;
                    participantView.addQuality(track, "Auto", true, async () => {
                        if (!self.abr) {
                            return;
                        }
                        self.abr.start();
                        self.abr.setAuto();
                        participantView.pickQuality(track, "Auto");
                    })
                    if (displayOptions.autoAbr && this.abr) {
                        this.abr.setAuto();
                        this.abr.start();
                        participantView.pickQuality(track, "Auto");
                    }
                }
                for (const remoteQualityInfo of remoteTrackQuality.quality) {
                    const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                    if (localQuality) {
                        localQuality.available = remoteQualityInfo.available;
                        if (this.abr) {
                            this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            participantView.updateQuality(track, localQuality.quality, localQuality.available);
                        }
                    } else {
                        track.quality.push(remoteQualityInfo);
                        if (this.abr) {
                            this.abr.addQuality(remoteQualityInfo.quality);
                            this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            const self = this;
                            participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
                                if (self.abr) {
                                    self.abr.setManual();
                                    self.abr.setQuality(remoteQualityInfo.quality);
                                }
                                return self.pickQuality(track, remoteQualityInfo.quality);
                            });
                        }
                    }
                }
            }
        },
        ...
    };
    ...
    return instance;
}

9.7. Выбор качества для проигрывания

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        pickQuality: async function (track, qualityName) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.setPreferredQuality(qualityName).then(() => participantView.pickQuality(track, qualityName));
            }
        },
        ...
    };
    ...
    return instance;
}

9.8. Заглушить видео участника

muteVideo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        muteVideo: async function (track) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.mute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        },
        ...
    };
    ...
    return instance;
}

9.9. Возобновить видео участника

unmuteVideo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        unmuteVideo: async function (track) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.unmute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        }
    };
    ...
    return instance;
}

9.10. Завершение работы

dispose() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        dispose: async function () {
            this.disposed = true;
            participantView.dispose();
            this.remoteVideoTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteVideoTracks.clear();

            this.remoteAudioTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteAudioTracks.clear();

            this.abrManagers.forEach((abrManager, id) => {
                abrManager.stop();
            })
            this.abrManagers.clear();

        },
    };
    ...
    return instance;
}

10. Создание объекта отображения участника в комнате с двумя участниками

createOneToOneParticipantView() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {

    const participantDiv = createContainer(null);

    const audioDisplay = createContainer(participantDiv);

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

    const videoPlayers = new Map();
    const audioElements = new Map();

    return {
        rootDiv: participantDiv,
        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) {
            ...
        },
        pickQuality: function (track, qualityName) {
            ...
        }
    }
}

10.1. Добавление видео дорожки

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addVideoTrack: function (track) {
            const player = createVideoPlayer(participantDiv);
            videoPlayers.set(track.mid, player);
        },
        ...
    }
}

10.2. Удаление видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        removeVideoTrack: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.dispose();
            }
        },
        ...
    }
}

10.3. Добавление источника к видео элементу

addVideoSource() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
            }
        },
        ...
    }
}

10.4. Удаление источника из видео элемента

removeVideoSource() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        removeVideoSource: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.removeVideoSource();
            }
        },
        ...
    }
}

10.5. Показать видео дорожку

showVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        showVideoTrack: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.showVideoTrack(track);
            }
        },
        ...
    }
}

10.6. Добавление аудио дорожки

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = 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;
        },
        ...
    }
}

10.7. Удаление аудио дорожки

removeAudioTrack() code

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

10.8. Отображение имени участника

setNickname() code

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

10.9. Обновление информации о качестве

updateQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        updateQuality: function (track, qualityName, available) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.updateQuality(qualityName, available);
            }
        },
        ...
    }
}

10.10. Добавление информации о качестве

addQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addQuality: function (track, qualityName, available, onQualityPick) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.addQuality(qualityName, available, onQualityPick);
            }
        },
        ...
    }
}

10.11. Выбор качества

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        pickQuality: function (track, qualityName) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.pickQuality(qualityName);
            }
        }
        ...
    }
}

10.12. Завершение отображения

dispose() code

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. Создание объекта отображения участника в комнате с несколькими участниками

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. Добавление видео дорожки

addVideoTrack() code

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

11.2. Удаление видео дорожки

removeVideoTrack() code

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

11.3. Добавление источника к видео элементу

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. Удаление источника из видео элемента

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. Показать видео дорожку

showVideoTrack() code

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

11.6. Добавление аудио дорожки

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. Удаление аудио дорожки

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. Отображение имени участника

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. Обновление информации о качестве

updateQuality() code

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

11.10. Добавление информации о качестве

addQuality() code

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

11.11. Выбор качества

pickQuality() code

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

11.12. Очистка отображаемого списка качеств

clearQualityState() code

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

11.13. Завершение отображения

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. Создание объекта видео плеера

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(), 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

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. Настройка обработчиков событий плеера для других браузеров

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. Перерисовка кнопок переключения качества

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. Удаление кнопок переключения качества

clearQualityState() code

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

12.6. Добавление видео дорожки

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. Удаление видео дорожки

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. Добавление видео элемента и назначение видео дорожки как источника проигрывания

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. Удаление видео элемента

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. Отображение видео элемента и информации о видео дорожке

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. Обновление информации о качестве

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. Добавление кнопки выбора качества

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. Нажатие на кнопку выбора качества

pickQuality() code

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

12.14. Завершение работы плеера

dispose() code

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

13. Получение дорожки из комнаты для отображения

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. Вспомогательные функции

14.1. Изменение размера видео под размеры плеера

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. Создание элемента для отображения текстовой информации

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. Создание элемента-контейнера

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. Скрытие и отображение элемента на странице

showItem(), hideItem() code

Code Block
languagejs
themeRDark
    const stopshowItem = function (tag) {
    if    for (const [nickName, participant] of Object.entries(remoteParticipants)(tag) {
            participant.displays.forEach(function(display){
  tag.style.display = "block";
    }
}

const hideItem = function (tag) {
    if display.dispose(tag);
            }); {
        tag.style.display    delete remoteParticipants[nickName]= "none";
        }
    }