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,646 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { SegmentSinkOperation } from "../../../../segment_sinks";
|
|
3
|
+
import getAdaptationSwitchStrategy from "../get_adaptation_switch_strategy";
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
8
|
+
|
|
9
|
+
// Mock only external dependencies that have side effects or complex behavior
|
|
10
|
+
vi.mock("../../../../../config", () => ({
|
|
11
|
+
default: {
|
|
12
|
+
getCurrent: vi.fn(() => ({
|
|
13
|
+
ADAP_REP_SWITCH_BUFFER_PADDINGS: {
|
|
14
|
+
video: { before: 0.5, after: 0.5 },
|
|
15
|
+
audio: { before: 0.5, after: 0.5 },
|
|
16
|
+
text: { before: 0, after: 0 },
|
|
17
|
+
},
|
|
18
|
+
})),
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe("getAdaptationSwitchStrategy", () => {
|
|
23
|
+
let mockSegmentSink: any;
|
|
24
|
+
let mockPeriod: any;
|
|
25
|
+
let mockAdaptation: any;
|
|
26
|
+
let mockPlaybackObserver: any;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
// Create base mocks
|
|
30
|
+
mockSegmentSink = {
|
|
31
|
+
codec: undefined,
|
|
32
|
+
getLastKnownInventory: vi.fn(() => []),
|
|
33
|
+
getPendingOperations: vi.fn(() => []),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
mockPeriod = {
|
|
37
|
+
id: "period-1",
|
|
38
|
+
start: 10,
|
|
39
|
+
end: 20,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
mockAdaptation = {
|
|
43
|
+
id: "adaptation-1",
|
|
44
|
+
type: "video",
|
|
45
|
+
representations: [],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
mockPlaybackObserver = {
|
|
49
|
+
getCurrentTime: vi.fn(() => 15),
|
|
50
|
+
getReadyState: vi.fn(() => 3),
|
|
51
|
+
getReference: vi.fn(() => ({
|
|
52
|
+
getValue: () => ({
|
|
53
|
+
position: {
|
|
54
|
+
getPolled: () => 15,
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("codec compatibility", () => {
|
|
62
|
+
it("should return needs-reload when codec is incompatible and onCodecSwitch is reload", () => {
|
|
63
|
+
mockSegmentSink.codec = "avc1.64001f";
|
|
64
|
+
mockAdaptation.representations = [
|
|
65
|
+
{
|
|
66
|
+
isPlayable: () => true,
|
|
67
|
+
getMimeTypeString: () => "video/mp4; codecs=hev1.1.6.L93.B0",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const result = getAdaptationSwitchStrategy(
|
|
72
|
+
mockSegmentSink,
|
|
73
|
+
mockPeriod,
|
|
74
|
+
mockAdaptation,
|
|
75
|
+
"seamless",
|
|
76
|
+
mockPlaybackObserver,
|
|
77
|
+
{ onCodecSwitch: "reload" },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual({ type: "needs-reload", value: undefined });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should continue when codec is compatible", () => {
|
|
84
|
+
mockSegmentSink.codec = "video/mp4;codecs=avc1.64001f";
|
|
85
|
+
mockAdaptation.representations = [
|
|
86
|
+
{
|
|
87
|
+
isPlayable: () => true,
|
|
88
|
+
getMimeTypeString: () => "video/mp4;codecs=avc1.65001f",
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const result = getAdaptationSwitchStrategy(
|
|
93
|
+
mockSegmentSink,
|
|
94
|
+
mockPeriod,
|
|
95
|
+
mockAdaptation,
|
|
96
|
+
"seamless",
|
|
97
|
+
mockPlaybackObserver,
|
|
98
|
+
{ onCodecSwitch: "reload" },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(result.type).not.toBe("needs-reload");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should continue when onCodecSwitch is continue regardless of codec", () => {
|
|
105
|
+
mockSegmentSink.codec = "avc1.64001f";
|
|
106
|
+
mockAdaptation.representations = [
|
|
107
|
+
{
|
|
108
|
+
isPlayable: () => true,
|
|
109
|
+
getMimeTypeString: () => "video/mp4; codecs=hev1.1.6.L93.B0",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const result = getAdaptationSwitchStrategy(
|
|
114
|
+
mockSegmentSink,
|
|
115
|
+
mockPeriod,
|
|
116
|
+
mockAdaptation,
|
|
117
|
+
"seamless",
|
|
118
|
+
mockPlaybackObserver,
|
|
119
|
+
{ onCodecSwitch: "continue" },
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(result.type).not.toBe("needs-reload");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("no unwanted segments", () => {
|
|
127
|
+
it("should return continue when no other adaptation is buffered", () => {
|
|
128
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
129
|
+
{
|
|
130
|
+
start: 10,
|
|
131
|
+
end: 15,
|
|
132
|
+
bufferedStart: 10,
|
|
133
|
+
bufferedEnd: 15,
|
|
134
|
+
infos: {
|
|
135
|
+
period: { id: "period-1", start: 0 },
|
|
136
|
+
adaptation: { id: "adaptation-1" }, // Same adaptation
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const result = getAdaptationSwitchStrategy(
|
|
142
|
+
mockSegmentSink,
|
|
143
|
+
mockPeriod,
|
|
144
|
+
mockAdaptation,
|
|
145
|
+
"seamless",
|
|
146
|
+
mockPlaybackObserver,
|
|
147
|
+
{ onCodecSwitch: "continue" },
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should return continue when segments are from different period", () => {
|
|
154
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
155
|
+
{
|
|
156
|
+
start: 5,
|
|
157
|
+
end: 10,
|
|
158
|
+
bufferedStart: 5,
|
|
159
|
+
bufferedEnd: 10,
|
|
160
|
+
infos: {
|
|
161
|
+
period: { id: "period-0", start: 0 }, // Different period
|
|
162
|
+
adaptation: { id: "adaptation-2" },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const result = getAdaptationSwitchStrategy(
|
|
168
|
+
mockSegmentSink,
|
|
169
|
+
mockPeriod,
|
|
170
|
+
mockAdaptation,
|
|
171
|
+
"seamless",
|
|
172
|
+
mockPlaybackObserver,
|
|
173
|
+
{ onCodecSwitch: "continue" },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("unwanted segments in inventory", () => {
|
|
181
|
+
it("should detect unwanted segments from different adaptation in same period", () => {
|
|
182
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
183
|
+
{
|
|
184
|
+
start: 10,
|
|
185
|
+
end: 15,
|
|
186
|
+
bufferedStart: 10,
|
|
187
|
+
bufferedEnd: 15,
|
|
188
|
+
infos: {
|
|
189
|
+
period: { id: "period-1", start: 0 },
|
|
190
|
+
adaptation: { id: "adaptation-2" }, // Different adaptation
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
const result = getAdaptationSwitchStrategy(
|
|
196
|
+
mockSegmentSink,
|
|
197
|
+
mockPeriod,
|
|
198
|
+
mockAdaptation,
|
|
199
|
+
"seamless",
|
|
200
|
+
mockPlaybackObserver,
|
|
201
|
+
{ onCodecSwitch: "continue" },
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(result.type).not.toBe("continue");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should use bufferedStart/bufferedEnd when available", () => {
|
|
208
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
209
|
+
{
|
|
210
|
+
start: 10,
|
|
211
|
+
end: 15,
|
|
212
|
+
bufferedStart: 10.5,
|
|
213
|
+
bufferedEnd: 14.5,
|
|
214
|
+
infos: {
|
|
215
|
+
period: { id: "period-1", start: 0 },
|
|
216
|
+
adaptation: { id: "adaptation-2" },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
const result = getAdaptationSwitchStrategy(
|
|
222
|
+
mockSegmentSink,
|
|
223
|
+
mockPeriod,
|
|
224
|
+
mockAdaptation,
|
|
225
|
+
"seamless",
|
|
226
|
+
mockPlaybackObserver,
|
|
227
|
+
{ onCodecSwitch: "continue" },
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(result.type).not.toBe("continue");
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("pending operations", () => {
|
|
235
|
+
it("should include unwanted segments from pending push operations", () => {
|
|
236
|
+
mockSegmentSink.getPendingOperations.mockReturnValue([
|
|
237
|
+
{
|
|
238
|
+
type: SegmentSinkOperation.Push,
|
|
239
|
+
value: {
|
|
240
|
+
inventoryInfos: {
|
|
241
|
+
period: { id: "period-1", start: 0 },
|
|
242
|
+
adaptation: { id: "adaptation-2" },
|
|
243
|
+
segment: {
|
|
244
|
+
time: 12,
|
|
245
|
+
duration: 3,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
const result = getAdaptationSwitchStrategy(
|
|
252
|
+
mockSegmentSink,
|
|
253
|
+
mockPeriod,
|
|
254
|
+
mockAdaptation,
|
|
255
|
+
"seamless",
|
|
256
|
+
mockPlaybackObserver,
|
|
257
|
+
{ onCodecSwitch: "continue" },
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
expect(result.type).not.toBe("continue");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should ignore non-push operations", () => {
|
|
264
|
+
mockSegmentSink.getPendingOperations.mockReturnValue([
|
|
265
|
+
{
|
|
266
|
+
type: SegmentSinkOperation.Remove,
|
|
267
|
+
value: { start: 10, end: 15 },
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
type: SegmentSinkOperation.SignalSegmentComplete,
|
|
271
|
+
value: { start: 10, end: 15 },
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
const result = getAdaptationSwitchStrategy(
|
|
276
|
+
mockSegmentSink,
|
|
277
|
+
mockPeriod,
|
|
278
|
+
mockAdaptation,
|
|
279
|
+
"seamless",
|
|
280
|
+
mockPlaybackObserver,
|
|
281
|
+
{ onCodecSwitch: "continue" },
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("switching modes", () => {
|
|
289
|
+
beforeEach(() => {
|
|
290
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
291
|
+
{
|
|
292
|
+
start: 10,
|
|
293
|
+
end: 15,
|
|
294
|
+
bufferedStart: 10,
|
|
295
|
+
bufferedEnd: 15,
|
|
296
|
+
infos: {
|
|
297
|
+
period: { id: "period-1", start: 0 },
|
|
298
|
+
adaptation: { id: "adaptation-2" },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should return needs-reload when switchingMode is reload and readyState > 1", () => {
|
|
305
|
+
mockPlaybackObserver.getReadyState.mockReturnValue(3);
|
|
306
|
+
|
|
307
|
+
const result = getAdaptationSwitchStrategy(
|
|
308
|
+
mockSegmentSink,
|
|
309
|
+
mockPeriod,
|
|
310
|
+
mockAdaptation,
|
|
311
|
+
"reload",
|
|
312
|
+
mockPlaybackObserver,
|
|
313
|
+
{ onCodecSwitch: "continue" },
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(result).toEqual({ type: "needs-reload", value: undefined });
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should not reload when readyState is 1 or less", () => {
|
|
320
|
+
mockPlaybackObserver.getReadyState.mockReturnValue(1);
|
|
321
|
+
|
|
322
|
+
const result = getAdaptationSwitchStrategy(
|
|
323
|
+
mockSegmentSink,
|
|
324
|
+
mockPeriod,
|
|
325
|
+
mockAdaptation,
|
|
326
|
+
"reload",
|
|
327
|
+
mockPlaybackObserver,
|
|
328
|
+
{ onCodecSwitch: "continue" },
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
expect(result.type).not.toBe("needs-reload");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should flush-buffer for direct mode with non-text adaptation", () => {
|
|
335
|
+
const result = getAdaptationSwitchStrategy(
|
|
336
|
+
mockSegmentSink,
|
|
337
|
+
mockPeriod,
|
|
338
|
+
mockAdaptation,
|
|
339
|
+
"direct",
|
|
340
|
+
mockPlaybackObserver,
|
|
341
|
+
{ onCodecSwitch: "continue" },
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
expect(result.type).toBe("flush-buffer");
|
|
345
|
+
expect(Array.isArray(result.value)).toBe(true);
|
|
346
|
+
expect(result.value?.length).toBeGreaterThan(0);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("should clean-buffer for direct mode with text adaptation", () => {
|
|
350
|
+
mockAdaptation.type = "text";
|
|
351
|
+
|
|
352
|
+
const result = getAdaptationSwitchStrategy(
|
|
353
|
+
mockSegmentSink,
|
|
354
|
+
mockPeriod,
|
|
355
|
+
mockAdaptation,
|
|
356
|
+
"direct",
|
|
357
|
+
mockPlaybackObserver,
|
|
358
|
+
{ onCodecSwitch: "continue" },
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect(result.type).toBe("clean-buffer");
|
|
362
|
+
expect(Array.isArray(result.value)).toBe(true);
|
|
363
|
+
expect(result.value?.length).toBeGreaterThan(0);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("should clean-buffer for seamless mode", () => {
|
|
367
|
+
const result = getAdaptationSwitchStrategy(
|
|
368
|
+
mockSegmentSink,
|
|
369
|
+
mockPeriod,
|
|
370
|
+
mockAdaptation,
|
|
371
|
+
"seamless",
|
|
372
|
+
mockPlaybackObserver,
|
|
373
|
+
{ onCodecSwitch: "continue" },
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(result.type).toBe("clean-buffer");
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("range exclusion logic", () => {
|
|
381
|
+
beforeEach(() => {
|
|
382
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
383
|
+
{
|
|
384
|
+
start: 12,
|
|
385
|
+
end: 18,
|
|
386
|
+
bufferedStart: 12,
|
|
387
|
+
bufferedEnd: 18,
|
|
388
|
+
infos: {
|
|
389
|
+
period: { id: "period-1", start: 0 },
|
|
390
|
+
adaptation: { id: "adaptation-2" },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
]);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should return continue when all unwanted ranges are excluded around current position", () => {
|
|
397
|
+
// Set up inventory with segments only around current time (which will be excluded)
|
|
398
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
399
|
+
{
|
|
400
|
+
start: 14.6,
|
|
401
|
+
end: 15.4,
|
|
402
|
+
bufferedStart: 14.6,
|
|
403
|
+
bufferedEnd: 15.4,
|
|
404
|
+
infos: {
|
|
405
|
+
period: { id: "period-1", start: 0 },
|
|
406
|
+
adaptation: { id: "adaptation-2" },
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
]);
|
|
410
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(15);
|
|
411
|
+
|
|
412
|
+
const result = getAdaptationSwitchStrategy(
|
|
413
|
+
mockSegmentSink,
|
|
414
|
+
mockPeriod,
|
|
415
|
+
mockAdaptation,
|
|
416
|
+
"seamless",
|
|
417
|
+
mockPlaybackObserver,
|
|
418
|
+
{ onCodecSwitch: "continue" },
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should clean ranges that are far from period boundaries", () => {
|
|
425
|
+
// Segment in the middle of the period, away from boundaries
|
|
426
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
427
|
+
{
|
|
428
|
+
start: 13,
|
|
429
|
+
end: 17,
|
|
430
|
+
bufferedStart: 13,
|
|
431
|
+
bufferedEnd: 17,
|
|
432
|
+
infos: {
|
|
433
|
+
period: { id: "period-1", start: 0 },
|
|
434
|
+
adaptation: { id: "adaptation-2" },
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
]);
|
|
438
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(18); // Away from segment
|
|
439
|
+
|
|
440
|
+
const result = getAdaptationSwitchStrategy(
|
|
441
|
+
mockSegmentSink,
|
|
442
|
+
mockPeriod,
|
|
443
|
+
mockAdaptation,
|
|
444
|
+
"seamless",
|
|
445
|
+
mockPlaybackObserver,
|
|
446
|
+
{ onCodecSwitch: "continue" },
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
expect(result.type).not.toBe("continue");
|
|
450
|
+
expect(result.value?.length).toBeGreaterThan(0);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("should exclude range near period start when previous period segment is close", () => {
|
|
454
|
+
// Set up inventory where segment from period-1 exists
|
|
455
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
456
|
+
{
|
|
457
|
+
start: 8,
|
|
458
|
+
end: 9.5,
|
|
459
|
+
bufferedStart: 8,
|
|
460
|
+
bufferedEnd: 9.5, // Less than 1 second before period start (10)
|
|
461
|
+
infos: {
|
|
462
|
+
period: { id: "period-0", start: 0 },
|
|
463
|
+
adaptation: { id: "adaptation-1" },
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
start: 10,
|
|
468
|
+
end: 10.5,
|
|
469
|
+
bufferedStart: 10,
|
|
470
|
+
bufferedEnd: 10.5,
|
|
471
|
+
infos: {
|
|
472
|
+
period: { id: "period-1", start: 0 },
|
|
473
|
+
adaptation: { id: "adaptation-2" }, // Different adaptation
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
]);
|
|
477
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(18); // Far away
|
|
478
|
+
|
|
479
|
+
const result = getAdaptationSwitchStrategy(
|
|
480
|
+
mockSegmentSink,
|
|
481
|
+
mockPeriod,
|
|
482
|
+
mockAdaptation,
|
|
483
|
+
"seamless",
|
|
484
|
+
mockPlaybackObserver,
|
|
485
|
+
{ onCodecSwitch: "continue" },
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// The segment at period start should be excluded from removal
|
|
489
|
+
expect(result.type).toBe("continue");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should exclude range near period end when next period segment is close", () => {
|
|
493
|
+
// Set up inventory where segment from next period exists
|
|
494
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
495
|
+
{
|
|
496
|
+
start: 19.5,
|
|
497
|
+
end: 20,
|
|
498
|
+
bufferedStart: 19.5,
|
|
499
|
+
bufferedEnd: 20,
|
|
500
|
+
infos: {
|
|
501
|
+
period: { id: "period-1", start: 0, end: 20 },
|
|
502
|
+
adaptation: { id: "adaptation-2" }, // Different adaptation
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
start: 20,
|
|
507
|
+
end: 21,
|
|
508
|
+
bufferedStart: 20.5, // Less than 1 second after period end (20)
|
|
509
|
+
bufferedEnd: 21,
|
|
510
|
+
infos: {
|
|
511
|
+
period: { id: "period-2", start: 20 },
|
|
512
|
+
adaptation: { id: "adaptation-1" },
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
]);
|
|
516
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(12); // Far away
|
|
517
|
+
|
|
518
|
+
const result = getAdaptationSwitchStrategy(
|
|
519
|
+
mockSegmentSink,
|
|
520
|
+
mockPeriod,
|
|
521
|
+
mockAdaptation,
|
|
522
|
+
"seamless",
|
|
523
|
+
mockPlaybackObserver,
|
|
524
|
+
{ onCodecSwitch: "continue" },
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// The segment at period end should be excluded from removal
|
|
528
|
+
expect(result.type).toBe("continue");
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("should not exclude period end range when period.end is undefined", () => {
|
|
532
|
+
mockPeriod.end = undefined;
|
|
533
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
534
|
+
{
|
|
535
|
+
start: 12,
|
|
536
|
+
end: 15,
|
|
537
|
+
bufferedStart: 12,
|
|
538
|
+
bufferedEnd: 15,
|
|
539
|
+
infos: {
|
|
540
|
+
period: { id: "period-1", start: 10 },
|
|
541
|
+
adaptation: { id: "adaptation-2" },
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
]);
|
|
545
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(18);
|
|
546
|
+
|
|
547
|
+
const result = getAdaptationSwitchStrategy(
|
|
548
|
+
mockSegmentSink,
|
|
549
|
+
mockPeriod,
|
|
550
|
+
mockAdaptation,
|
|
551
|
+
"seamless",
|
|
552
|
+
mockPlaybackObserver,
|
|
553
|
+
{ onCodecSwitch: "continue" },
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
// Should be able to clean buffer since no end boundary to worry about
|
|
557
|
+
expect(result.type).not.toBe("continue");
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should exclude current position padding for seamless mode", () => {
|
|
561
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
562
|
+
{
|
|
563
|
+
start: 14.6,
|
|
564
|
+
end: 15.4,
|
|
565
|
+
bufferedStart: 14.6,
|
|
566
|
+
bufferedEnd: 15.4,
|
|
567
|
+
infos: {
|
|
568
|
+
period: { id: "period-1", start: 10 },
|
|
569
|
+
adaptation: { id: "adaptation-2" },
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
]);
|
|
573
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(15);
|
|
574
|
+
|
|
575
|
+
const result = getAdaptationSwitchStrategy(
|
|
576
|
+
mockSegmentSink,
|
|
577
|
+
mockPeriod,
|
|
578
|
+
mockAdaptation,
|
|
579
|
+
"seamless",
|
|
580
|
+
mockPlaybackObserver,
|
|
581
|
+
{ onCodecSwitch: "continue" },
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Segment is within padding (15 - 0.5 to 15 + 0.5), so should be excluded
|
|
585
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("should not exclude current position padding for direct mode", () => {
|
|
589
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
590
|
+
{
|
|
591
|
+
start: 14.6,
|
|
592
|
+
end: 15.4,
|
|
593
|
+
bufferedStart: 14.6,
|
|
594
|
+
bufferedEnd: 15.4,
|
|
595
|
+
infos: {
|
|
596
|
+
period: { id: "period-1", start: 10 },
|
|
597
|
+
adaptation: { id: "adaptation-2" },
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
]);
|
|
601
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(15);
|
|
602
|
+
|
|
603
|
+
const result = getAdaptationSwitchStrategy(
|
|
604
|
+
mockSegmentSink,
|
|
605
|
+
mockPeriod,
|
|
606
|
+
mockAdaptation,
|
|
607
|
+
"direct",
|
|
608
|
+
mockPlaybackObserver,
|
|
609
|
+
{ onCodecSwitch: "continue" },
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// In direct mode, current position is not excluded, so buffer should be flushed
|
|
613
|
+
expect(result.type).toBe("flush-buffer");
|
|
614
|
+
expect(result.value?.length).toBeGreaterThan(0);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should use polled position when getCurrentTime returns undefined", () => {
|
|
618
|
+
mockPlaybackObserver.getCurrentTime.mockReturnValue(undefined);
|
|
619
|
+
mockSegmentSink.getLastKnownInventory.mockReturnValue([
|
|
620
|
+
{
|
|
621
|
+
start: 14.6,
|
|
622
|
+
end: 15.4,
|
|
623
|
+
bufferedStart: 14.6,
|
|
624
|
+
bufferedEnd: 15.4,
|
|
625
|
+
infos: {
|
|
626
|
+
period: { id: "period-1", start: 10 },
|
|
627
|
+
adaptation: { id: "adaptation-2" },
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
]);
|
|
631
|
+
|
|
632
|
+
const result = getAdaptationSwitchStrategy(
|
|
633
|
+
mockSegmentSink,
|
|
634
|
+
mockPeriod,
|
|
635
|
+
mockAdaptation,
|
|
636
|
+
"seamless",
|
|
637
|
+
mockPlaybackObserver,
|
|
638
|
+
{ onCodecSwitch: "continue" },
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
expect(mockPlaybackObserver.getReference).toHaveBeenCalled();
|
|
642
|
+
// Should still exclude based on polled position (15)
|
|
643
|
+
expect(result).toEqual({ type: "continue", value: undefined });
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|