...
Code Block | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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; } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 () {
for (const player of videoPlayers.values()) {
player.dispose();
}
videoPlayers.clear();
for (const element of audioElements.values()) {
element.remove();
}
audioElements.clear();
},
addVideoTrack: function (track) {
const player = createVideoPlayer(participantDiv);
videoPlayers.set(track.mid, player);
},
removeVideoTrack: function (track) {
const player = videoPlayers.get(track.mid);
if (player) {
player.dispose();
}
},
addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
const player = videoPlayers.get(track.mid);
if (player) {
player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
}
},
removeVideoSource: function (track) {
const player = videoPlayers.get(track.mid);
if (player) {
player.removeVideoSource();
}
},
showVideoTrack: function (track) {
const player = videoPlayers.get(track.mid);
if (player) {
player.showVideoTrack(track);
}
},
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;
},
removeAudioTrack: function (track) {
const audioElement = audioElements.get(track.mid);
if (audioElement) {
audioElement.remove();
audioElements.delete(track.mid);
}
},
setNickname: function (userId, nickname) {
const additionalUserId = userId ? "#" + getShortUserId(userId) : "";
participantNicknameDisplay.innerText = "Name: " + nickname + additionalUserId;
},
updateQuality: function (track, qualityName, available) {
const player = videoPlayers.get(track.mid);
if (player) {
player.updateQuality(qualityName, available);
}
},
addQuality: function (track, qualityName, available, onQualityPick) {
const player = videoPlayers.get(track.mid);
if (player) {
player.addQuality(qualityName, available, onQualityPick);
}
},
pickQuality: function (track, qualityName) {
const player = videoPlayers.get(track.mid);
if (player) {
player.pickQuality(qualityName);
}
}
}
} |