import { EventEmitterProxy } from 'events-constructor'; import { StatsPeerConnection, TStatsPeerConnectionEventMap } from '../StatsPeerConnection'; import { ApiManager } from '../ApiManager'; import { CallManager } from '../CallManager'; import { TOutboundVideoVerificationStrictness } from './constants'; declare class StatsManager extends EventEmitterProxy { readonly statsPeerConnection: StatsPeerConnection; private availableStats; private previousAvailableStats; private readonly callManager; private readonly apiManager; constructor({ callManager, apiManager, }: { callManager: CallManager; apiManager: ApiManager; }); get availableIncomingBitrate(): number | undefined; get isInvalidInboundFrames(): boolean; get isNoInboundVideoTraffic(): boolean; get isInboundVideoStalled(): boolean; private get isEmptyInboundFrames(); private get previousAvailableIncomingBitrate(); private get previousInboundRtp(); private get previousFramesReceived(); private get previousFramesDecoded(); private get inboundRtp(); private get framesReceived(); private get framesDecoded(); private get isFramesReceived(); private get isFramesDecoded(); private get packetsReceived(); private get previousPacketsReceived(); private get bytesReceived(); private get previousBytesReceived(); private get isReceivingPackets(); private get outboundVideoBytesSent(); private get outboundVideoFramesEncoded(); private get outboundVideoMediaSourceFrames(); private get previousOutboundVideoBytesSent(); private get previousOutboundVideoFramesEncoded(); private get previousOutboundVideoMediaSourceFrames(); /** * Вычисляет дельту метрики относительно baseline. * * Если `current` отсутствует в stats — дельту определить нельзя (undefined). * Если baseline отсутствует (пустой snapshot, stats ещё не собирались) — считаем baseline = 0, * т.е. любое положительное current уже считается приростом после замены. */ private static resolveDelta; /** * Дельта кадров: берём максимум из двух источников WebRTC stats. * * Браузеры могут заполнять либо `outbound-rtp.framesEncoded`, либо `media-source.frames`, * либо оба. Для верификации достаточно роста хотя бы в одном из них. * * Если в current нет ни одного frame-счётчика — дельту кадров определить нельзя. */ private static resolveFramesDelta; /** Извлекает outbound video-поля из полного объекта stats (`collected`). */ private static extractOutboundVideoStatsSnapshot; /** * Ожидает, пока stats подтвердят реальную отправку video с указанным track id. * * ## Алгоритм * * 1. Фиксируем baseline — snapshot метрик до начала ожидания (см. `captureOutboundVideoWaitBaseline`). * 2. На каждом событии `collected` (poll stats ~раз в 1 с) проверяем current против baseline. * 3. Успех, когда `isSendingOutboundVideoPacketsWithTrack` возвращает `isSending: true`. * 4. При таймауте — reject с ошибкой. * * ## Почему baseline, а не previous poll * * Сравнение с «предыдущим poll'ом» даёт ложное срабатывание сразу после replace: * `trackIdentifier` уже новый, а `packetsSent` вырос на +1 между двумя соседними сэмплами. * Baseline фиксирует состояние **на момент вызова wait** (сразу после replaceMediaStream), * поэтому мы ждём накопленный прирост трафика **после замены**, а не любой монotonic tick. * * @param trackId — `MediaStreamTrack.id` нового video track из replaceMediaStream * @param timeout — максимальное время ожидания (мс) * @param strictness — режим порогов (см. `OUTBOUND_VIDEO_VERIFICATION_THRESHOLDS`) */ waitForOutboundVideoPackets(trackId: string, { timeout, strictness, }?: { timeout?: number; strictness?: TOutboundVideoVerificationStrictness; }): Promise; hasAvailableIncomingBitrateChangedQuarter(): boolean; private getOutboundVideoStatsSnapshot; /** * Фиксирует baseline для wait сразу после replaceMediaStream. * * Берём `previousAvailableStats`, если он есть: это последний poll **до** текущего * `availableStats`, т.е. состояние «до замены» или непосредственно перед ней. * Если stats ещё ни разу не собирались — fallback на `availableStats` или пустой объект. */ private captureOutboundVideoWaitBaseline; /** * Проверяет, что current stats удовлетворяют порогам strictness относительно baseline. * * Шаги: * 1. `media-source.trackIdentifier` должен совпасть с id нового track (metadata swap). * 2. Дельта `packetsSent` от baseline >= порога. * 3. Дельта `bytesSent` от baseline >= порога (в fast может быть 0). * 4. Для `normal`/`strict`: дельта кадров >= порога (fast кадры не проверяет). */ private meetsOutboundVideoThresholds; /** * Interval delta: рост метрик между двумя **соседними** poll'ами stats. * * Используется только в режиме `strict`. Отличается от baseline-delta: * baseline отвечает на «сколько отправили с момента replace», interval — «идёт ли отправка * прямо сейчас между poll N и poll N+1». * * Если на одном poll'е пороги от baseline уже выполнены, но на следующем poll'е счётчики * не выросли — strict сбрасывает consecutive-счётчик (отправка не устойчива). */ private hasPositiveOutboundVideoIntervalDelta; /** * Главная проверка: «отправляется ли video с нужным track» на текущем poll'е. * * Возвращает: * - `isSending: true` — все условия выполнены, можно резолвить wait-промис. * - `newVerificationState` — обновлённый счётчик consecutive poll'ов (для strict — 2 подряд). * * Логика: * 1. `meetsOutboundVideoThresholds` — baseline-проверка (track + deltas). * 2. Для `strict`: дополнительно `hasPositiveOutboundVideoIntervalDelta`. * 3. Если оба шага OK — increment consecutive; иначе сброс в 0. * 4. Успех, когда consecutive >= `requiredConsecutivePositiveSamples`. */ private isSendingOutboundVideoPacketsWithTrack; private subscribe; private readonly handleStatsCollected; private readonly start; private readonly stop; private restart; private readonly handleRecvSessionStarted; private readonly handleRecvSessionEnded; private readonly handleRecvQualityChanged; private readonly handleEnded; private maybeSendStats; } export default StatsManager;