rx-player 4.5.0-dev.2026012302 → 4.5.0-dev.2026021900
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/commonjs/compat/should_seek_at_zero.d.ts +12 -0
- package/dist/commonjs/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/commonjs/compat/should_seek_at_zero.js +23 -0
- package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
- package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
- package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
- package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/commonjs/default_config.d.ts +5 -0
- package/dist/commonjs/default_config.d.ts.map +1 -1
- package/dist/commonjs/default_config.js +5 -0
- package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +27 -3
- package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/commonjs/manifest/classes/index.d.ts +2 -1
- package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.js +2 -2
- package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/commonjs/utils/base64.d.ts +15 -1
- package/dist/commonjs/utils/base64.d.ts.map +1 -1
- package/dist/commonjs/utils/base64.js +6 -0
- package/dist/commonjs/utils/ranges.d.ts +1 -1
- package/dist/commonjs/utils/ranges.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
- package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/es2017/compat/should_seek_at_zero.js +20 -0
- package/dist/es2017/core/entry/FreezeResolver.js +1 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
- package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
- package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/es2017/default_config.d.ts +5 -0
- package/dist/es2017/default_config.d.ts.map +1 -1
- package/dist/es2017/default_config.js +5 -0
- package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +27 -3
- package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/es2017/manifest/classes/index.d.ts +2 -1
- package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.js +1 -1
- package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/es2017/utils/base64.d.ts +15 -1
- package/dist/es2017/utils/base64.d.ts.map +1 -1
- package/dist/es2017/utils/base64.js +6 -0
- package/dist/es2017/utils/ranges.d.ts +1 -1
- package/dist/es2017/utils/ranges.js +1 -1
- package/dist/mpd-parser.wasm +0 -0
- package/dist/worker.js +6 -6
- package/package.json +9 -3
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/should_seek_at_zero.ts +23 -0
- package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +790 -0
- package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
- package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +285 -0
- package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +247 -0
- package/src/core/entry/FreezeResolver.ts +1 -1
- package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
- package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +868 -0
- package/src/core/entry/content_time_boundaries_observer.ts +10 -0
- package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +592 -0
- package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
- package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
- package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
- package/src/core/stream/representation/representation_stream.ts +11 -49
- package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
- package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
- package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
- package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
- package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
- package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
- package/src/default_config.ts +6 -0
- package/src/main_thread/api/public_api.ts +32 -3
- package/src/main_thread/init/directfile_content_initializer.ts +26 -1
- package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
- package/src/manifest/classes/index.ts +2 -0
- package/src/manifest/classes/representation.ts +2 -1
- package/src/manifest/classes/representation_index/types.ts +2 -2
- package/src/utils/base64.ts +30 -1
- package/src/utils/ranges.ts +1 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import type { IRepresentation } from "../../../manifest";
|
|
3
|
+
import GuessBasedChooser from "../guess_based_chooser";
|
|
4
|
+
import { ABRAlgorithmType } from "../utils/last_estimate_storage";
|
|
5
|
+
import type { IRequestInfo } from "../utils/pending_requests_store";
|
|
6
|
+
import type { IRepresentationMaintainabilityScore } from "../utils/representation_score_calculator";
|
|
7
|
+
import { ScoreConfidenceLevel } from "../utils/representation_score_calculator";
|
|
8
|
+
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
11
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
12
|
+
|
|
13
|
+
const mocks = vi.hoisted(() => ({
|
|
14
|
+
estimateRequestBandwidth: vi.fn(),
|
|
15
|
+
getMonotonicTimeStamp: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock("../../../log", () => ({
|
|
19
|
+
default: {
|
|
20
|
+
debug: vi.fn(),
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock("../../../utils/array_find_index", () => ({
|
|
26
|
+
// eslint-disable-next-line no-restricted-properties
|
|
27
|
+
default: vi.fn((arr, predicate) => arr.findIndex(predicate)),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("../../../utils/monotonic_timestamp", () => ({
|
|
31
|
+
default: mocks.getMonotonicTimeStamp,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("../network_analyzer", () => ({
|
|
35
|
+
estimateRequestBandwidth: mocks.estimateRequestBandwidth,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Helper functions
|
|
39
|
+
function createRepresentation(id: string, bitrate: number): IRepresentation {
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
bitrate,
|
|
43
|
+
} as IRepresentation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createScoreData(
|
|
47
|
+
score: number,
|
|
48
|
+
confidenceLevel: ScoreConfidenceLevel,
|
|
49
|
+
): IRepresentationMaintainabilityScore {
|
|
50
|
+
return { score, confidenceLevel };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createRequest(
|
|
54
|
+
representationId: string,
|
|
55
|
+
segmentDuration: number,
|
|
56
|
+
requestTimestamp: number,
|
|
57
|
+
isInit = false,
|
|
58
|
+
): IRequestInfo {
|
|
59
|
+
return {
|
|
60
|
+
content: {
|
|
61
|
+
representation: { id: representationId } as IRepresentation,
|
|
62
|
+
segment: {
|
|
63
|
+
duration: segmentDuration,
|
|
64
|
+
isInit,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
requestTimestamp,
|
|
68
|
+
} as IRequestInfo;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("GuessBasedChooser", () => {
|
|
72
|
+
let mockScoreCalculator: any;
|
|
73
|
+
let mockPrevEstimate: any;
|
|
74
|
+
let representations: IRepresentation[];
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
vi.clearAllMocks();
|
|
78
|
+
mocks.getMonotonicTimeStamp.mockReturnValue(10000);
|
|
79
|
+
|
|
80
|
+
// Setup mock representations (sorted by bitrate ascending)
|
|
81
|
+
representations = [
|
|
82
|
+
createRepresentation("rep-1", 100000),
|
|
83
|
+
createRepresentation("rep-2", 500000),
|
|
84
|
+
createRepresentation("rep-3", 1000000),
|
|
85
|
+
createRepresentation("rep-4", 2000000),
|
|
86
|
+
createRepresentation("rep-5", 3000000),
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
mockScoreCalculator = {
|
|
90
|
+
getEstimate: vi.fn(),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
mockPrevEstimate = {
|
|
94
|
+
representation: null,
|
|
95
|
+
algorithmType: ABRAlgorithmType.GuessBased,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("constructor", () => {
|
|
100
|
+
it("should initialize with correct default values", () => {
|
|
101
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
102
|
+
expect(chooser).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("getGuess", () => {
|
|
107
|
+
describe("when last chosen representation is null", () => {
|
|
108
|
+
it("should return null - nothing to base guess on", () => {
|
|
109
|
+
mockPrevEstimate.representation = null;
|
|
110
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
111
|
+
|
|
112
|
+
const result = chooser.getGuess(
|
|
113
|
+
representations,
|
|
114
|
+
{ bufferGap: 5, speed: 1 },
|
|
115
|
+
representations[2],
|
|
116
|
+
1500000,
|
|
117
|
+
[],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(result).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("when incomingBestBitrate > lastChosenRep.bitrate", () => {
|
|
125
|
+
it("should return null - ABR estimates are already superior", () => {
|
|
126
|
+
mockPrevEstimate.representation = representations[1]; // 500000
|
|
127
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
128
|
+
|
|
129
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
130
|
+
|
|
131
|
+
const result = chooser.getGuess(
|
|
132
|
+
representations,
|
|
133
|
+
{ bufferGap: 5, speed: 1 },
|
|
134
|
+
representations[1],
|
|
135
|
+
1000000, // Higher than last chosen
|
|
136
|
+
[],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(result).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should reset consecutive wrong guesses when algorithm was GuessBased", () => {
|
|
143
|
+
mockPrevEstimate.representation = representations[1];
|
|
144
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
145
|
+
|
|
146
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
147
|
+
|
|
148
|
+
// First call should reset counter
|
|
149
|
+
chooser.getGuess(
|
|
150
|
+
representations,
|
|
151
|
+
{ bufferGap: 5, speed: 1 },
|
|
152
|
+
representations[1],
|
|
153
|
+
1000000,
|
|
154
|
+
[],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Subsequent calls should still work (counter was reset)
|
|
158
|
+
const result = chooser.getGuess(
|
|
159
|
+
representations,
|
|
160
|
+
{ bufferGap: 5, speed: 1 },
|
|
161
|
+
representations[1],
|
|
162
|
+
1000000,
|
|
163
|
+
[],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(result).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("when not in guessing mode yet", () => {
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
mockPrevEstimate.representation = representations[1];
|
|
173
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should return null when score data is undefined", () => {
|
|
177
|
+
mockScoreCalculator.getEstimate.mockReturnValue(undefined);
|
|
178
|
+
|
|
179
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
180
|
+
|
|
181
|
+
const result = chooser.getGuess(
|
|
182
|
+
representations,
|
|
183
|
+
{ bufferGap: 5, speed: 1 },
|
|
184
|
+
representations[1],
|
|
185
|
+
100000,
|
|
186
|
+
[],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(result).toBeNull();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should return next representation when conditions allow guessing higher", () => {
|
|
193
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
194
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
198
|
+
|
|
199
|
+
const result = chooser.getGuess(
|
|
200
|
+
representations,
|
|
201
|
+
{ bufferGap: 3, speed: 1 },
|
|
202
|
+
representations[1],
|
|
203
|
+
100000,
|
|
204
|
+
[],
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(result).toBe(representations[2]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should return null when buffer gap is too low to guess higher", () => {
|
|
211
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
212
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
216
|
+
|
|
217
|
+
const result = chooser.getGuess(
|
|
218
|
+
representations,
|
|
219
|
+
{ bufferGap: 1.5, speed: 1 }, // Below 2.5 threshold
|
|
220
|
+
representations[1],
|
|
221
|
+
100000,
|
|
222
|
+
[],
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(result).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should return null when confidence level is not HIGH", () => {
|
|
229
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
230
|
+
createScoreData(1.5, ScoreConfidenceLevel.LOW),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
234
|
+
|
|
235
|
+
const result = chooser.getGuess(
|
|
236
|
+
representations,
|
|
237
|
+
{ bufferGap: 3, speed: 1 },
|
|
238
|
+
representations[1],
|
|
239
|
+
100000,
|
|
240
|
+
[],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(result).toBeNull();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("should return null when score/speed ratio is too low", () => {
|
|
247
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
248
|
+
createScoreData(1.005, ScoreConfidenceLevel.HIGH),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
252
|
+
|
|
253
|
+
const result = chooser.getGuess(
|
|
254
|
+
representations,
|
|
255
|
+
{ bufferGap: 3, speed: 1 },
|
|
256
|
+
representations[1],
|
|
257
|
+
100000,
|
|
258
|
+
[],
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
expect(result).toBeNull();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should account for playback speed when checking score", () => {
|
|
265
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
266
|
+
createScoreData(2.0, ScoreConfidenceLevel.HIGH),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
270
|
+
|
|
271
|
+
const result = chooser.getGuess(
|
|
272
|
+
representations,
|
|
273
|
+
{ bufferGap: 3, speed: 2.0 }, // Speed 2x requires higher score
|
|
274
|
+
representations[1],
|
|
275
|
+
100000,
|
|
276
|
+
[],
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(result).toBeNull(); // 2.0 / 2.0 = 1.0, not > 1.01
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should return null when already at highest representation", () => {
|
|
283
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
284
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
288
|
+
|
|
289
|
+
const result = chooser.getGuess(
|
|
290
|
+
representations,
|
|
291
|
+
{ bufferGap: 3, speed: 1 },
|
|
292
|
+
representations[4], // Highest representation
|
|
293
|
+
100000,
|
|
294
|
+
[],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(result).toBeNull();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("when already in guessing mode", () => {
|
|
302
|
+
beforeEach(() => {
|
|
303
|
+
mockPrevEstimate.representation = representations[2]; // 1000000
|
|
304
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should take higher guess when conditions are met", () => {
|
|
308
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
309
|
+
createScoreData(2.0, ScoreConfidenceLevel.HIGH),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
313
|
+
|
|
314
|
+
const result = chooser.getGuess(
|
|
315
|
+
representations,
|
|
316
|
+
{ bufferGap: 3, speed: 1 },
|
|
317
|
+
representations[2],
|
|
318
|
+
1000000,
|
|
319
|
+
[],
|
|
320
|
+
);
|
|
321
|
+
expect(result).toBe(representations[3]);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("should take same guess when conditions are met with highest guess", () => {
|
|
325
|
+
mockPrevEstimate.representation = representations[4];
|
|
326
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
327
|
+
createScoreData(2.0, ScoreConfidenceLevel.HIGH),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
331
|
+
|
|
332
|
+
const result = chooser.getGuess(
|
|
333
|
+
representations,
|
|
334
|
+
{ bufferGap: 3, speed: 1 },
|
|
335
|
+
representations[2],
|
|
336
|
+
1000000,
|
|
337
|
+
[],
|
|
338
|
+
);
|
|
339
|
+
expect(result).toBe(representations[4]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should return last chosen representation when current differs", () => {
|
|
343
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
344
|
+
createScoreData(1.2, ScoreConfidenceLevel.HIGH),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
348
|
+
|
|
349
|
+
const result = chooser.getGuess(
|
|
350
|
+
representations,
|
|
351
|
+
{ bufferGap: 3, speed: 1 },
|
|
352
|
+
representations[1], // Different from last chosen
|
|
353
|
+
100000,
|
|
354
|
+
[],
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(result).toBe(representations[2]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should stop guess and downgrade when score is too low", () => {
|
|
361
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
362
|
+
createScoreData(0.9, ScoreConfidenceLevel.HIGH),
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
366
|
+
|
|
367
|
+
const result = chooser.getGuess(
|
|
368
|
+
representations,
|
|
369
|
+
{ bufferGap: 3, speed: 1 },
|
|
370
|
+
representations[2],
|
|
371
|
+
100000,
|
|
372
|
+
[],
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(result).toBe(representations[1]); // Previous representation
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("should stop guess when buffer gap is low and score is marginal", () => {
|
|
379
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
380
|
+
createScoreData(1.1, ScoreConfidenceLevel.HIGH),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
384
|
+
|
|
385
|
+
const result = chooser.getGuess(
|
|
386
|
+
representations,
|
|
387
|
+
{ bufferGap: 0.5, speed: 1 }, // Below 0.6 threshold
|
|
388
|
+
representations[2],
|
|
389
|
+
100000,
|
|
390
|
+
[],
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
expect(result).toBe(representations[1]);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should stop guess when score is undefined and buffer gap is low", () => {
|
|
397
|
+
mockScoreCalculator.getEstimate.mockReturnValue(undefined);
|
|
398
|
+
|
|
399
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
400
|
+
|
|
401
|
+
const result = chooser.getGuess(
|
|
402
|
+
representations,
|
|
403
|
+
{ bufferGap: 0.5, speed: 1 },
|
|
404
|
+
representations[2],
|
|
405
|
+
100000,
|
|
406
|
+
[],
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
expect(result).toBe(representations[1]);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("should block guesses for increasing duration after consecutive wrong guesses", () => {
|
|
413
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
414
|
+
createScoreData(0.9, ScoreConfidenceLevel.HIGH),
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
418
|
+
|
|
419
|
+
// First wrong guess
|
|
420
|
+
chooser.getGuess(
|
|
421
|
+
representations,
|
|
422
|
+
{ bufferGap: 3, speed: 1 },
|
|
423
|
+
representations[2],
|
|
424
|
+
100000,
|
|
425
|
+
[],
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Try to guess again immediately
|
|
429
|
+
mocks.getMonotonicTimeStamp.mockReturnValue(10000 + 5000); // 5 seconds later
|
|
430
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
431
|
+
|
|
432
|
+
const result = chooser.getGuess(
|
|
433
|
+
representations,
|
|
434
|
+
{ bufferGap: 3, speed: 1 },
|
|
435
|
+
representations[1],
|
|
436
|
+
100000,
|
|
437
|
+
[],
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Should be blocked (15 seconds block)
|
|
441
|
+
expect(result).toBeNull();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("should allow guessing after block period expires", () => {
|
|
445
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
446
|
+
createScoreData(0.9, ScoreConfidenceLevel.HIGH),
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
450
|
+
|
|
451
|
+
// First wrong guess
|
|
452
|
+
chooser.getGuess(
|
|
453
|
+
representations,
|
|
454
|
+
{ bufferGap: 3, speed: 1 },
|
|
455
|
+
representations[2],
|
|
456
|
+
100000,
|
|
457
|
+
[],
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// Wait for block to expire
|
|
461
|
+
mocks.getMonotonicTimeStamp.mockReturnValue(10000 + 20000); // 20 seconds later
|
|
462
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
463
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
464
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const result = chooser.getGuess(
|
|
468
|
+
representations,
|
|
469
|
+
{ bufferGap: 3, speed: 1 },
|
|
470
|
+
representations[1],
|
|
471
|
+
100000,
|
|
472
|
+
[],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Should be allowed now
|
|
476
|
+
expect(result).toBe(representations[2]);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("should guess higher when conditions allow in guessing mode", () => {
|
|
480
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
481
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
485
|
+
|
|
486
|
+
const result = chooser.getGuess(
|
|
487
|
+
representations,
|
|
488
|
+
{ bufferGap: 3, speed: 1 },
|
|
489
|
+
representations[2],
|
|
490
|
+
100000,
|
|
491
|
+
[],
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
expect(result).toBe(representations[3]); // Next higher representation
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("should return current representation when score data is undefined and not stopping", () => {
|
|
498
|
+
mockScoreCalculator.getEstimate.mockReturnValue(undefined);
|
|
499
|
+
|
|
500
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
501
|
+
|
|
502
|
+
const result = chooser.getGuess(
|
|
503
|
+
representations,
|
|
504
|
+
{ bufferGap: 3, speed: 1 },
|
|
505
|
+
representations[2],
|
|
506
|
+
100000,
|
|
507
|
+
[],
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
expect(result).toBe(representations[2]);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe("request-based guess stopping", () => {
|
|
515
|
+
beforeEach(() => {
|
|
516
|
+
mockPrevEstimate.representation = representations[2];
|
|
517
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
518
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
519
|
+
createScoreData(1.2, ScoreConfidenceLevel.HIGH),
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("should stop guess when init segment takes too long (>1000ms)", () => {
|
|
524
|
+
const requests = [
|
|
525
|
+
createRequest("rep-3", 0, 8000, true), // 2000ms elapsed (10000 - 8000)
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
529
|
+
|
|
530
|
+
const result = chooser.getGuess(
|
|
531
|
+
representations,
|
|
532
|
+
{ bufferGap: 3, speed: 1 },
|
|
533
|
+
representations[2],
|
|
534
|
+
100000,
|
|
535
|
+
requests,
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
expect(result).toBe(representations[1]);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("should not stop guess when init segment is fast enough", () => {
|
|
542
|
+
const requests = [
|
|
543
|
+
createRequest("rep-3", 0, 9500, true), // 500ms elapsed
|
|
544
|
+
];
|
|
545
|
+
|
|
546
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
547
|
+
|
|
548
|
+
const result = chooser.getGuess(
|
|
549
|
+
representations,
|
|
550
|
+
{ bufferGap: 3, speed: 1 },
|
|
551
|
+
representations[2],
|
|
552
|
+
100000,
|
|
553
|
+
requests,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
expect(result).toBe(representations[3]);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("should stop guess when regular segment takes too long", () => {
|
|
560
|
+
const requests = [
|
|
561
|
+
createRequest("rep-3", 2, 5000, false), // 5000ms elapsed, duration is 2s
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
565
|
+
|
|
566
|
+
const result = chooser.getGuess(
|
|
567
|
+
representations,
|
|
568
|
+
{ bufferGap: 3, speed: 1 },
|
|
569
|
+
representations[2],
|
|
570
|
+
100000,
|
|
571
|
+
requests,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
expect(result).toBe(representations[1]); // Should downgrade
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("should not stop guess when regular segment is within acceptable time", () => {
|
|
578
|
+
const requests = [
|
|
579
|
+
createRequest("rep-3", 2, 8500, false), // 1500ms elapsed, duration is 2s (threshold: 2200ms)
|
|
580
|
+
];
|
|
581
|
+
|
|
582
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
583
|
+
|
|
584
|
+
const result = chooser.getGuess(
|
|
585
|
+
representations,
|
|
586
|
+
{ bufferGap: 3, speed: 1 },
|
|
587
|
+
representations[2],
|
|
588
|
+
100000,
|
|
589
|
+
requests,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
expect(result).toBe(representations[3]);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("should stop guess when estimated bandwidth is too low", () => {
|
|
596
|
+
mocks.estimateRequestBandwidth.mockReturnValue(700000); // 70% of bitrate
|
|
597
|
+
|
|
598
|
+
const requests = [createRequest("rep-3", 2, 9000, false)];
|
|
599
|
+
|
|
600
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
601
|
+
|
|
602
|
+
const result = chooser.getGuess(
|
|
603
|
+
representations,
|
|
604
|
+
{ bufferGap: 3, speed: 1 },
|
|
605
|
+
representations[2],
|
|
606
|
+
100000,
|
|
607
|
+
requests,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
expect(result).toBe(representations[1]);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("should not consider requests for other representations", () => {
|
|
614
|
+
const requests = [createRequest("rep-4", 2, 5000, false)];
|
|
615
|
+
|
|
616
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
617
|
+
createScoreData(1.1, ScoreConfidenceLevel.HIGH),
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
621
|
+
|
|
622
|
+
const result = chooser.getGuess(
|
|
623
|
+
representations,
|
|
624
|
+
{ bufferGap: 2.0, speed: 1 },
|
|
625
|
+
representations[2],
|
|
626
|
+
100000,
|
|
627
|
+
requests,
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
expect(result).toBe(representations[2]);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("should handle multiple requests and stop on any failing condition", () => {
|
|
634
|
+
const requests = [
|
|
635
|
+
createRequest("rep-3", 2, 9500, false), // OK
|
|
636
|
+
createRequest("rep-3", 2, 5000, false), // TOO SLOW - should trigger stop
|
|
637
|
+
createRequest("rep-3", 2, 9000, false), // OK
|
|
638
|
+
];
|
|
639
|
+
|
|
640
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
641
|
+
|
|
642
|
+
const result = chooser.getGuess(
|
|
643
|
+
representations,
|
|
644
|
+
{ bufferGap: 3, speed: 1 },
|
|
645
|
+
representations[2],
|
|
646
|
+
100000,
|
|
647
|
+
requests,
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
expect(result).toBe(representations[1]);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
describe("guess validation", () => {
|
|
655
|
+
beforeEach(() => {
|
|
656
|
+
mockPrevEstimate.representation = representations[2];
|
|
657
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("should validate guess with high score and high confidence", () => {
|
|
661
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
662
|
+
createScoreData(1.6, ScoreConfidenceLevel.HIGH),
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
666
|
+
|
|
667
|
+
chooser.getGuess(
|
|
668
|
+
representations,
|
|
669
|
+
{ bufferGap: 3, speed: 1 },
|
|
670
|
+
representations[2],
|
|
671
|
+
100000,
|
|
672
|
+
[],
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
// If validated, should allow guessing higher next time
|
|
676
|
+
const result = chooser.getGuess(
|
|
677
|
+
representations,
|
|
678
|
+
{ bufferGap: 3, speed: 1 },
|
|
679
|
+
representations[2],
|
|
680
|
+
100000,
|
|
681
|
+
[],
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
expect(result).toBe(representations[3]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("should validate guess when incoming bitrate matches and it's an improvement", () => {
|
|
688
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
689
|
+
createScoreData(1.2, ScoreConfidenceLevel.LOW),
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
693
|
+
|
|
694
|
+
const result = chooser.getGuess(
|
|
695
|
+
representations,
|
|
696
|
+
{ bufferGap: 3, speed: 1 },
|
|
697
|
+
representations[2],
|
|
698
|
+
1000000, // Matches current bitrate
|
|
699
|
+
[],
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
// Should be validated and allow progression
|
|
703
|
+
expect(result).toBe(representations[2]);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
describe("edge cases", () => {
|
|
708
|
+
it("should handle infinite buffer gap correctly", () => {
|
|
709
|
+
mockPrevEstimate.representation = representations[1];
|
|
710
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
711
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
712
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
716
|
+
|
|
717
|
+
const result = chooser.getGuess(
|
|
718
|
+
representations,
|
|
719
|
+
{ bufferGap: Infinity, speed: 1 },
|
|
720
|
+
representations[1],
|
|
721
|
+
100000,
|
|
722
|
+
[],
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
expect(result).toBe(null);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("should handle NaN buffer gap by not guessing", () => {
|
|
729
|
+
mockPrevEstimate.representation = representations[1];
|
|
730
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
731
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
732
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
736
|
+
|
|
737
|
+
const result = chooser.getGuess(
|
|
738
|
+
representations,
|
|
739
|
+
{ bufferGap: NaN, speed: 1 },
|
|
740
|
+
representations[1],
|
|
741
|
+
100000,
|
|
742
|
+
[],
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
expect(result).toBeNull();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("should cap block duration at 120 seconds", () => {
|
|
749
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
750
|
+
createScoreData(0.9, ScoreConfidenceLevel.HIGH),
|
|
751
|
+
);
|
|
752
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.GuessBased;
|
|
753
|
+
mockPrevEstimate.representation = representations[2];
|
|
754
|
+
|
|
755
|
+
const chooser = new GuessBasedChooser(mockScoreCalculator, mockPrevEstimate);
|
|
756
|
+
|
|
757
|
+
// Trigger many consecutive wrong guesses
|
|
758
|
+
for (let i = 0; i < 20; i++) {
|
|
759
|
+
chooser.getGuess(
|
|
760
|
+
representations,
|
|
761
|
+
{ bufferGap: 3, speed: 1 },
|
|
762
|
+
representations[2],
|
|
763
|
+
100000,
|
|
764
|
+
[],
|
|
765
|
+
);
|
|
766
|
+
mocks.getMonotonicTimeStamp.mockReturnValue(10000 + 1000 * (i + 1));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// The block should be capped at 120 seconds max
|
|
770
|
+
// After 120 seconds, should be able to guess again
|
|
771
|
+
mocks.getMonotonicTimeStamp.mockReturnValue(10000 + 20000 + 120100);
|
|
772
|
+
mockPrevEstimate.algorithmType = ABRAlgorithmType.BufferBased;
|
|
773
|
+
mockPrevEstimate.representation = representations[1]; // Update to reflect actual downgraded state
|
|
774
|
+
mockScoreCalculator.getEstimate.mockReturnValue(
|
|
775
|
+
createScoreData(1.5, ScoreConfidenceLevel.HIGH),
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
const result = chooser.getGuess(
|
|
779
|
+
representations,
|
|
780
|
+
{ bufferGap: 3, speed: 1 },
|
|
781
|
+
representations[1],
|
|
782
|
+
100000,
|
|
783
|
+
[],
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(result).toBe(representations[2]);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
});
|