From 70b63b8f6801f5cf3559dac7844f9c7e89158ab1 Mon Sep 17 00:00:00 2001 From: Igor Zolotarenko Date: Sun, 24 Dec 2023 19:29:10 +0200 Subject: [PATCH 1/5] Rename file. --- .../src/{bandwidth-approximator.ts => bandwidth-calculator.ts} | 0 packages/p2p-media-loader-core/src/core.ts | 2 +- packages/p2p-media-loader-core/src/hybrid-loader.ts | 2 +- packages/p2p-media-loader-core/src/request-container.ts | 2 +- packages/p2p-media-loader-core/src/request.ts | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename packages/p2p-media-loader-core/src/{bandwidth-approximator.ts => bandwidth-calculator.ts} (100%) diff --git a/packages/p2p-media-loader-core/src/bandwidth-approximator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts similarity index 100% rename from packages/p2p-media-loader-core/src/bandwidth-approximator.ts rename to packages/p2p-media-loader-core/src/bandwidth-calculator.ts diff --git a/packages/p2p-media-loader-core/src/core.ts b/packages/p2p-media-loader-core/src/core.ts index 3aab237c..1ef0e7cb 100644 --- a/packages/p2p-media-loader-core/src/core.ts +++ b/packages/p2p-media-loader-core/src/core.ts @@ -9,7 +9,7 @@ import { } from "./types"; import * as StreamUtils from "./utils/stream"; import { LinkedMap } from "./linked-map"; -import { BandwidthApproximator } from "./bandwidth-approximator"; +import { BandwidthApproximator } from "./bandwidth-calculator"; import { EngineCallbacks } from "./request"; import { SegmentsMemoryStorage } from "./segments-storage"; diff --git a/packages/p2p-media-loader-core/src/hybrid-loader.ts b/packages/p2p-media-loader-core/src/hybrid-loader.ts index ff2edc58..677f5a14 100644 --- a/packages/p2p-media-loader-core/src/hybrid-loader.ts +++ b/packages/p2p-media-loader-core/src/hybrid-loader.ts @@ -2,7 +2,7 @@ import { Segment, StreamWithSegments } from "./index"; import { fulfillHttpSegmentRequest } from "./http-loader"; import { SegmentsMemoryStorage } from "./segments-storage"; import { Settings, CoreEventHandlers, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-approximator"; +import { BandwidthApproximator } from "./bandwidth-calculator"; import { P2PLoadersContainer } from "./p2p/loaders-container"; import { RequestsContainer } from "./request-container"; import { EngineCallbacks } from "./request"; diff --git a/packages/p2p-media-loader-core/src/request-container.ts b/packages/p2p-media-loader-core/src/request-container.ts index f5f56667..faef5a87 100644 --- a/packages/p2p-media-loader-core/src/request-container.ts +++ b/packages/p2p-media-loader-core/src/request-container.ts @@ -1,5 +1,5 @@ import { Segment, Settings, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-approximator"; +import { BandwidthApproximator } from "./bandwidth-calculator"; import { Request } from "./request"; export class RequestsContainer { diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index a36cbef9..09ebc743 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -1,5 +1,5 @@ import { Segment, SegmentResponse, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-approximator"; +import { BandwidthApproximator } from "./bandwidth-calculator"; import * as StreamUtils from "./utils/stream"; import * as Utils from "./utils/utils"; import * as LoggerUtils from "./utils/logger"; From 91576fce3dac829b6ff6d35dfe152e6de3a48a41 Mon Sep 17 00:00:00 2001 From: Igor Zolotarenko Date: Tue, 26 Dec 2023 00:10:16 +0200 Subject: [PATCH 2/5] Add bandwidth calculator. --- .../src/bandwidth-calculator.ts | 28 ++++++++++++++++++- packages/p2p-media-loader-core/src/core.ts | 4 +-- .../src/hybrid-loader.ts | 4 +-- .../src/request-container.ts | 4 +-- packages/p2p-media-loader-core/src/request.ts | 4 +-- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts index f66e8ccc..95152a8a 100644 --- a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts +++ b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts @@ -1,6 +1,6 @@ import { LoadProgress } from "./request"; -export class BandwidthApproximator { +export class BandwidthCalculator { private readonly loadings: LoadProgress[] = []; addLoading(progress: LoadProgress) { @@ -55,3 +55,29 @@ function getBandwidthByProgressList(loadings: LoadProgress[]) { return (totalBytes * 8000) / totalLoadingTime; } + +class BandwidthCalculator1 { + private simultaneousLoadingsCount = 0; + private readonly bytes: number[] = []; + private readonly timestamps: number[] = []; + + addBytes(bytesLength: number) { + this.bytes.push(bytesLength); + this.timestamps.push(performance.now()); + } + + startLoading() { + this.simultaneousLoadingsCount++; + } + + stopLoading() { + if (this.simultaneousLoadingsCount === 0) return; + this.simultaneousLoadingsCount--; + } + + private clearStale() { + const length = this.bytes.length; + + for (let i = 0; i < length; i++) {} + } +} diff --git a/packages/p2p-media-loader-core/src/core.ts b/packages/p2p-media-loader-core/src/core.ts index 1ef0e7cb..aaede818 100644 --- a/packages/p2p-media-loader-core/src/core.ts +++ b/packages/p2p-media-loader-core/src/core.ts @@ -9,7 +9,7 @@ import { } from "./types"; import * as StreamUtils from "./utils/stream"; import { LinkedMap } from "./linked-map"; -import { BandwidthApproximator } from "./bandwidth-calculator"; +import { BandwidthCalculator } from "./bandwidth-calculator"; import { EngineCallbacks } from "./request"; import { SegmentsMemoryStorage } from "./segments-storage"; @@ -29,7 +29,7 @@ export class Core { p2pLoaderDestroyTimeoutMs: 30 * 1000, httpNotReceivingBytesTimeoutMs: 1000, }; - private readonly bandwidthApproximator = new BandwidthApproximator(); + private readonly bandwidthApproximator = new BandwidthCalculator(); private segmentStorage?: SegmentsMemoryStorage; private mainStreamLoader?: HybridLoader; private secondaryStreamLoader?: HybridLoader; diff --git a/packages/p2p-media-loader-core/src/hybrid-loader.ts b/packages/p2p-media-loader-core/src/hybrid-loader.ts index 677f5a14..f9fdc7a4 100644 --- a/packages/p2p-media-loader-core/src/hybrid-loader.ts +++ b/packages/p2p-media-loader-core/src/hybrid-loader.ts @@ -2,7 +2,7 @@ import { Segment, StreamWithSegments } from "./index"; import { fulfillHttpSegmentRequest } from "./http-loader"; import { SegmentsMemoryStorage } from "./segments-storage"; import { Settings, CoreEventHandlers, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-calculator"; +import { BandwidthCalculator } from "./bandwidth-calculator"; import { P2PLoadersContainer } from "./p2p/loaders-container"; import { RequestsContainer } from "./request-container"; import { EngineCallbacks } from "./request"; @@ -28,7 +28,7 @@ export class HybridLoader { private streamManifestUrl: string, requestedSegment: Segment, private readonly settings: Settings, - private readonly bandwidthApproximator: BandwidthApproximator, + private readonly bandwidthApproximator: BandwidthCalculator, private readonly segmentStorage: SegmentsMemoryStorage, private readonly eventHandlers?: Pick ) { diff --git a/packages/p2p-media-loader-core/src/request-container.ts b/packages/p2p-media-loader-core/src/request-container.ts index faef5a87..8391c916 100644 --- a/packages/p2p-media-loader-core/src/request-container.ts +++ b/packages/p2p-media-loader-core/src/request-container.ts @@ -1,5 +1,5 @@ import { Segment, Settings, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-calculator"; +import { BandwidthCalculator } from "./bandwidth-calculator"; import { Request } from "./request"; export class RequestsContainer { @@ -7,7 +7,7 @@ export class RequestsContainer { constructor( private readonly requestProcessQueueCallback: () => void, - private readonly bandwidthApproximator: BandwidthApproximator, + private readonly bandwidthApproximator: BandwidthCalculator, private readonly playback: Playback, private readonly settings: Settings ) {} diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index 09ebc743..1bfa2665 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -1,5 +1,5 @@ import { Segment, SegmentResponse, Playback } from "./types"; -import { BandwidthApproximator } from "./bandwidth-calculator"; +import { BandwidthCalculator } from "./bandwidth-calculator"; import * as StreamUtils from "./utils/stream"; import * as Utils from "./utils/utils"; import * as LoggerUtils from "./utils/logger"; @@ -67,7 +67,7 @@ export class Request { constructor( readonly segment: Segment, private readonly requestProcessQueueCallback: () => void, - private readonly bandwidthApproximator: BandwidthApproximator, + private readonly bandwidthApproximator: BandwidthCalculator, private readonly playback: Playback, private readonly settings: StreamUtils.PlaybackTimeWindowsSettings ) { From 41f62920a3b5895293bcef7bbc937090e4f6a760 Mon Sep 17 00:00:00 2001 From: Igor Zolotarenko Date: Fri, 29 Dec 2023 17:53:26 +0200 Subject: [PATCH 3/5] Add bandwidth calculator. --- .../src/bandwidth-calculator.ts | 113 ++++++++---------- packages/p2p-media-loader-core/src/core.ts | 5 +- .../src/hybrid-loader.ts | 16 +-- .../src/p2p/tracker-client.ts | 5 +- .../src/request-container.ts | 4 +- packages/p2p-media-loader-core/src/request.ts | 19 +-- .../p2p-media-loader-core/src/utils/utils.ts | 6 + 7 files changed, 82 insertions(+), 86 deletions(-) diff --git a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts index 95152a8a..8cae886a 100644 --- a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts +++ b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts @@ -1,65 +1,10 @@ -import { LoadProgress } from "./request"; +import { arrayBackwards } from "./utils/utils"; export class BandwidthCalculator { - private readonly loadings: LoadProgress[] = []; - - addLoading(progress: LoadProgress) { - this.clearStale(); - this.loadings.push(progress); - } - - // in bits per second - getBandwidth(): number { - this.clearStale(); - return getBandwidthByProgressList(this.loadings); - } - - private clearStale() { - const now = performance.now(); - for (const { startTimestamp } of this.loadings) { - if (now - startTimestamp <= 15000) break; - this.loadings.shift(); - } - } - - destroy() { - this.loadings.length = 0; - } -} - -function getBandwidthByProgressList(loadings: LoadProgress[]) { - if (!loadings.length) return 0; - let margin: number | undefined; - let totalLoadingTime = 0; - let totalBytes = 0; - const now = performance.now(); - - for (const { - startTimestamp: from, - lastLoadedChunkTimestamp: to = now, - loadedBytes, - } of loadings) { - totalBytes += loadedBytes; - - if (margin === undefined || from > margin) { - margin = to; - totalLoadingTime += to - from; - continue; - } - - if (from <= margin && to > margin) { - totalLoadingTime += to - margin; - margin = to; - } - } - - return (totalBytes * 8000) / totalLoadingTime; -} - -class BandwidthCalculator1 { private simultaneousLoadingsCount = 0; private readonly bytes: number[] = []; private readonly timestamps: number[] = []; + private loadingIntervals: { start: number; end?: number }[] = []; addBytes(bytesLength: number) { this.bytes.push(bytesLength); @@ -67,17 +12,63 @@ class BandwidthCalculator1 { } startLoading() { + this.clearStale(); + if (this.simultaneousLoadingsCount === 0) { + this.loadingIntervals.push({ start: performance.now() }); + } this.simultaneousLoadingsCount++; } stopLoading() { - if (this.simultaneousLoadingsCount === 0) return; + this.clearStale(); + if (this.simultaneousLoadingsCount <= 0) return; this.simultaneousLoadingsCount--; + if (this.simultaneousLoadingsCount !== 0) return; + this.loadingIntervals[this.loadingIntervals.length - 1].end = + performance.now(); } - private clearStale() { - const length = this.bytes.length; + // in bits per second + getBandwidthForLastNSeconds(seconds: number) { + this.clearStale(); + const { bytes, timestamps, loadingIntervals } = this; + const samplesLength = bytes.length; + const now = performance.now(); + const threshold = now - seconds * 1000; + + let loadedBytes = 0; + for (let i = samplesLength - 1; i >= 0; i--) { + if (timestamps[i] < threshold) break; + loadedBytes += bytes[i]; + } + + let clearLoadingTime = 0; + for (const { start, end } of arrayBackwards(loadingIntervals)) { + if (start < threshold && end !== undefined && end < threshold) break; + const from = Math.max(start, threshold); + const to = end ?? now; + clearLoadingTime += to - from; + } + + if (clearLoadingTime === 0) return 0; + return (loadedBytes * 8000) / clearLoadingTime; + } - for (let i = 0; i < length; i++) {} + private clearStale() { + const { timestamps, bytes, loadingIntervals } = this; + const samplesLength = bytes.length; + const threshold = performance.now() - 15000; + + let count = 0; + while (count < samplesLength && timestamps[count] < threshold) count++; + bytes.splice(0, count); + timestamps.splice(0, count); + + count = 0; + for (const { start, end } of loadingIntervals) { + if (!(start < threshold && end !== undefined && end <= threshold)) break; + count++; + } + loadingIntervals.splice(0, count); } } diff --git a/packages/p2p-media-loader-core/src/core.ts b/packages/p2p-media-loader-core/src/core.ts index 1ed8cbc3..20cbada8 100644 --- a/packages/p2p-media-loader-core/src/core.ts +++ b/packages/p2p-media-loader-core/src/core.ts @@ -31,7 +31,7 @@ export class Core { httpErrorRetries: 3, p2pErrorRetries: 3, }; - private readonly bandwidthApproximator = new BandwidthCalculator(); + private readonly bandwidthCalculator = new BandwidthCalculator(); private segmentStorage?: SegmentsMemoryStorage; private mainStreamLoader?: HybridLoader; private secondaryStreamLoader?: HybridLoader; @@ -113,7 +113,6 @@ export class Core { this.mainStreamLoader = undefined; this.secondaryStreamLoader = undefined; this.segmentStorage = undefined; - this.bandwidthApproximator.destroy(); this.manifestResponseUrl = undefined; } @@ -145,7 +144,7 @@ export class Core { manifestResponseUrl, segment, this.settings, - this.bandwidthApproximator, + this.bandwidthCalculator, this.segmentStorage, this.eventHandlers ); diff --git a/packages/p2p-media-loader-core/src/hybrid-loader.ts b/packages/p2p-media-loader-core/src/hybrid-loader.ts index c18745ba..783e232b 100644 --- a/packages/p2p-media-loader-core/src/hybrid-loader.ts +++ b/packages/p2p-media-loader-core/src/hybrid-loader.ts @@ -30,7 +30,7 @@ export class HybridLoader { private streamManifestUrl: string, requestedSegment: Segment, private readonly settings: Settings, - private readonly bandwidthApproximator: BandwidthCalculator, + private readonly bandwidthCalculator: BandwidthCalculator, private readonly segmentStorage: SegmentsMemoryStorage, private readonly eventHandlers?: Pick ) { @@ -40,7 +40,7 @@ export class HybridLoader { this.segmentAvgDuration = StreamUtils.getSegmentAvgDuration(activeStream); this.requests = new RequestsContainer( this.requestProcessQueueMicrotask, - this.bandwidthApproximator, + this.bandwidthCalculator, this.playback, this.settings ); @@ -94,7 +94,7 @@ export class HybridLoader { if (data) { callbacks.onSuccess({ data, - bandwidth: this.bandwidthApproximator.getBandwidth(), + bandwidth: this.bandwidthCalculator.getBandwidthForLastNSeconds(3), }); } } else { @@ -344,7 +344,7 @@ export class HybridLoader { queue: QueueUtils.QueueItem[], segment: Segment ): boolean { - for (const { segment: itemSegment } of arrayBackwards(queue)) { + for (const { segment: itemSegment } of Utils.arrayBackwards(queue)) { if (itemSegment === segment) break; const request = this.requests.get(itemSegment); if (request?.type === "http" && request.status === "loading") { @@ -359,7 +359,7 @@ export class HybridLoader { queue: QueueUtils.QueueItem[], segment: Segment ): boolean { - for (const { segment: itemSegment } of arrayBackwards(queue)) { + for (const { segment: itemSegment } of Utils.arrayBackwards(queue)) { if (itemSegment === segment) break; const request = this.requests.get(itemSegment); if (request?.type === "p2p" && request.status === "loading") { @@ -403,9 +403,3 @@ export class HybridLoader { this.logger.destroy(); } } - -function* arrayBackwards(arr: T[]) { - for (let i = arr.length - 1; i >= 0; i--) { - yield arr[i]; - } -} diff --git a/packages/p2p-media-loader-core/src/p2p/tracker-client.ts b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts index 6f862655..625b1fe4 100644 --- a/packages/p2p-media-loader-core/src/p2p/tracker-client.ts +++ b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts @@ -31,8 +31,9 @@ export class P2PTrackerClient { private readonly settings: Settings ) { const { string: peerId, bytes: peerIdBytes } = PeerUtil.generatePeerId(); - const { bytes: streamIdBytes, string: streamHash } = - PeerUtil.getStreamHash(streamId); + const { bytes: streamIdBytes, string: streamHash } = PeerUtil.getStreamHash( + streamId + "a" + ); this.peerId = peerId; this.streamShortId = LoggerUtils.getStreamString(stream); diff --git a/packages/p2p-media-loader-core/src/request-container.ts b/packages/p2p-media-loader-core/src/request-container.ts index 07c9116f..b0bf517b 100644 --- a/packages/p2p-media-loader-core/src/request-container.ts +++ b/packages/p2p-media-loader-core/src/request-container.ts @@ -7,7 +7,7 @@ export class RequestsContainer { constructor( private readonly requestProcessQueueCallback: () => void, - private readonly bandwidthApproximator: BandwidthCalculator, + private readonly bandwidthCalculator: BandwidthCalculator, private readonly playback: Playback, private readonly settings: Settings ) {} @@ -44,7 +44,7 @@ export class RequestsContainer { request = new Request( segment, this.requestProcessQueueCallback, - this.bandwidthApproximator, + this.bandwidthCalculator, this.playback, this.settings ); diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index 4e7fa428..c82717fa 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -73,7 +73,7 @@ export class Request { constructor( readonly segment: Segment, private readonly requestProcessQueueCallback: () => void, - private readonly bandwidthApproximator: BandwidthCalculator, + private readonly bandwidthCalculator: BandwidthCalculator, private readonly playback: Playback, private readonly settings: StreamUtils.PlaybackTimeWindowsSettings ) { @@ -179,7 +179,7 @@ export class Request { loadedBytes: 0, startTimestamp: performance.now(), }; - this.bandwidthApproximator.addLoading(this.progress); + this.bandwidthCalculator.startLoading(); const { notReceivingBytesTimeoutMs, abort } = controls; this._abortRequestCallback = abort; @@ -207,10 +207,11 @@ export class Request { resolveEngineCallbacksSuccessfully() { if (!this.finalData) return; - this._engineCallbacks?.onSuccess({ - data: this.finalData, - bandwidth: this.bandwidthApproximator.getBandwidth(), - }); + const bandwidth = this.bandwidthCalculator.getBandwidthForLastNSeconds(3); + const bandwidth6 = this.bandwidthCalculator.getBandwidthForLastNSeconds(6); + console.log("bandwidth", bandwidth / 1000); + console.log("bandwidth6", bandwidth6 / 1000); + this._engineCallbacks?.onSuccess({ data: this.finalData, bandwidth }); this._engineCallbacks = undefined; } @@ -236,6 +237,7 @@ export class Request { this._abortRequestCallback = undefined; this.currentAttempt = undefined; this.notReceivingBytesTimeout.clear(); + this.bandwidthCalculator.stopLoading(); } private abortOnTimeout = () => { @@ -252,6 +254,7 @@ export class Request { error, }); this.notReceivingBytesTimeout.clear(); + this.bandwidthCalculator.stopLoading(); this.requestProcessQueueCallback(); }; @@ -266,6 +269,7 @@ export class Request { error, }); this.notReceivingBytesTimeout.clear(); + this.bandwidthCalculator.stopLoading(); this.requestProcessQueueCallback(); }; @@ -273,12 +277,12 @@ export class Request { this.throwErrorIfNotLoadingStatus(); if (!this.currentAttempt) return; + this.bandwidthCalculator.stopLoading(); this.notReceivingBytesTimeout.clear(); this.finalData = Utils.joinChunks(this.bytes); this.setStatus("succeed"); this._totalBytes = this._loadedBytes; - this.resolveEngineCallbacksSuccessfully(); this.logger( `${this.currentAttempt.type} ${this.segment.externalId} succeed` ); @@ -290,6 +294,7 @@ export class Request { if (!this.currentAttempt || !this.progress) return; this.notReceivingBytesTimeout.restart(); + this.bandwidthCalculator.addBytes(chunk.length); this.bytes.push(chunk); this.progress.lastLoadedChunkTimestamp = performance.now(); this.progress.loadedBytes += chunk.length; diff --git a/packages/p2p-media-loader-core/src/utils/utils.ts b/packages/p2p-media-loader-core/src/utils/utils.ts index d5ab0969..862c1577 100644 --- a/packages/p2p-media-loader-core/src/utils/utils.ts +++ b/packages/p2p-media-loader-core/src/utils/utils.ts @@ -56,3 +56,9 @@ export function hexToUtf8(hexString: string) { const decoder = new TextDecoder(); return decoder.decode(bytes); } + +export function* arrayBackwards(arr: T[]) { + for (let i = arr.length - 1; i >= 0; i--) { + yield arr[i]; + } +} From f354ff14c4e89ad7a3fae3be106b0faec63789d4 Mon Sep 17 00:00:00 2001 From: Igor Zolotarenko Date: Fri, 29 Dec 2023 20:41:30 +0200 Subject: [PATCH 4/5] Git use another approach. Suppress loading intervals together. --- .../src/bandwidth-calculator.ts | 76 +++++++++++-------- .../p2p-media-loader-core/src/linked-map.ts | 14 ---- packages/p2p-media-loader-core/src/request.ts | 2 - 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts index 8cae886a..11807c9a 100644 --- a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts +++ b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts @@ -1,10 +1,14 @@ import { arrayBackwards } from "./utils/utils"; +type Interval = { start: number; end?: number }; + +const CLEAR_THRESHOLD_MS = 3000; + export class BandwidthCalculator { private simultaneousLoadingsCount = 0; private readonly bytes: number[] = []; private readonly timestamps: number[] = []; - private loadingIntervals: { start: number; end?: number }[] = []; + private loadingIntervals: Interval[] = []; addBytes(bytesLength: number) { this.bytes.push(bytesLength); @@ -30,45 +34,57 @@ export class BandwidthCalculator { // in bits per second getBandwidthForLastNSeconds(seconds: number) { - this.clearStale(); - const { bytes, timestamps, loadingIntervals } = this; - const samplesLength = bytes.length; + const { bytes, timestamps } = this; + if (!bytes.length) return 0; + const milliseconds = seconds * 1000; const now = performance.now(); - const threshold = now - seconds * 1000; + let totalTime = 0; - let loadedBytes = 0; - for (let i = samplesLength - 1; i >= 0; i--) { - if (timestamps[i] < threshold) break; - loadedBytes += bytes[i]; + let firstIntervalStart = Number.POSITIVE_INFINITY; + for (const { start, end = now } of arrayBackwards(this.loadingIntervals)) { + const duration = end - start; + if (totalTime + duration < milliseconds) { + totalTime += duration; + firstIntervalStart = start; + continue; + } + firstIntervalStart = end - (milliseconds - totalTime); + totalTime = milliseconds; + break; } + if (totalTime === 0) return 0; - let clearLoadingTime = 0; - for (const { start, end } of arrayBackwards(loadingIntervals)) { - if (start < threshold && end !== undefined && end < threshold) break; - const from = Math.max(start, threshold); - const to = end ?? now; - clearLoadingTime += to - from; + let totalBytes = 0; + for (let i = bytes.length - 1; i >= 0; i--) { + if (timestamps[i] < firstIntervalStart) break; + totalBytes += bytes[i]; } - if (clearLoadingTime === 0) return 0; - return (loadedBytes * 8000) / clearLoadingTime; + return (totalBytes * 8000) / totalTime; } - private clearStale() { - const { timestamps, bytes, loadingIntervals } = this; - const samplesLength = bytes.length; - const threshold = performance.now() - 15000; + clearStale() { + if (!this.loadingIntervals.length) return; + const now = performance.now(); + let totalTime = 0; - let count = 0; - while (count < samplesLength && timestamps[count] < threshold) count++; - bytes.splice(0, count); - timestamps.splice(0, count); + let intervalsToLeave = 0; + for (const { start, end = now } of arrayBackwards(this.loadingIntervals)) { + const duration = end - start; + intervalsToLeave++; + if (totalTime + duration >= CLEAR_THRESHOLD_MS) break; + totalTime += duration; + } + const intervalsToRemove = this.loadingIntervals.length - intervalsToLeave; + this.loadingIntervals.splice(0, intervalsToRemove); - count = 0; - for (const { start, end } of loadingIntervals) { - if (!(start < threshold && end !== undefined && end <= threshold)) break; - count++; + const { start: firstIntervalStart } = this.loadingIntervals[0]; + let samplesToRemove = 0; + for (const timestamp of this.timestamps) { + if (timestamp >= firstIntervalStart) break; + samplesToRemove++; } - loadingIntervals.splice(0, count); + this.bytes.splice(0, samplesToRemove); + this.timestamps.splice(0, samplesToRemove); } } diff --git a/packages/p2p-media-loader-core/src/linked-map.ts b/packages/p2p-media-loader-core/src/linked-map.ts index 3af745ec..78ee364d 100644 --- a/packages/p2p-media-loader-core/src/linked-map.ts +++ b/packages/p2p-media-loader-core/src/linked-map.ts @@ -9,14 +9,6 @@ export class LinkedMap { private _first?: LinkedObject; private _last?: LinkedObject; - get first() { - return this._first?.item; - } - - get last() { - return this._last?.item; - } - get size() { return this.map.size; } @@ -53,12 +45,6 @@ export class LinkedMap { this.map.delete(key); } - clear() { - this._first = undefined; - this._last = undefined; - this.map.clear(); - } - *values(key?: K) { let value = key ? this.map.get(key) : this._first; if (value === undefined) return; diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index c82717fa..5875b8dc 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -208,9 +208,7 @@ export class Request { resolveEngineCallbacksSuccessfully() { if (!this.finalData) return; const bandwidth = this.bandwidthCalculator.getBandwidthForLastNSeconds(3); - const bandwidth6 = this.bandwidthCalculator.getBandwidthForLastNSeconds(6); console.log("bandwidth", bandwidth / 1000); - console.log("bandwidth6", bandwidth6 / 1000); this._engineCallbacks?.onSuccess({ data: this.finalData, bandwidth }); this._engineCallbacks = undefined; } From 91249e0f7b4832a207faa3b694bdb83be9bc0f7b Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 2 Jan 2024 12:57:56 +0200 Subject: [PATCH 5/5] Use time shift instead of loading intervals. --- .../src/bandwidth-calculator.ts | 77 ++++++------------- .../src/p2p/tracker-client.ts | 5 +- packages/p2p-media-loader-core/src/request.ts | 7 -- 3 files changed, 27 insertions(+), 62 deletions(-) diff --git a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts index 11807c9a..d5657ce0 100644 --- a/packages/p2p-media-loader-core/src/bandwidth-calculator.ts +++ b/packages/p2p-media-loader-core/src/bandwidth-calculator.ts @@ -1,89 +1,62 @@ -import { arrayBackwards } from "./utils/utils"; - -type Interval = { start: number; end?: number }; - const CLEAR_THRESHOLD_MS = 3000; export class BandwidthCalculator { private simultaneousLoadingsCount = 0; private readonly bytes: number[] = []; private readonly timestamps: number[] = []; - private loadingIntervals: Interval[] = []; + private noLoadingsTotalTime = 0; + private allLoadingsStoppedTimestamp = 0; - addBytes(bytesLength: number) { + addBytes(bytesLength: number, now = performance.now()) { this.bytes.push(bytesLength); - this.timestamps.push(performance.now()); + this.timestamps.push(now - this.noLoadingsTotalTime); } - startLoading() { + startLoading(now = performance.now()) { this.clearStale(); if (this.simultaneousLoadingsCount === 0) { - this.loadingIntervals.push({ start: performance.now() }); + this.noLoadingsTotalTime += now - this.allLoadingsStoppedTimestamp; } this.simultaneousLoadingsCount++; } - stopLoading() { - this.clearStale(); + // in bits per second + stopLoading(now = performance.now()) { if (this.simultaneousLoadingsCount <= 0) return; this.simultaneousLoadingsCount--; if (this.simultaneousLoadingsCount !== 0) return; - this.loadingIntervals[this.loadingIntervals.length - 1].end = - performance.now(); + this.allLoadingsStoppedTimestamp = now; } - // in bits per second getBandwidthForLastNSeconds(seconds: number) { - const { bytes, timestamps } = this; - if (!bytes.length) return 0; + if (!this.timestamps.length) return 0; const milliseconds = seconds * 1000; - const now = performance.now(); - let totalTime = 0; - - let firstIntervalStart = Number.POSITIVE_INFINITY; - for (const { start, end = now } of arrayBackwards(this.loadingIntervals)) { - const duration = end - start; - if (totalTime + duration < milliseconds) { - totalTime += duration; - firstIntervalStart = start; - continue; - } - firstIntervalStart = end - (milliseconds - totalTime); - totalTime = milliseconds; - break; - } - if (totalTime === 0) return 0; - + const lastItemTimestamp = this.timestamps[this.timestamps.length - 1]; + let lastCountedTimestamp = lastItemTimestamp; + const threshold = lastItemTimestamp - milliseconds; let totalBytes = 0; - for (let i = bytes.length - 1; i >= 0; i--) { - if (timestamps[i] < firstIntervalStart) break; - totalBytes += bytes[i]; + + for (let i = this.bytes.length - 1; i >= 0; i--) { + const timestamp = this.timestamps[i]; + if (timestamp < threshold) break; + lastCountedTimestamp = timestamp; + totalBytes += this.bytes[i]; } - return (totalBytes * 8000) / totalTime; + return (totalBytes * 8000) / (lastItemTimestamp - lastCountedTimestamp); } clearStale() { - if (!this.loadingIntervals.length) return; - const now = performance.now(); - let totalTime = 0; + if (!this.timestamps.length) return; + const threshold = + this.timestamps[this.timestamps.length - 1] - CLEAR_THRESHOLD_MS; - let intervalsToLeave = 0; - for (const { start, end = now } of arrayBackwards(this.loadingIntervals)) { - const duration = end - start; - intervalsToLeave++; - if (totalTime + duration >= CLEAR_THRESHOLD_MS) break; - totalTime += duration; - } - const intervalsToRemove = this.loadingIntervals.length - intervalsToLeave; - this.loadingIntervals.splice(0, intervalsToRemove); - - const { start: firstIntervalStart } = this.loadingIntervals[0]; let samplesToRemove = 0; for (const timestamp of this.timestamps) { - if (timestamp >= firstIntervalStart) break; + if (timestamp > threshold) break; samplesToRemove++; } + this.bytes.splice(0, samplesToRemove); this.timestamps.splice(0, samplesToRemove); } diff --git a/packages/p2p-media-loader-core/src/p2p/tracker-client.ts b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts index 625b1fe4..6f862655 100644 --- a/packages/p2p-media-loader-core/src/p2p/tracker-client.ts +++ b/packages/p2p-media-loader-core/src/p2p/tracker-client.ts @@ -31,9 +31,8 @@ export class P2PTrackerClient { private readonly settings: Settings ) { const { string: peerId, bytes: peerIdBytes } = PeerUtil.generatePeerId(); - const { bytes: streamIdBytes, string: streamHash } = PeerUtil.getStreamHash( - streamId + "a" - ); + const { bytes: streamIdBytes, string: streamHash } = + PeerUtil.getStreamHash(streamId); this.peerId = peerId; this.streamShortId = LoggerUtils.getStreamString(stream); diff --git a/packages/p2p-media-loader-core/src/request.ts b/packages/p2p-media-loader-core/src/request.ts index 5875b8dc..f7ec5b7a 100644 --- a/packages/p2p-media-loader-core/src/request.ts +++ b/packages/p2p-media-loader-core/src/request.ts @@ -208,7 +208,6 @@ export class Request { resolveEngineCallbacksSuccessfully() { if (!this.finalData) return; const bandwidth = this.bandwidthCalculator.getBandwidthForLastNSeconds(3); - console.log("bandwidth", bandwidth / 1000); this._engineCallbacks?.onSuccess({ data: this.finalData, bandwidth }); this._engineCallbacks = undefined; } @@ -323,11 +322,6 @@ export class Request { class FailedRequestAttempts { private attempts: Required[] = []; - private _lastClearTimestamp = performance.now(); - - get lastClearTimestamp() { - return this._lastClearTimestamp; - } add(attempt: Required) { this.attempts.push(attempt); @@ -346,7 +340,6 @@ class FailedRequestAttempts { clear() { this.attempts = []; - this._lastClearTimestamp = performance.now(); } }