Skip to content

Commit 1be6eb2

Browse files
Bandwidth algorithm. (#325)
* Add ability to get bandwidth for last N not spliced seconds. * Get from engines additional stream info (is live, active stream bandwidth) * Pass multiple bandwidth calculators of different types to hybrid loader and requests. * Return generator from generateQueue function. * Add shaka segment index reading optimisation. * Revise queue generation algorithm. * Fix issue with load stalling after abrupt position changing. * Fix issue with engine request has been already settled. * Enhance bandwidth algorithm. * Fix lint errors. * Make CLEAR_THRESHOLD_MS a class field * Rename methods * Remove LinkedMap class * Remove redundant comment * Rename downloadProgressRatio * Rename to queueDownloadRatio * Move demo to shorter folder * Open browser on demo start * Update dependencies * Fix GitHub Actions --------- Co-authored-by: Igor Zolotarenko <zolotarenko.i.r@gmail.com> Co-authored-by: Andriy Lysnevych <andriy.lysnevych@novage.com.ua>
1 parent f9382a6 commit 1be6eb2

37 files changed

+828
-930
lines changed

.github/workflows/check-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ jobs:
2929
- run: pnpm lint
3030
- run: pnpm build
3131
- run: npx tsc
32-
working-directory: ./p2p-media-loader-demo
32+
working-directory: ./demo

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pnpm-debug.log*
88
lerna-debug.log*
99

1010
node_modules
11-
/p2p-media-loader-demo/dist
11+
/demo/dist
1212
/packages/*/dist
1313
/packages/*/lib
1414
/packages/*/build
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

p2p-media-loader-demo/package.json renamed to demo/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
},
2222
"devDependencies": {
2323
"@types/dplayer": "^1.25.5",
24-
"@types/react": "^18.2.45",
24+
"@types/react": "^18.2.47",
2525
"@types/react-dom": "^18.2.18",
2626
"@vitejs/plugin-react": "^4.2.1",
2727
"eslint-plugin-react-hooks": "^4.6.0",
2828
"eslint-plugin-react-refresh": "^0.4.5",
29-
"vite-plugin-node-polyfills": "^0.18.0"
29+
"vite-plugin-node-polyfills": "^0.19.0"
3030
}
3131
}

p2p-media-loader-demo/src/App.tsx renamed to demo/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function App() {
133133

134134
const initHlsDPlayer = (url: string) => {
135135
if (!hlsEngine.current) return;
136-
const engine = hlsEngine.current!;
136+
const engine = hlsEngine.current;
137137
const player = new DPlayer({
138138
container: containerRef.current,
139139
video: {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

p2p-media-loader-demo/vite.config.ts renamed to demo/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import react from "@vitejs/plugin-react";
33
import { nodePolyfills } from "vite-plugin-node-polyfills";
44

55
export default defineConfig({
6+
server: { open: true, host: true },
67
plugins: [nodePolyfills(), react()],
78
});

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
},
1616
"devDependencies": {
1717
"@types/debug": "^4.1.12",
18-
"@typescript-eslint/eslint-plugin": "^6.15.0",
19-
"@typescript-eslint/parser": "^6.15.0",
18+
"@typescript-eslint/eslint-plugin": "^6.18.1",
19+
"@typescript-eslint/parser": "^6.18.1",
2020
"eslint": "^8.56.0",
21-
"eslint-plugin-prettier": "^5.1.2",
21+
"eslint-plugin-prettier": "^5.1.3",
2222
"prettier": "^3.1.1",
2323
"rimraf": "^5.0.5",
2424
"typescript": "^5.3.3",
25-
"vite": "^5.0.10"
25+
"vite": "^5.0.11"
2626
},
2727
"dependencies": {
2828
"debug": "^4.3.4"
Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
const CLEAR_THRESHOLD_MS = 3000;
2-
31
export class BandwidthCalculator {
42
private simultaneousLoadingsCount = 0;
53
private readonly bytes: number[] = [];
4+
private readonly loadingOnlyTimestamps: number[] = [];
65
private readonly timestamps: number[] = [];
76
private noLoadingsTotalTime = 0;
87
private allLoadingsStoppedTimestamp = 0;
98

9+
constructor(private readonly clearThresholdMs = 10000) {}
10+
1011
addBytes(bytesLength: number, now = performance.now()) {
1112
this.bytes.push(bytesLength);
12-
this.timestamps.push(now - this.noLoadingsTotalTime);
13+
this.loadingOnlyTimestamps.push(now - this.noLoadingsTotalTime);
14+
this.timestamps.push(now);
1315
}
1416

1517
startLoading(now = performance.now()) {
@@ -28,36 +30,68 @@ export class BandwidthCalculator {
2830
this.allLoadingsStoppedTimestamp = now;
2931
}
3032

31-
getBandwidthForLastNSeconds(seconds: number) {
32-
if (!this.timestamps.length) return 0;
33+
getBandwidthLoadingOnly(
34+
seconds: number,
35+
ignoreThresholdTimestamp = Number.NEGATIVE_INFINITY
36+
) {
37+
if (!this.loadingOnlyTimestamps.length) return 0;
3338
const milliseconds = seconds * 1000;
34-
const lastItemTimestamp = this.timestamps[this.timestamps.length - 1];
39+
const lastItemTimestamp =
40+
this.loadingOnlyTimestamps[this.loadingOnlyTimestamps.length - 1];
3541
let lastCountedTimestamp = lastItemTimestamp;
3642
const threshold = lastItemTimestamp - milliseconds;
3743
let totalBytes = 0;
3844

3945
for (let i = this.bytes.length - 1; i >= 0; i--) {
40-
const timestamp = this.timestamps[i];
41-
if (timestamp < threshold) break;
46+
const timestamp = this.loadingOnlyTimestamps[i];
47+
if (
48+
timestamp < threshold ||
49+
this.timestamps[i] < ignoreThresholdTimestamp
50+
) {
51+
break;
52+
}
4253
lastCountedTimestamp = timestamp;
4354
totalBytes += this.bytes[i];
4455
}
4556

4657
return (totalBytes * 8000) / (lastItemTimestamp - lastCountedTimestamp);
4758
}
4859

60+
getBandwidth(
61+
seconds: number,
62+
ignoreThresholdTimestamp = Number.NEGATIVE_INFINITY,
63+
now = performance.now()
64+
) {
65+
if (!this.timestamps.length) return 0;
66+
const milliseconds = seconds * 1000;
67+
const threshold = now - milliseconds;
68+
let lastCountedTimestamp = now;
69+
let totalBytes = 0;
70+
71+
for (let i = this.bytes.length - 1; i >= 0; i--) {
72+
const timestamp = this.timestamps[i];
73+
if (timestamp < threshold || timestamp < ignoreThresholdTimestamp) break;
74+
lastCountedTimestamp = timestamp;
75+
totalBytes += this.bytes[i];
76+
}
77+
78+
return (totalBytes * 8000) / (now - lastCountedTimestamp);
79+
}
80+
4981
clearStale() {
50-
if (!this.timestamps.length) return;
82+
if (!this.loadingOnlyTimestamps.length) return;
5183
const threshold =
52-
this.timestamps[this.timestamps.length - 1] - CLEAR_THRESHOLD_MS;
84+
this.loadingOnlyTimestamps[this.loadingOnlyTimestamps.length - 1] -
85+
this.clearThresholdMs;
5386

5487
let samplesToRemove = 0;
55-
for (const timestamp of this.timestamps) {
88+
for (const timestamp of this.loadingOnlyTimestamps) {
5689
if (timestamp > threshold) break;
5790
samplesToRemove++;
5891
}
5992

6093
this.bytes.splice(0, samplesToRemove);
94+
this.loadingOnlyTimestamps.splice(0, samplesToRemove);
6195
this.timestamps.splice(0, samplesToRemove);
6296
}
6397
}

packages/p2p-media-loader-core/src/core.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {
66
Settings,
77
SegmentBase,
88
CoreEventHandlers,
9+
BandwidthCalculators,
10+
StreamDetails,
911
} from "./types";
1012
import * as StreamUtils from "./utils/stream";
11-
import { LinkedMap } from "./linked-map";
1213
import { BandwidthCalculator } from "./bandwidth-calculator";
1314
import { EngineCallbacks } from "./requests/engine-request";
1415
import { SegmentsMemoryStorage } from "./segments-storage";
@@ -31,10 +32,17 @@ export class Core<TStream extends Stream = Stream> {
3132
httpErrorRetries: 3,
3233
p2pErrorRetries: 3,
3334
};
34-
private readonly bandwidthCalculator = new BandwidthCalculator();
35+
private readonly bandwidthCalculators: BandwidthCalculators = {
36+
all: new BandwidthCalculator(),
37+
http: new BandwidthCalculator(),
38+
};
3539
private segmentStorage?: SegmentsMemoryStorage;
3640
private mainStreamLoader?: HybridLoader;
3741
private secondaryStreamLoader?: HybridLoader;
42+
private streamDetails: StreamDetails = {
43+
isLive: false,
44+
activeLevelBitrate: 0,
45+
};
3846

3947
constructor(private readonly eventHandlers?: CoreEventHandlers) {}
4048

@@ -58,7 +66,7 @@ export class Core<TStream extends Stream = Stream> {
5866
if (this.streams.has(stream.localId)) return;
5967
this.streams.set(stream.localId, {
6068
...stream,
61-
segments: new LinkedMap<string, Segment>(),
69+
segments: new Map<string, Segment>(),
6270
});
6371
}
6472

@@ -72,7 +80,7 @@ export class Core<TStream extends Stream = Stream> {
7280

7381
addSegments?.forEach((s) => {
7482
const segment = { ...s, stream };
75-
stream.segments.addToEnd(segment.localId, segment);
83+
stream.segments.set(segment.localId, segment);
7684
});
7785
removeSegmentIds?.forEach((id) => stream.segments.delete(id));
7886
this.mainStreamLoader?.updateStream(stream);
@@ -105,6 +113,18 @@ export class Core<TStream extends Stream = Stream> {
105113
this.secondaryStreamLoader?.updatePlayback(position, rate);
106114
}
107115

116+
setActiveLevelBitrate(bitrate: number) {
117+
if (bitrate !== this.streamDetails.activeLevelBitrate) {
118+
this.streamDetails.activeLevelBitrate = bitrate;
119+
this.mainStreamLoader?.notifyLevelChanged();
120+
this.secondaryStreamLoader?.notifyLevelChanged();
121+
}
122+
}
123+
124+
setIsLive(isLive: boolean) {
125+
this.streamDetails.isLive = isLive;
126+
}
127+
108128
destroy(): void {
109129
this.streams.clear();
110130
this.mainStreamLoader?.destroy();
@@ -114,6 +134,7 @@ export class Core<TStream extends Stream = Stream> {
114134
this.secondaryStreamLoader = undefined;
115135
this.segmentStorage = undefined;
116136
this.manifestResponseUrl = undefined;
137+
this.streamDetails = { isLive: false, activeLevelBitrate: 0 };
117138
}
118139

119140
private identifySegment(segmentId: string): Segment {
@@ -143,8 +164,9 @@ export class Core<TStream extends Stream = Stream> {
143164
return new HybridLoader(
144165
manifestResponseUrl,
145166
segment,
167+
this.streamDetails as Required<StreamDetails>,
146168
this.settings,
147-
this.bandwidthCalculator,
169+
this.bandwidthCalculators,
148170
this.segmentStorage,
149171
this.eventHandlers
150172
);

0 commit comments

Comments
 (0)