Versions Compared

Key

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

...

Объявление локальных переменных для работы с константами, SFU SDK, для отображения локального видео и работы с конфигурацией клиента

code

Code Block
languagejs
themeRDark
const constants = SFU.constants;
const sfu = SFU;
let localDisplay;
let cControls;

...

Объявление конфигурации комнаты и публикации потоков по умолчанию, на случай, если нет файла конфигурации config.json

code

Клиент настраивается на соединение с сервером по WSS по адресу localhost для входа в комнату "ROOM1" с пин-кодом "1234" под именем "Alice". Секция media задает публикацию аудио и видео дорожек. Видео публикуется двумя дорожками с качествами high (h) и medium (m)

Code Block
languagejs
themeRDark
const defaultConfig = {
    room: {
        url: "wss://127.0.0.1:8888",
        name: "ROOM1",
        pin: "1234",
        nickName: "Alice"
    },
    media: {
        audio: {
            tracks: [
                {
                    source: "mic",
                    channels: 1
                }
            ]
        },
        video: {
            tracks: [
                {
                    source: "camera",
                    width: 1280,
                    height: 720,
                    codec: "H264",
                    encodings: [
                        { rid: "hm", active: true, maxBitrate: 300000, 900000scaleResolutionDownBy: 2},
                        { rid: "mh", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 900000}
                    ]
                }
            ]
        }
    }
};

3. Инициализация

init() code

Функция init() вызывается после того, как страница загрузится. Функция загружает config.json или конфигурацию по умолчанию , создает элемент для отображения локального видео и открывает модальное окно входа

Code Block
languagejs
themeRDark
/**
 * load config and show entrance modal
 */ 
const init = function () {
    //read config
    $.getJSON("config.json", function (config) {
        cControls = createControls(config);
    }).fail(function () {
        //use default config
        cControls = createControls(defaultConfig);
    });
    //createopen local display to show local streamsentrance modal
    localDisplay = initLocalDisplay(document.getElementById("localDisplay"));
    //open entrance modal
    $('#entranceModal').modal('show');
}

...

$('#entranceModal').modal('show');
}

4. Соединение с сервером и создание либо вход в комнату

connect() code

Функция вызывается по щелчку пользователя по кнопке Enter  в модальном окне входа

Code Block
languagejs
themeRDark
/**
 * connect to server
 */ 
async function connect() {
    // hide modal
    $('#entranceModal').modal('hide');
    // disable controls
    cControls.muteInput();
    //create peer connection
    const pc = new RTCPeerConnection();
    //get config object for room creation
    const roomConfig = cControls.roomConfig();
    roomConfig.pc = pc;
    //kick off connect to server and local room creation
    try {
        const session = await sfu.createRoom(roomConfig);
      session.on(constants.SFU_EVENT.CONNECTED, function(room) {
        //connected to server  // Now we connected to the server (if no exception was thrown)
        const chatDiv = document.getElementById('messages');session.on(constants.SFU_EVENT.FAILED, function (e) {
        const chatInput = document.getElementById('localMessage');
      if (e.status && e.statusText) {
   const chatButton = document.getElementById('sendMessage');
        //create and bind chat to the new room
    displayError("CONNECTION FAILED: " + e.status + " " + e.statusText);
    createChat(room, chatDiv, chatInput, chatButton);

     } else if room.on(constants.SFU_ROOM_EVENT.FAILED, function(e(e.type && e.info) {
            const  errField = document.getElementByIddisplayError("errorMsg"CONNECTION FAILED: " + e.info);
            errField.style.color = "red";} else {
            errField.innerText = e    displayError("CONNECTION FAILED: " + e);
            }
        }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILEDDISCONNECTED, function (e) {
            displayError("DISCONNECTED. Refresh the page to constenter errFieldthe = document.getElementById("errorMsg"room again");
        });
    errField.style.color  = "red";
 const room = session.room();
        errField.innerText = e.operation + " failed: " + e.errorroom.on(constants.SFU_ROOM_EVENT.FAILED, function (e) {
            displayError(e);
        })
   .on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
     //setup remote display for showing remote audio/video tracks
        const remoteDisplay = document.getElementById("display" displayError(e.operation + " failed: " + e.error);
        initRemoteDisplay(room, remoteDisplay, pc);})

        //get configuredcreate local video display to show local streams
        letlocalDisplay streams = cControlsinitLocalDisplay(document.getVideoStreamsgetElementById("localDisplay"));
        //combine local display audio and video streams with audio streamscontrol tables
        streams.push.apply(streams,await cControls.getAudioStreamsdisplayTables());
        //add our local streams to the room (to PeerConnection)
cControls.onTrack(async function (s) {
            await streams.forEach(function (s) {
publishNewTrack(room, pc, s);
        });
        //addcreate and localbind streamchat to localthe new displayroom
        const chatDiv =  localDisplaydocument.add(s.stream.id, "local", s.streamgetElementById('messages');
        const chatInput = document.getElementById('localMessage');
   //add each track to PeerConnection
 const chatButton = document.getElementById('sendMessage');
        s.stream.getTracks().forEach((track) => {createChat(room, chatDiv, chatInput, chatButton);

        //setup remote display for showing remote   addTrackToPeerConnection(pc, s.stream, track, s.encodings);
audio/video tracks
        const remoteDisplay = document.getElementById("display");
        subscribeTrackToEndedEventinitDefaultRemoteDisplay(room, trackremoteDisplay, pc);
  {quality: true},{thresholds: [
          });
      {parameter: "nackCount", });
maxLeap: 10},
         //add callback for the new local stream to the local controls{parameter: "freezeCount", maxLeap: 10},
        cControls.onTrack(function (s) {
      {parameter: "packetsLost", maxLeap: 10}
   //add local stream to local display
    ], abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY,      localDisplay.add(s.stream.id, "local", s.stream);
abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY, interval: ABR_QUALITY_CHECK_PERIOD});

        //get configured local video //addstreams
 each track to PeerConnection
    let streams = cControls.getVideoStreams();
     s.stream.getTracks().forEach((track) => {
 //combine local video streams with audio streams
         addTrackToPeerConnection(pcstreams.push.apply(streams, s.stream, track, s.encodingscControls.getAudioStreams());

        // Publish preconfigured streams
     subscribeTrackToEndedEvent(room, track   publishPreconfiguredStreams(room, pc, streams);
    }        });
            //kickoff renegotiationcatch (e) {
            room.updateState(console.error(e);
        }displayError(e);
        //join room
        room.join();
    });
}

...

}
}

5. Подробнее о функции connect()

Скрытие модального окна входа и отключение полей ввода до установки соединения с сервером

code

Code Block
languagejs
themeRDark
async function connect() {
    // hide modal
    $('#entranceModal').modal('hide');
    // disable controls
    cControls.muteInput();
    ...
}

Создание объекта PeerConnection и подготовка объекта конфигурации комнаты

code

Code Block
languagejs
themeRDark
//create peer connection
const pc = new RTCPeerConnectionasync function connect() {
    ...
    //create peer connection
    const pc = new RTCPeerConnection();
    //get config object for room creation
    const roomConfig = cControls.roomConfig();
roomConfig.pc   = pc; ...
}

Создание сессии и установка соединения с сервером

code

Code Block
languagejs
themeRDark
const session = sfu.createRoom(roomConfig);

Подписка на событие сессии "CONNECTED"

code

Code Block
languagejs
themeRDark
session.on(constants.SFU_EVENT.CONNECTED, function(room) {

Инициализация чата после установки соединения

code

Code Block
languagejs
themeRDark
//connected to server
const chatDiv = document.getElementById('messages');
const chatInput = document.getElementById('localMessage');
const chatButton = document.getElementById('sendMessage');
//create and bind chat to the new room
createChat(room, chatDiv, chatInput, chatButton);

...

async function connect() {
    ...
    //kick off connect to server and local room creation
    try {
        const session = await sfu.createRoom(roomConfig);
        ...
    } catch (e) {
        console.error(e);
        displayError(e);
    }
}

Подписка на события сессии

code

Code Block
languagejs
themeRDark
room.on(constants.SFU_ROOM_EVENT.FAILED,async function connect(e) {
    const errField = document.getElementById("errorMsg");...
    errField.style.color = "red";
    errField.innerText = e;
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e)//kick off connect to server and local room creation
    try {
    const  errField = document.getElementById("errorMsg");
...
     errField.style.color  = "red";
    errField.innerText = e.operation + " failed: " + e.error;
})

Инициализация объекта для отображения потоков от других участников

code

Code Block
languagejs
themeRDark
//setup remote display for showing remote audio/video tracks
const remoteDisplay = document.getElementById("display");
initRemoteDisplay(room, remoteDisplay, pc);

Получение настроек публикации локального медиа

code

Code Block
languagejs
themeRDark
//get configured local video streams
let streams = cControls.getVideoStreams();
//combine local video streams with audio streams
streams.push.apply(streams, cControls.getAudioStreams());

Добавление каждого потока в объект localDisplay для отображения и в объект PeerConnection для публикации

code

Code Block
languagejs
themeRDark
 // Now we connected to the server (if no exception was thrown)
        session.on(constants.SFU_EVENT.FAILED, function (e) {
            if (e.status && e.statusText) {
            //add our local streams to the room (to PeerConnection)
        streams.forEach(function (s) {
 displayError("CONNECTION FAILED: " + e.status + " " + e.statusText);
            } //addelse local stream to local displayif (e.type && e.info) {
            localDisplay.add(s.stream.id, "local", s.stream    displayError("CONNECTION FAILED: " + e.info);
            //add} each track to PeerConnectionelse {
            s.stream.getTracks().forEach((track) => {
    displayError("CONNECTION FAILED: " + e);
        if (s.source === "screen") {}
        }).on(constants.SFU_EVENT.DISCONNECTED, function (e) {
         config[track.id] = s.source;
           displayError("DISCONNECTED. Refresh the page to enter the room again");
        });
        ...
    } catch (e)  addTrackToPeerConnection(pc, s.stream, track, s.encodings{
        console.error(e);
        displayError(e);
    }
}

Создание объекта комнаты и подписка на сообщения об ошибках

code

Code Block
languagejs
themeRDark
async function connect() {
  subscribeTrackToEndedEvent(room, track, pc); ...
    //kick off connect to server and local room });creation
    try {
   });

Добавление слушателя, чтобы определить, когда клиент добавляет новые потоки для публикации. Получив новый поток, необходимо добавить его в localDisplay для отображения, добавить в PeerConnection для публикации и обновить состояние комнаты

code

Code Block
languagejs
themeRDark
     ...
   //add callback for the new localconst streamroom to the local controls= session.room();
        cControlsroom.onTrack(on(constants.SFU_ROOM_EVENT.FAILED, function (se) {
            let config = {}displayError(e);
        }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function   //add local stream to local display(e) {
            localDisplay.adddisplayError(s.stream.id, "local", s.streame.operation + " failed: " + e.error);
        })
    //add each track to PeerConnection...
    }        s.stream.getTracks().forEach((track) => catch (e) {
        console.error(e);
        if (s.source === "screen"displayError(e);
    }
}

Создание объекта для отображения локального видео

code

Code Block
languagejs
themeRDark
async function connect() {
    ...
    //kick off connect to server and local room creation
    config[track.id] = s.source;try {
         ...
       }
 // create local display to show local streams
        addTrackToPeerConnection(pc, s.stream, track, s.encodingslocalDisplay = initLocalDisplay(document.getElementById("localDisplay"));
        // display audio and video    subscribeTrackToEndedEvent(room, track, pc);control tables
            }await cControls.displayTables();
        cControls.onTrack(async function (s) {
  //kickoff renegotiation
         await publishNewTrack(room,  room.updateState(configpc, s);
        });

Настройка WebRTC соединения в комнате

code

Code Block
languagejs
themeRDark
//join room
room.join(pc, null, config); 

6. Завершение публикации потока

subscribeTrackToEndedEvent() code

Вспомогательная функция, которая подписывается на событие "ended" для локального потока. При получении события поток удаляется из PeerConnection, и состояние комнаты обновляется.

Code Block
languagejs
themeRDark
const subscribeTrackToEndedEvent = function(room, track, pc) {
    track.addEventListener("ended", function() {

        ...
    } catch (e) {
        console.error(e);
        displayError(e);
    }
}

Инициализация окна чата

code

Code Block
languagejs
themeRDark
async function connect() {
    ...
    //kick off connect to server and local room creation
    try {
        ...
        //create and bind chat to the new room
        const chatDiv = document.getElementById('messages');
        const chatInput = document.getElementById('localMessage');
        const chatButton = document.getElementById('sendMessage');
        createChat(room, chatDiv, chatInput, chatButton);
        ...
    } catch (e) {
        console.error(e);
        displayError(e);
    }
}

Инициализация объекта для отображения потоков от других участников

code

Code Block
languagejs
themeRDark
async function connect() {
    ...
    //kick off connect to server and local room creation
    try {
        ...
        //setup remote display for showing remote audio/video tracks
        const remoteDisplay = document.getElementById("display");
        initDefaultRemoteDisplay(room, remoteDisplay, {quality: true},{thresholds: [
                {parameter: "nackCount", maxLeap: 10},
                {parameter: "freezeCount", maxLeap: 10},
                {parameter: "packetsLost", maxLeap: 10}
            ], abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY, abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY, interval: ABR_QUALITY_CHECK_PERIOD});
        ...
    } catch (e) {
        console.error(e);
        displayError(e);
    }
}

Получение настроек и публикация локальных медиа потоков

code

Code Block
languagejs
themeRDark
async function connect() {
    ...
    //kick off connect to server and local room creation
    try {
        ...
        //get configured local video streams
        let streams = cControls.getVideoStreams();
        //combine local video streams with audio streams
        streams.push.apply(streams, cControls.getAudioStreams());

        // Publish preconfigured streams
        publishPreconfiguredStreams(room, pc, streams);
    } catch (e) {
        console.error(e);
        displayError(e);
    }
}

6. Вход в комнату и публикация локальных потоков из файла конфигурации

publishPreconfiguredStreams(), Room.join() code

Code Block
languagejs
themeRDark
const publishPreconfiguredStreams = async function (room, pc, streams) {
    try {
        const config = {};
        //add our local streams to the room (to PeerConnection)
        streams.forEach(function (s) {
            let contentType = s.type || s.source;
            //add each track to PeerConnection
            s.stream.getTracks().forEach((track) => {
                config[track.id] = contentType;
                addTrackToPeerConnection(pc, s.stream, track, s.encodings);
                subscribeTrackToEndedEvent(room, track, pc);
            });
            localDisplay.add(s.stream.id, "local", s.stream, contentType);
        });
        //join room
        await room.join(pc, null, config, 10);
        // Enable Delete button for each preconfigured stream #WCS-3689
        streams.forEach(function (s) {
            $('#' + s.stream.id + "-button").prop('disabled', false);
        });
    } catch (e) {
        onOperationFailed("Failed to publish a preconfigured streams", e);
        // Enable Delete button for each preconfigured stream #WCS-3689
        streams.forEach(function (s) {
            $('#' + s.stream.id + "-button").prop('disabled', false);
        });
    }
}

7. Публикация дополнительных локальных потоков

publishNewTrack(), Room.updateState() code

Code Block
languagejs
themeRDark
const publishNewTrack = async function (room, pc, media) {
    try {
        let config = {};
        //add local stream to local display
        let contentType = media.type || media.source;

        localDisplay.add(media.stream.id, "local", media.stream, contentType);
        //add each track to PeerConnection
        media.stream.getTracks().forEach((track) => {
            config[track.id] = contentType;
            addTrackToPeerConnection(pc, media.stream, track, media.encodings);
            subscribeTrackToEndedEvent(room, track, pc);
        });
        // Clean error message
        displayError("");
        //kickoff renegotiation
        await room.updateState(config);
        // Enable Delete button for a new stream #WCS-3689
        $('#' + media.stream.id + "-button").prop('disabled', false);
    } catch (e) {
        onOperationFailed("Failed to publish a new track", e);
        // Enable Delete button for a new stream #WCS-3689
        $('#' + media.stream.id + "-button").prop('disabled', false);
    }
}

8. Завершение публикации потока

subscribeTrackToEndedEvent() code

Вспомогательная функция, которая подписывается на событие "ended" для локального потока. При получении события поток удаляется из PeerConnection, и состояние комнаты обновляется.

Code Block
languagejs
themeRDark
const subscribeTrackToEndedEvent = function (room, track, pc) {
    track.addEventListener("ended", async function () {
        try {
            //track ended, see if we need to cleanup
            let negotiate = false;
            for (const sender of pc.getSenders()) {
                if (sender.track === track) {
                    pc.removeTrack(sender);
                    //track found, set renegotiation flag
                    negotiate = true;
             //track ended, see if we need to cleanupbreak;
        let negotiate = false;
     }
   for (const sender of pc.getSenders()) {
     }
       if (sender.track === track) {
 // Clean error message
            pc.removeTrack(senderdisplayError("");
                //track found, set renegotiation flagif (negotiate) {
                negotiate = true;//kickoff renegotiation
                breakawait room.updateState();
            }
        }
        if (negotiatecatch (e) {
            //kickoff renegotiation
            room.updateState(onOperationFailed("Failed to update room state", e);
        }
    });
};

...

9. Добавление новой дорожки в PeerConnection

addTrackToPeerConnection() code

Вспомогательная функция, которая добавляет новую дорожку в PeerConnection  для публикации

...