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.
Files changed (108) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  6. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  7. package/dist/commonjs/compat/should_seek_at_zero.d.ts +12 -0
  8. package/dist/commonjs/compat/should_seek_at_zero.d.ts.map +1 -0
  9. package/dist/commonjs/compat/should_seek_at_zero.js +23 -0
  10. package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
  11. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  12. package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
  13. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  14. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  15. package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
  16. package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
  17. package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
  18. package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
  19. package/dist/commonjs/default_config.d.ts +5 -0
  20. package/dist/commonjs/default_config.d.ts.map +1 -1
  21. package/dist/commonjs/default_config.js +5 -0
  22. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  23. package/dist/commonjs/main_thread/api/public_api.js +27 -3
  24. package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  25. package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
  26. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  27. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
  28. package/dist/commonjs/manifest/classes/index.d.ts +2 -1
  29. package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
  30. package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
  31. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  32. package/dist/commonjs/manifest/classes/representation.js +2 -2
  33. package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
  34. package/dist/commonjs/utils/base64.d.ts +15 -1
  35. package/dist/commonjs/utils/base64.d.ts.map +1 -1
  36. package/dist/commonjs/utils/base64.js +6 -0
  37. package/dist/commonjs/utils/ranges.d.ts +1 -1
  38. package/dist/commonjs/utils/ranges.js +1 -1
  39. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  40. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  41. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  42. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  43. package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
  44. package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
  45. package/dist/es2017/compat/should_seek_at_zero.js +20 -0
  46. package/dist/es2017/core/entry/FreezeResolver.js +1 -1
  47. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  48. package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
  49. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  50. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  51. package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
  52. package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
  53. package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
  54. package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
  55. package/dist/es2017/default_config.d.ts +5 -0
  56. package/dist/es2017/default_config.d.ts.map +1 -1
  57. package/dist/es2017/default_config.js +5 -0
  58. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  59. package/dist/es2017/main_thread/api/public_api.js +27 -3
  60. package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  61. package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
  62. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  63. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
  64. package/dist/es2017/manifest/classes/index.d.ts +2 -1
  65. package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
  66. package/dist/es2017/manifest/classes/representation.d.ts +1 -1
  67. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  68. package/dist/es2017/manifest/classes/representation.js +1 -1
  69. package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
  70. package/dist/es2017/utils/base64.d.ts +15 -1
  71. package/dist/es2017/utils/base64.d.ts.map +1 -1
  72. package/dist/es2017/utils/base64.js +6 -0
  73. package/dist/es2017/utils/ranges.d.ts +1 -1
  74. package/dist/es2017/utils/ranges.js +1 -1
  75. package/dist/mpd-parser.wasm +0 -0
  76. package/dist/worker.js +6 -6
  77. package/package.json +9 -3
  78. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  79. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  80. package/src/compat/should_seek_at_zero.ts +23 -0
  81. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +790 -0
  82. package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
  83. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +285 -0
  84. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +247 -0
  85. package/src/core/entry/FreezeResolver.ts +1 -1
  86. package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
  87. package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +868 -0
  88. package/src/core/entry/content_time_boundaries_observer.ts +10 -0
  89. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +592 -0
  90. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
  91. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
  92. package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
  93. package/src/core/stream/representation/representation_stream.ts +11 -49
  94. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
  95. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
  96. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
  97. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
  98. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
  99. package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
  100. package/src/default_config.ts +6 -0
  101. package/src/main_thread/api/public_api.ts +32 -3
  102. package/src/main_thread/init/directfile_content_initializer.ts +26 -1
  103. package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
  104. package/src/manifest/classes/index.ts +2 -0
  105. package/src/manifest/classes/representation.ts +2 -1
  106. package/src/manifest/classes/representation_index/types.ts +2 -2
  107. package/src/utils/base64.ts +30 -1
  108. package/src/utils/ranges.ts +1 -1
@@ -27,6 +27,7 @@ import type {
27
27
  } from "../../manifest";
28
28
  import type { IReadOnlyPlaybackObserver } from "../../playback_observer";
29
29
  import type { IPlayerError } from "../../public_types";
30
+ import arrayIncludes from "../../utils/array_includes";
30
31
  import EventEmitter from "../../utils/event_emitter";
31
32
  import isNullOrUndefined from "../../utils/is_null_or_undefined";
32
33
  import queueMicrotask from "../../utils/queue_microtask";
@@ -92,6 +93,15 @@ export default class ContentTimeBoundariesObserver extends EventEmitter<IContent
92
93
  * whole content.
93
94
  */
94
95
  const maximumPositionCalculator = new MaximumPositionCalculator(manifest);
96
+
97
+ // Indicate directly that no Adaptation of a particular type will be set
98
+ if (!arrayIncludes(this._allBufferTypes, "video")) {
99
+ maximumPositionCalculator.updateLastVideoAdaptation(null);
100
+ }
101
+ if (!arrayIncludes(this._allBufferTypes, "audio")) {
102
+ maximumPositionCalculator.updateLastAudioAdaptation(null);
103
+ }
104
+
95
105
  this._maximumPositionCalculator = maximumPositionCalculator;
96
106
 
97
107
  const cancelSignal = this._canceller.signal;
@@ -0,0 +1,592 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import type { IAdaptation, IPeriod } from "../../../../manifest";
3
+ import type { IReadOnlyPlaybackObserver } from "../../../../playback_observer";
4
+ import type { SegmentSink } from "../../../segment_sinks";
5
+ import { SegmentSinkOperation } from "../../../segment_sinks";
6
+ import getRepresentationsSwitchingStrategy from "../get_representations_switch_strategy";
7
+
8
+ /* eslint-disable @typescript-eslint/no-explicit-any */
9
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
10
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
11
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
12
+
13
+ // Mock dependencies
14
+ vi.mock("../../../config", () => ({
15
+ default: {
16
+ getCurrent: vi.fn(() => ({
17
+ ADAP_REP_SWITCH_BUFFER_PADDINGS: {
18
+ video: { before: 0.5, after: 0.5 },
19
+ audio: { before: 0.5, after: 0.5 },
20
+ text: { before: 0, after: 0 },
21
+ },
22
+ })),
23
+ },
24
+ }));
25
+
26
+ describe("getRepresentationsSwitchingStrategy", () => {
27
+ let mockPeriod: IPeriod;
28
+ let mockAdaptation: IAdaptation;
29
+ let mockSegmentSink: any;
30
+ let mockPlaybackObserver: any;
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+
35
+ mockPeriod = {
36
+ id: "period-1",
37
+ start: 0,
38
+ end: 100,
39
+ } as IPeriod;
40
+
41
+ mockAdaptation = {
42
+ id: "adaptation-1",
43
+ type: "video",
44
+ } as IAdaptation;
45
+
46
+ mockSegmentSink = {
47
+ getLastKnownInventory: vi.fn(() => []),
48
+ getPendingOperations: vi.fn(() => []),
49
+ } as unknown as SegmentSink;
50
+
51
+ mockPlaybackObserver = {
52
+ getReadyState: vi.fn(() => 4),
53
+ getCurrentTime: vi.fn(() => 10),
54
+ getReference: vi.fn(() => ({
55
+ getValue: () => ({
56
+ position: {
57
+ getPolled: () => 10,
58
+ },
59
+ }),
60
+ })),
61
+ } as unknown as IReadOnlyPlaybackObserver<any>;
62
+ });
63
+
64
+ describe("lazy switching mode", () => {
65
+ it("should return continue when switching mode is lazy", () => {
66
+ const settings = {
67
+ switchingMode: "lazy" as const,
68
+ representationIds: ["rep-1"],
69
+ };
70
+
71
+ const result = getRepresentationsSwitchingStrategy(
72
+ mockPeriod,
73
+ mockAdaptation,
74
+ settings,
75
+ mockSegmentSink,
76
+ mockPlaybackObserver,
77
+ );
78
+
79
+ expect(result).toEqual({ type: "continue", value: undefined });
80
+ });
81
+ });
82
+
83
+ describe("no unwanted segments", () => {
84
+ it("should return continue when inventory is empty", () => {
85
+ const settings = {
86
+ switchingMode: "direct" as const,
87
+ representationIds: ["rep-1"],
88
+ };
89
+
90
+ const result = getRepresentationsSwitchingStrategy(
91
+ mockPeriod,
92
+ mockAdaptation,
93
+ settings,
94
+ mockSegmentSink,
95
+ mockPlaybackObserver,
96
+ );
97
+
98
+ expect(result).toEqual({ type: "continue", value: undefined });
99
+ });
100
+
101
+ it("should return continue when all segments match current representation", () => {
102
+ const settings = {
103
+ switchingMode: "direct" as const,
104
+ representationIds: ["rep-1"],
105
+ };
106
+
107
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
108
+ {
109
+ infos: {
110
+ period: { id: "period-1" },
111
+ adaptation: { id: "adaptation-1" },
112
+ representation: { id: "rep-1" },
113
+ },
114
+ bufferedStart: 0,
115
+ bufferedEnd: 10,
116
+ start: 0,
117
+ end: 10,
118
+ },
119
+ ]);
120
+
121
+ const result = getRepresentationsSwitchingStrategy(
122
+ mockPeriod,
123
+ mockAdaptation,
124
+ settings,
125
+ mockSegmentSink,
126
+ mockPlaybackObserver,
127
+ );
128
+
129
+ expect(result).toEqual({ type: "continue", value: undefined });
130
+ });
131
+ });
132
+
133
+ describe("unwanted segments in inventory", () => {
134
+ it("should identify unwanted segments from different representation", () => {
135
+ const settings = {
136
+ switchingMode: "direct" as const,
137
+ representationIds: ["rep-2"],
138
+ };
139
+
140
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
141
+ {
142
+ infos: {
143
+ period: { id: "period-1" },
144
+ adaptation: { id: "adaptation-1" },
145
+ representation: { id: "rep-1" }, // Different representation
146
+ },
147
+ bufferedStart: 0,
148
+ bufferedEnd: 10,
149
+ start: 0,
150
+ end: 10,
151
+ },
152
+ ]);
153
+
154
+ const result = getRepresentationsSwitchingStrategy(
155
+ mockPeriod,
156
+ mockAdaptation,
157
+ settings,
158
+ mockSegmentSink,
159
+ mockPlaybackObserver,
160
+ );
161
+
162
+ expect(result.type).toBe("flush-buffer");
163
+ });
164
+
165
+ it("should identify unwanted segments from different adaptation", () => {
166
+ const settings = {
167
+ switchingMode: "direct" as const,
168
+ representationIds: ["rep-1"],
169
+ };
170
+
171
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
172
+ {
173
+ infos: {
174
+ period: { id: "period-1" },
175
+ adaptation: { id: "adaptation-2" }, // Different adaptation
176
+ representation: { id: "rep-1" },
177
+ },
178
+ bufferedStart: 0,
179
+ bufferedEnd: 10,
180
+ start: 0,
181
+ end: 10,
182
+ },
183
+ ]);
184
+
185
+ const result = getRepresentationsSwitchingStrategy(
186
+ mockPeriod,
187
+ mockAdaptation,
188
+ settings,
189
+ mockSegmentSink,
190
+ mockPlaybackObserver,
191
+ );
192
+
193
+ expect(result.type).toBe("flush-buffer");
194
+ });
195
+
196
+ it("should ignore segments from different periods", () => {
197
+ const settings = {
198
+ switchingMode: "direct" as const,
199
+ representationIds: ["rep-2"],
200
+ };
201
+
202
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
203
+ {
204
+ infos: {
205
+ period: { id: "period-2" }, // Different period
206
+ adaptation: { id: "adaptation-1" },
207
+ representation: { id: "rep-1" },
208
+ },
209
+ bufferedStart: 0,
210
+ bufferedEnd: 10,
211
+ start: 0,
212
+ end: 10,
213
+ },
214
+ ]);
215
+
216
+ const result = getRepresentationsSwitchingStrategy(
217
+ mockPeriod,
218
+ mockAdaptation,
219
+ settings,
220
+ mockSegmentSink,
221
+ mockPlaybackObserver,
222
+ );
223
+
224
+ expect(result).toEqual({ type: "continue", value: undefined });
225
+ });
226
+ });
227
+
228
+ describe("pending operations", () => {
229
+ it("should identify unwanted segments in pending push operations", () => {
230
+ const settings = {
231
+ switchingMode: "direct" as const,
232
+ representationIds: ["rep-2"],
233
+ };
234
+
235
+ mockSegmentSink.getPendingOperations = vi.fn(() => [
236
+ {
237
+ type: SegmentSinkOperation.Push,
238
+ value: {
239
+ inventoryInfos: {
240
+ period: { id: "period-1" },
241
+ adaptation: { id: "adaptation-1" },
242
+ representation: { id: "rep-1" }, // Different representation
243
+ segment: {
244
+ time: 5,
245
+ duration: 5,
246
+ },
247
+ },
248
+ },
249
+ },
250
+ ]);
251
+
252
+ const result = getRepresentationsSwitchingStrategy(
253
+ mockPeriod,
254
+ mockAdaptation,
255
+ settings,
256
+ mockSegmentSink,
257
+ mockPlaybackObserver,
258
+ );
259
+
260
+ expect(result.type).toBe("flush-buffer");
261
+ });
262
+
263
+ it("should ignore non-push pending operations", () => {
264
+ const settings = {
265
+ switchingMode: "direct" as const,
266
+ representationIds: ["rep-2"],
267
+ };
268
+
269
+ mockSegmentSink.getPendingOperations = vi.fn(() => [
270
+ {
271
+ type: "remove",
272
+ value: {},
273
+ },
274
+ ]);
275
+
276
+ const result = getRepresentationsSwitchingStrategy(
277
+ mockPeriod,
278
+ mockAdaptation,
279
+ settings,
280
+ mockSegmentSink,
281
+ mockPlaybackObserver,
282
+ );
283
+
284
+ expect(result).toEqual({ type: "continue", value: undefined });
285
+ });
286
+ });
287
+
288
+ describe("reload switching mode", () => {
289
+ it("should return needs-reload when readyState > 1 and mode is reload", () => {
290
+ const settings = {
291
+ switchingMode: "reload" as const,
292
+ representationIds: ["rep-2"],
293
+ };
294
+
295
+ mockPlaybackObserver.getReadyState = vi.fn(() => 4);
296
+
297
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
298
+ {
299
+ infos: {
300
+ period: { id: "period-1" },
301
+ adaptation: { id: "adaptation-1" },
302
+ representation: { id: "rep-1" },
303
+ },
304
+ bufferedStart: 0,
305
+ bufferedEnd: 10,
306
+ start: 0,
307
+ end: 10,
308
+ },
309
+ ]);
310
+
311
+ const result = getRepresentationsSwitchingStrategy(
312
+ mockPeriod,
313
+ mockAdaptation,
314
+ settings,
315
+ mockSegmentSink,
316
+ mockPlaybackObserver,
317
+ );
318
+
319
+ expect(result).toEqual({ type: "needs-reload", value: undefined });
320
+ });
321
+
322
+ it("should reload when readyState is undefined", () => {
323
+ const settings = {
324
+ switchingMode: "reload" as const,
325
+ representationIds: ["rep-2"],
326
+ };
327
+
328
+ mockPlaybackObserver.getReadyState = vi.fn(() => undefined);
329
+
330
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
331
+ {
332
+ infos: {
333
+ period: { id: "period-1" },
334
+ adaptation: { id: "adaptation-1" },
335
+ representation: { id: "rep-1" },
336
+ },
337
+ bufferedStart: 0,
338
+ bufferedEnd: 10,
339
+ start: 0,
340
+ end: 10,
341
+ },
342
+ ]);
343
+
344
+ const result = getRepresentationsSwitchingStrategy(
345
+ mockPeriod,
346
+ mockAdaptation,
347
+ settings,
348
+ mockSegmentSink,
349
+ mockPlaybackObserver,
350
+ );
351
+
352
+ expect(result.type).toBe("needs-reload");
353
+ });
354
+
355
+ it("should not reload when readyState <= 1", () => {
356
+ const settings = {
357
+ switchingMode: "reload" as const,
358
+ representationIds: ["rep-2"],
359
+ };
360
+
361
+ mockPlaybackObserver.getReadyState = vi.fn(() => 1);
362
+
363
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
364
+ {
365
+ infos: {
366
+ period: { id: "period-1" },
367
+ adaptation: { id: "adaptation-1" },
368
+ representation: { id: "rep-1" },
369
+ },
370
+ bufferedStart: 0,
371
+ bufferedEnd: 10,
372
+ start: 0,
373
+ end: 10,
374
+ },
375
+ ]);
376
+
377
+ const result = getRepresentationsSwitchingStrategy(
378
+ mockPeriod,
379
+ mockAdaptation,
380
+ settings,
381
+ mockSegmentSink,
382
+ mockPlaybackObserver,
383
+ );
384
+
385
+ expect(result.type).not.toBe("needs-reload");
386
+ });
387
+ });
388
+
389
+ describe("direct vs clean-buffer mode", () => {
390
+ it("should return flush-buffer when mode is direct", () => {
391
+ const settings = {
392
+ switchingMode: "direct" as const,
393
+ representationIds: ["rep-2"],
394
+ };
395
+
396
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
397
+ {
398
+ infos: {
399
+ period: { id: "period-1" },
400
+ adaptation: { id: "adaptation-1" },
401
+ representation: { id: "rep-1" },
402
+ },
403
+ bufferedStart: 0,
404
+ bufferedEnd: 10,
405
+ start: 0,
406
+ end: 10,
407
+ },
408
+ ]);
409
+
410
+ const result = getRepresentationsSwitchingStrategy(
411
+ mockPeriod,
412
+ mockAdaptation,
413
+ settings,
414
+ mockSegmentSink,
415
+ mockPlaybackObserver,
416
+ );
417
+
418
+ expect(result.type).toBe("flush-buffer");
419
+ });
420
+
421
+ it("should return clean-buffer when mode is not direct or reload", () => {
422
+ const settings = {
423
+ switchingMode: "seamless" as any,
424
+ representationIds: ["rep-2"],
425
+ };
426
+
427
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
428
+ {
429
+ infos: {
430
+ period: { id: "period-1" },
431
+ adaptation: { id: "adaptation-1" },
432
+ representation: { id: "rep-1" },
433
+ },
434
+ bufferedStart: 0,
435
+ bufferedEnd: 10,
436
+ start: 0,
437
+ end: 10,
438
+ },
439
+ ]);
440
+
441
+ const result = getRepresentationsSwitchingStrategy(
442
+ mockPeriod,
443
+ mockAdaptation,
444
+ settings,
445
+ mockSegmentSink,
446
+ mockPlaybackObserver,
447
+ );
448
+
449
+ expect(result.type).toBe("clean-buffer");
450
+ });
451
+ });
452
+
453
+ describe("getCurrentTime fallback", () => {
454
+ it("should use getPolled when getCurrentTime returns undefined", () => {
455
+ const settings = {
456
+ switchingMode: "seamless" as any,
457
+ representationIds: ["rep-2"],
458
+ };
459
+
460
+ const getPolledMock = vi.fn(() => 15);
461
+ mockPlaybackObserver.getCurrentTime = vi.fn(() => undefined);
462
+ mockPlaybackObserver.getReference = vi.fn(() => ({
463
+ getValue: () => ({
464
+ position: {
465
+ getPolled: getPolledMock,
466
+ },
467
+ }),
468
+ }));
469
+
470
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
471
+ {
472
+ infos: {
473
+ period: { id: "period-1" },
474
+ adaptation: { id: "adaptation-1" },
475
+ representation: { id: "rep-1" },
476
+ },
477
+ bufferedStart: 0,
478
+ bufferedEnd: 10,
479
+ start: 0,
480
+ end: 10,
481
+ },
482
+ ]);
483
+
484
+ getRepresentationsSwitchingStrategy(
485
+ mockPeriod,
486
+ mockAdaptation,
487
+ settings,
488
+ mockSegmentSink,
489
+ mockPlaybackObserver,
490
+ );
491
+
492
+ expect(getPolledMock).toHaveBeenCalled();
493
+ });
494
+ });
495
+
496
+ describe("segment buffered time handling", () => {
497
+ it("should use bufferedStart/bufferedEnd when available", () => {
498
+ const settings = {
499
+ switchingMode: "direct" as const,
500
+ representationIds: ["rep-2"],
501
+ };
502
+
503
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
504
+ {
505
+ infos: {
506
+ period: { id: "period-1" },
507
+ adaptation: { id: "adaptation-1" },
508
+ representation: { id: "rep-1" },
509
+ },
510
+ bufferedStart: 5,
511
+ bufferedEnd: 15,
512
+ start: 0,
513
+ end: 20,
514
+ },
515
+ ]);
516
+
517
+ const result = getRepresentationsSwitchingStrategy(
518
+ mockPeriod,
519
+ mockAdaptation,
520
+ settings,
521
+ mockSegmentSink,
522
+ mockPlaybackObserver,
523
+ );
524
+
525
+ expect(result.type).toBe("flush-buffer");
526
+ });
527
+
528
+ it("should fallback to start/end when bufferedStart/bufferedEnd is undefined", () => {
529
+ const settings = {
530
+ switchingMode: "direct" as const,
531
+ representationIds: ["rep-2"],
532
+ };
533
+
534
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
535
+ {
536
+ infos: {
537
+ period: { id: "period-1" },
538
+ adaptation: { id: "adaptation-1" },
539
+ representation: { id: "rep-1" },
540
+ },
541
+ bufferedStart: undefined,
542
+ bufferedEnd: undefined,
543
+ start: 0,
544
+ end: 10,
545
+ },
546
+ ]);
547
+
548
+ const result = getRepresentationsSwitchingStrategy(
549
+ mockPeriod,
550
+ mockAdaptation,
551
+ settings,
552
+ mockSegmentSink,
553
+ mockPlaybackObserver,
554
+ );
555
+
556
+ expect(result.type).toBe("flush-buffer");
557
+ });
558
+ });
559
+
560
+ describe("multiple representation IDs", () => {
561
+ it("should not mark segments as unwanted if they match any of the representation IDs", () => {
562
+ const settings = {
563
+ switchingMode: "direct" as const,
564
+ representationIds: ["rep-1", "rep-2", "rep-3"],
565
+ };
566
+
567
+ mockSegmentSink.getLastKnownInventory = vi.fn(() => [
568
+ {
569
+ infos: {
570
+ period: { id: "period-1" },
571
+ adaptation: { id: "adaptation-1" },
572
+ representation: { id: "rep-2" },
573
+ },
574
+ bufferedStart: 0,
575
+ bufferedEnd: 10,
576
+ start: 0,
577
+ end: 10,
578
+ },
579
+ ]);
580
+
581
+ const result = getRepresentationsSwitchingStrategy(
582
+ mockPeriod,
583
+ mockAdaptation,
584
+ settings,
585
+ mockSegmentSink,
586
+ mockPlaybackObserver,
587
+ );
588
+
589
+ expect(result).toEqual({ type: "continue", value: undefined });
590
+ });
591
+ });
592
+ });