...
- Server url - Websocket URL WCS сервера
- Test duration - максимальная длительность теста в мс
- Current bitrate - текущий измеренный битрейт передачи данных в кбит/с
Исходный код примера
Исходный код разбит на следующие модули:
- bitrate_test.html - HTML страница
- bitrate_test.css - стили HTML страницы
- bitrate_test.js - основная логика приложения
Анализ исходного кода
Для работы с исходным кодом примера возьмем версию файла bitrate_test.js
, доступную здесь.
1. Локальные переменные
Объявление локальных переменных для работы с константами, SFU SDK, для хранения текущего состояния и работы с конфигурацией тестовой комнаты
code
Code Block |
---|
|
const constants = SFU.constants;
const sfu = SFU;
let bitrateTestState;
const BITRATE_TEST = "bitrateTest";
const TEST_DURATION = 30000;
|
2. Объект для хранения текущего состояния тестирования
Хранит данные Websocket сессии, WebRTC соединения, SFU комнаты и объекта для запуска теста
code
Code Block |
---|
|
const CurrentState = function (prefix) {
let state = {
prefix: prefix,
pc: null,
session: null,
room: null,
bitrateController: null,
set: function (pc, session, room) {
state.pc = pc;
state.session = session;
state.room = room;
},
clear: function () {
state.room = null;
state.session = null;
state.pc = null;
state.bitrateController = null;
},
durationId: function () {
return state.prefix + "Duration";
},
buttonId: function () {
return state.prefix + "Btn";
},
statusId: function () {
return state.prefix + "Status";
},
errInfoId: function () {
return state.prefix + "ErrorInfo";
},
currentStateId: function () {
return state.prefix + "CurrentState";
},
getBitrateController: function () {
return state.bitrateController;
},
setBitrateController: function (controller) {
state.bitrateController = controller;
},
isConnected: function () {
return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
}
};
return state;
} |
3. Инициализация
code
Функция init() вызывается после того, как страница загрузится:
- инициализирует объекты состояния
- инициализирует поля ввода
Code Block |
---|
|
const init = function () {
bitrateTestState = CurrentState(BITRATE_TEST);
$("#" + bitrateTestState.buttonId()).prop('disabled', true);
$("#url").prop('disabled', true);
onDisconnected(bitrateTestState);
$("#url").val(setURL());
$("#" + bitrateTestState.durationId()).val(TEST_DURATION);
} |
4. Соединение с сервером
RTCPeerConnection(), SFU.createRoom() code
Функция connect() вызывается по нажатию кнопки Play:
- создает объект PeerConnection
- очищает отображение статуса предыдущей сессии
- настраивает конфигурацию комнаты и создает Websocket сессию
- подписывается на события Websocket сессии
Code Block |
---|
|
const connect = async function (state) {
//create peer connection
const pc = new RTCPeerConnection();
//get config object for room creation
const roomConfig = getRoomConfig(defaultConfig);
roomConfig.url = $("#url").val();
roomConfig.roomName = "ROOM1-" + createUUID(4);
roomConfig.nickname = "User1" + createUUID(4);
// clean status display items
setStatus(state.statusId(), " ");
setStatus(state.errInfoId(), " ");
// clean bitrate display item
$("#" + state.currentStateId()).val("");
// connect to server and create a room if not
try {
const session = await sfu.createRoom(roomConfig);
// Set up session ending events
session.on(constants.SFU_EVENT.DISCONNECTED, function () {
onStopClick(state);
onDisconnected(state);
setStatus(state.statusId(), "DISCONNECTED", "green");
}).on(constants.SFU_EVENT.FAILED, function (e) {
onStopClick(state);
onDisconnected(state);
setStatus(state.statusId(), "FAILED", "red");
if (e.status && e.statusText) {
setStatus(state.errInfoId(), e.status + " " + e.statusText, "red");
} else if (e.type && e.info) {
setStatus(state.errInfoId(), e.type + ": " + e.info, "red");
}
});
// Connected successfully
onConnected(state, pc, session);
setStatus(state.statusId(), "ESTABLISHED", "green");
} catch (e) {
onDisconnected(state);
setStatus(state.statusId(), "FAILED", "red");
setStatus(state.errInfoId(), e, "red");
}
} |
5. Запуск тестирования канала при установке соединения
code
Функция onConnected():
- настраивает действия по нажатию кнопки Stop
- подписывается на события об ошибках и завершении комнаты
- вызывает функцию тестирования
Code Block |
---|
|
const onConnected = function (state, pc, session) {
state.set(pc, session, session.room());
$("#" + state.buttonId()).text("Stop").off('click').click(function () {
onStopClick(state);
}).prop('disabled', false);
$('#url').prop('disabled', true);
$("#" + bitrateTestState.durationId()).prop('disabled', true);
// Add errors displaying
state.room.on(constants.SFU_ROOM_EVENT.FAILED, function (e) {
setStatus(state.errInfoId(), e, "red");
onStopClick(state);
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
onOperationFailed(state, e);
}).on(constants.SFU_ROOM_EVENT.ENDED, function () {
setStatus(state.errInfoId(), "Room " + state.room.name() + " has ended", "red");
onStopClick(state);
}).on(constants.SFU_ROOM_EVENT.DROPPED, function () {
setStatus(state.errInfoId(), "Dropped from the room " + state.room.name() + " due to network issues", "red");
onStopClick(state);
});
startBitrateTest(state);
} |
6. Тестирование канала
SFURoom.join(), BitrateTest.setListener(), BitrateTest.test() code
Функция startBitrateTest():
- присоединяется к SFU комнате и настраивает WebRTC соединение
- получает доступ к объекту BitrateTest
- добавляет обработчик события onStateChange объекта BitrateTest
- запускает тест канала
- отображает результаты теста
Code Block |
---|
|
const startBitrateTest = async function (state) {
if (state.room) {
await state.room.join(state.pc, null, {});
const stateSelector = $("#" + state.currentStateId());
stateSelector.attr("style", "display:inline-block;margin-left: 10px");
try {
const bitrateTest = state.room.getBitrateTest();
state.setBitrateController(bitrateTest);
bitrateTest.setListener({
onStatusUpdate(bitrateKbps) {
stateSelector.text("Current bitrate: " + bitrateKbps + " kbps");
}
});
bitrateTest.test($("#" + bitrateTestState.durationId()).val()).then((bitrateKbps) => {
stateSelector.text("Test is finished, last measured bitrate: " + bitrateKbps + " kbps");
state.setBitrateController(null);
onStopClick(state);
});
} catch (e) {
if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
onOperationFailed(state, e);
} else {
console.error("Failed to start bitrate test: " + e);
setStatus(state.errInfoId(), e.name, "red");
onStopClick(state);
}
}
}
} |
7. Остановка теста
BitrateTest.stop() code
Code Block |
---|
|
const stopBitrateTest = function (state) {
const controller = state.getBitrateController();
if (controller) {
controller.stop();
}
} |
8. Действия по нажатию кнопки Play
connect() code
Функция onStartClick():
- проверяет правильность заполнения полей ввода
- вызывает функцию connect()
Code Block |
---|
|
const onStartClick = function (state) {
if (validateForm("connectionForm", state.errInfoId())) {
$("#" + state.buttonId()).prop('disabled', true);
connect(state);
}
} |
9. Действия по нажатию кнопки Stop
Session.disconnect() code
Функция onStopClick():
- останавливает тест, если он запущен
- разрывает Websocket сессию
Code Block |
---|
|
const onStopClick = async function (state) {
if (state.isConnected()) {
stopBitrateTest(state);
await state.session.disconnect();
onDisconnected(state);
}
} |
10. Действия при разрыве Websocket сессии
code
Функция onDisconnected():
- настраивает действия по нажатию кнопки Start
- открывает доступ к полям ввода Server url и Test duration
Code Block |
---|
|
const onDisconnected = function (state) {
state.clear();
$("#" + state.buttonId()).text("Start").off('click').click(function () {
onStartClick(state);
}).prop('disabled', false);
$('#url').prop('disabled', false);
$("#" + bitrateTestState.durationId()).prop('disabled', false);
} |