rx-player 4.5.0-dev.2026012302 → 4.5.0-dev.2026031700

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 (180) hide show
  1. package/CHANGELOG.md +21 -3
  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/cmcd/cmcd_data_builder.js +2 -2
  11. package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
  12. package/dist/commonjs/core/entry/content_preparer.d.ts +1 -6
  13. package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
  14. package/dist/commonjs/core/entry/content_preparer.js +2 -125
  15. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  16. package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
  17. package/dist/commonjs/core/entry/core_entry.d.ts +1 -1
  18. package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
  19. package/dist/commonjs/core/entry/core_entry.js +2 -7
  20. package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +1 -1
  21. package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -1
  22. package/dist/commonjs/core/entry/index.d.ts +1 -1
  23. package/dist/commonjs/core/entry/index.d.ts.map +1 -1
  24. package/dist/commonjs/core/entry/utils.d.ts +64 -0
  25. package/dist/commonjs/core/entry/utils.d.ts.map +1 -1
  26. package/dist/commonjs/core/entry/utils.js +165 -0
  27. package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts +5 -6
  28. package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  29. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
  30. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +34 -30
  31. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  32. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  33. package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
  34. package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
  35. package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
  36. package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
  37. package/dist/commonjs/default_config.d.ts +5 -0
  38. package/dist/commonjs/default_config.d.ts.map +1 -1
  39. package/dist/commonjs/default_config.js +5 -0
  40. package/dist/commonjs/experimental/features/worker/index.d.ts +2 -1
  41. package/dist/commonjs/experimental/features/worker/index.d.ts.map +1 -1
  42. package/dist/commonjs/experimental/features/worker/index.js +4 -3
  43. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  44. package/dist/commonjs/main_thread/api/public_api.js +28 -4
  45. package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  46. package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
  47. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  48. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
  49. package/dist/commonjs/manifest/classes/index.d.ts +2 -1
  50. package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
  51. package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
  52. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  53. package/dist/commonjs/manifest/classes/representation.js +2 -2
  54. package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
  55. package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
  56. package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
  57. package/dist/commonjs/utils/base64.d.ts +15 -1
  58. package/dist/commonjs/utils/base64.d.ts.map +1 -1
  59. package/dist/commonjs/utils/base64.js +6 -0
  60. package/dist/commonjs/utils/ranges.d.ts +1 -1
  61. package/dist/commonjs/utils/ranges.js +1 -1
  62. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  63. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  64. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  65. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  66. package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
  67. package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
  68. package/dist/es2017/compat/should_seek_at_zero.js +20 -0
  69. package/dist/es2017/core/cmcd/cmcd_data_builder.js +2 -2
  70. package/dist/es2017/core/entry/FreezeResolver.js +1 -1
  71. package/dist/es2017/core/entry/content_preparer.d.ts +1 -6
  72. package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
  73. package/dist/es2017/core/entry/content_preparer.js +1 -76
  74. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  75. package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
  76. package/dist/es2017/core/entry/core_entry.d.ts +1 -1
  77. package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
  78. package/dist/es2017/core/entry/core_entry.js +2 -7
  79. package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +1 -1
  80. package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -1
  81. package/dist/es2017/core/entry/index.d.ts +1 -1
  82. package/dist/es2017/core/entry/index.d.ts.map +1 -1
  83. package/dist/es2017/core/entry/utils.d.ts +64 -0
  84. package/dist/es2017/core/entry/utils.d.ts.map +1 -1
  85. package/dist/es2017/core/entry/utils.js +98 -0
  86. package/dist/es2017/core/fetchers/segment/segment_queue.d.ts +5 -6
  87. package/dist/es2017/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  88. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
  89. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +21 -16
  90. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  91. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  92. package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
  93. package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
  94. package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
  95. package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
  96. package/dist/es2017/default_config.d.ts +5 -0
  97. package/dist/es2017/default_config.d.ts.map +1 -1
  98. package/dist/es2017/default_config.js +5 -0
  99. package/dist/es2017/experimental/features/worker/index.d.ts +2 -1
  100. package/dist/es2017/experimental/features/worker/index.d.ts.map +1 -1
  101. package/dist/es2017/experimental/features/worker/index.js +2 -3
  102. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  103. package/dist/es2017/main_thread/api/public_api.js +28 -4
  104. package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  105. package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
  106. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  107. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
  108. package/dist/es2017/manifest/classes/index.d.ts +2 -1
  109. package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
  110. package/dist/es2017/manifest/classes/representation.d.ts +1 -1
  111. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  112. package/dist/es2017/manifest/classes/representation.js +1 -1
  113. package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
  114. package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
  115. package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
  116. package/dist/es2017/utils/base64.d.ts +15 -1
  117. package/dist/es2017/utils/base64.d.ts.map +1 -1
  118. package/dist/es2017/utils/base64.js +6 -0
  119. package/dist/es2017/utils/ranges.d.ts +1 -1
  120. package/dist/es2017/utils/ranges.js +1 -1
  121. package/dist/mpd-parser.wasm +0 -0
  122. package/dist/worker.js +8 -8
  123. package/package.json +26 -23
  124. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  125. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  126. package/src/compat/should_seek_at_zero.ts +23 -0
  127. package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +734 -0
  128. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +793 -0
  129. package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
  130. package/src/core/adaptive/utils/__tests__/bandwidth_estimator.test.ts +117 -0
  131. package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +76 -0
  132. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +288 -0
  133. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +249 -0
  134. package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +464 -0
  135. package/src/core/cmcd/cmcd_data_builder.ts +2 -2
  136. package/src/core/entry/FreezeResolver.ts +1 -1
  137. package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
  138. package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +867 -0
  139. package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +205 -0
  140. package/src/core/entry/__tests__/get_buffered_data_per_media_buffer.test.ts +95 -0
  141. package/src/core/entry/__tests__/track_choice_setter.test.ts +288 -0
  142. package/src/core/entry/__tests__/utils.test.ts +309 -0
  143. package/src/core/entry/content_preparer.ts +7 -118
  144. package/src/core/entry/content_time_boundaries_observer.ts +10 -0
  145. package/src/core/entry/core_entry.ts +7 -14
  146. package/src/core/entry/core_text_displayer_interface.ts +3 -1
  147. package/src/core/entry/index.ts +1 -1
  148. package/src/core/entry/utils.ts +151 -0
  149. package/src/core/fetchers/__tests__/cdn_prioritizer.test.ts +166 -0
  150. package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +803 -0
  151. package/src/core/fetchers/segment/__tests__/segment_fetcher.test.ts +872 -0
  152. package/src/core/fetchers/segment/__tests__/segment_queue.test.ts +557 -0
  153. package/src/core/fetchers/segment/segment_queue.ts +5 -6
  154. package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +435 -0
  155. package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +28 -20
  156. package/src/core/fetchers/utils/__tests__/schedule_request.test.ts +331 -0
  157. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +593 -0
  158. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
  159. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
  160. package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
  161. package/src/core/stream/representation/representation_stream.ts +11 -49
  162. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
  163. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
  164. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
  165. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
  166. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
  167. package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
  168. package/src/default_config.ts +6 -0
  169. package/src/experimental/features/worker/index.ts +2 -4
  170. package/src/main_thread/api/public_api.ts +33 -4
  171. package/src/main_thread/init/directfile_content_initializer.ts +26 -1
  172. package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
  173. package/src/manifest/classes/index.ts +2 -0
  174. package/src/manifest/classes/representation.ts +2 -1
  175. package/src/manifest/classes/representation_index/types.ts +2 -2
  176. package/src/mse/__tests__/main_media_source_interface.test.ts +846 -0
  177. package/src/parsers/texttracks/ttml/html/apply_line_height.ts +16 -1
  178. package/src/utils/__tests__/base64.test.ts +159 -191
  179. package/src/utils/base64.ts +30 -1
  180. 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
+ });