Пример демонстрирует тестирование пропускной способности канала публикации
На скриншоте ниже
- 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, для хранения текущего состояния и работы с конфигурацией тестовой комнаты
const constants = SFU.constants; const sfu = SFU; let bitrateTestState; const BITRATE_TEST = "bitrateTest"; const TEST_DURATION = 30000;
2. Объект для хранения текущего состояния тестирования
Хранит данные Websocket сессии, WebRTC соединения, SFU комнаты и объекта для запуска теста
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. Инициализация
Функция init() вызывается после того, как страница загрузится:
- инициализирует объекты состояния
- инициализирует поля ввода
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() вызывается по нажатию кнопки Start:
- создает объект PeerConnection
- очищает отображение статуса предыдущей сессии
- настраивает конфигурацию комнаты и создает Websocket сессию
- подписывается на события Websocket сессии
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. Запуск тестирования канала при установке соединения
Функция onConnected():
- настраивает действия по нажатию кнопки Stop
- подписывается на события об ошибках и завершении комнаты
- вызывает функцию тестирования
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
- запускает тест канала
- отображает результаты теста
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
const stopBitrateTest = function (state) { const controller = state.getBitrateController(); if (controller) { controller.stop(); } }
8. Действия по нажатию кнопки Start
connect() code
Функция onStartClick():
- проверяет правильность заполнения полей ввода
- вызывает функцию connect()
const onStartClick = function (state) { if (validateForm("connectionForm", state.errInfoId())) { $("#" + state.buttonId()).prop('disabled', true); connect(state); } }
9. Действия по нажатию кнопки Stop
Session.disconnect() code
Функция onStopClick():
- останавливает тест, если он запущен
- разрывает Websocket сессию
const onStopClick = async function (state) { if (state.isConnected()) { stopBitrateTest(state); await state.session.disconnect(); onDisconnected(state); } }
10. Действия при разрыве Websocket сессии
Функция onDisconnected():
- настраивает действия по нажатию кнопки Start
- открывает доступ к полям ввода Server url и Test duration
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); }