rx-player 4.5.0-dev.2026033100 → 4.5.0-dev.2026041501

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 (251) hide show
  1. package/CHANGELOG.md +22 -5
  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/can_patch_out_pssh.d.ts +42 -0
  8. package/dist/commonjs/compat/can_patch_out_pssh.d.ts.map +1 -0
  9. package/dist/commonjs/compat/can_patch_out_pssh.js +53 -0
  10. package/dist/commonjs/compat/env_detector.d.ts +2 -0
  11. package/dist/commonjs/compat/env_detector.d.ts.map +1 -1
  12. package/dist/commonjs/compat/env_detector.js +5 -0
  13. package/dist/commonjs/core/adaptive/network_analyzer.d.ts +1 -2
  14. package/dist/commonjs/core/adaptive/network_analyzer.d.ts.map +1 -1
  15. package/dist/commonjs/core/adaptive/network_analyzer.js +3 -3
  16. package/dist/commonjs/core/adaptive/utils/representation_score_calculator.js +2 -2
  17. package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  18. package/dist/commonjs/core/cmcd/cmcd_data_builder.js +9 -3
  19. package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
  20. package/dist/commonjs/core/entry/content_preparer.js +5 -7
  21. package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
  22. package/dist/commonjs/core/entry/core_entry.js +6 -2
  23. package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -3
  24. package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
  25. package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.js +12 -0
  26. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
  27. package/dist/commonjs/core/fetchers/utils/schedule_request.d.ts.map +1 -1
  28. package/dist/commonjs/core/fetchers/utils/schedule_request.js +4 -3
  29. package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts +0 -2
  30. package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts.map +1 -1
  31. package/dist/commonjs/core/segment_sinks/garbage_collector.js +0 -3
  32. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
  33. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
  34. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
  35. package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  36. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +6 -6
  37. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  38. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +37 -40
  39. package/dist/commonjs/default_config.d.ts +5 -0
  40. package/dist/commonjs/default_config.d.ts.map +1 -1
  41. package/dist/commonjs/default_config.js +5 -0
  42. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  43. package/dist/commonjs/main_thread/api/public_api.js +23 -16
  44. package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  45. package/dist/commonjs/main_thread/decrypt/content_decryptor.js +4 -1
  46. package/dist/commonjs/main_thread/decrypt/session_events_listener.js +1 -1
  47. package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts +2 -0
  48. package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
  49. package/dist/commonjs/main_thread/decrypt/set_server_certificate.js +4 -0
  50. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +0 -8
  51. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  52. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +58 -50
  53. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
  54. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
  55. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
  56. package/dist/commonjs/main_thread/render_thumbnail.d.ts.map +1 -1
  57. package/dist/commonjs/main_thread/render_thumbnail.js +4 -0
  58. package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
  59. package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.js +1 -0
  60. package/dist/commonjs/main_thread/tracks_store/tracks_store.d.ts +1 -1
  61. package/dist/commonjs/main_thread/tracks_store/tracks_store.js +1 -1
  62. package/dist/{es2017/parsers/containers/isobmff/take_pssh_out.d.ts → commonjs/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
  63. package/dist/commonjs/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
  64. package/dist/commonjs/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +22 -17
  65. package/dist/commonjs/parsers/containers/isobmff/index.d.ts +2 -2
  66. package/dist/commonjs/parsers/containers/isobmff/index.d.ts.map +1 -1
  67. package/dist/commonjs/parsers/containers/isobmff/index.js +4 -4
  68. package/dist/commonjs/playback_observer/core_playback_observer.d.ts +4 -4
  69. package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +1 -1
  70. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts +1 -2
  71. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  72. package/dist/commonjs/transports/dash/segment_parser.js +1 -1
  73. package/dist/commonjs/transports/local/segment_parser.js +1 -1
  74. package/dist/commonjs/utils/test-utils.d.ts +30 -0
  75. package/dist/commonjs/utils/test-utils.d.ts.map +1 -0
  76. package/dist/commonjs/utils/test-utils.js +79 -0
  77. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  78. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  79. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  80. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  81. package/dist/es2017/compat/can_patch_out_pssh.d.ts +42 -0
  82. package/dist/es2017/compat/can_patch_out_pssh.d.ts.map +1 -0
  83. package/dist/es2017/compat/can_patch_out_pssh.js +50 -0
  84. package/dist/es2017/compat/env_detector.d.ts +2 -0
  85. package/dist/es2017/compat/env_detector.d.ts.map +1 -1
  86. package/dist/es2017/compat/env_detector.js +5 -0
  87. package/dist/es2017/core/adaptive/network_analyzer.d.ts +1 -2
  88. package/dist/es2017/core/adaptive/network_analyzer.d.ts.map +1 -1
  89. package/dist/es2017/core/adaptive/network_analyzer.js +3 -3
  90. package/dist/es2017/core/adaptive/utils/representation_score_calculator.js +2 -2
  91. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  92. package/dist/es2017/core/cmcd/cmcd_data_builder.js +9 -3
  93. package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
  94. package/dist/es2017/core/entry/content_preparer.js +3 -5
  95. package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
  96. package/dist/es2017/core/entry/core_entry.js +6 -2
  97. package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -3
  98. package/dist/es2017/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
  99. package/dist/es2017/core/fetchers/manifest/manifest_fetcher.js +12 -0
  100. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
  101. package/dist/es2017/core/fetchers/utils/schedule_request.d.ts.map +1 -1
  102. package/dist/es2017/core/fetchers/utils/schedule_request.js +2 -3
  103. package/dist/es2017/core/segment_sinks/garbage_collector.d.ts +0 -2
  104. package/dist/es2017/core/segment_sinks/garbage_collector.d.ts.map +1 -1
  105. package/dist/es2017/core/segment_sinks/garbage_collector.js +0 -3
  106. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
  107. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
  108. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
  109. package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  110. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +6 -6
  111. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  112. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.js +40 -39
  113. package/dist/es2017/default_config.d.ts +5 -0
  114. package/dist/es2017/default_config.d.ts.map +1 -1
  115. package/dist/es2017/default_config.js +5 -0
  116. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  117. package/dist/es2017/main_thread/api/public_api.js +20 -13
  118. package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  119. package/dist/es2017/main_thread/decrypt/content_decryptor.js +4 -1
  120. package/dist/es2017/main_thread/decrypt/session_events_listener.js +1 -1
  121. package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts +2 -0
  122. package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
  123. package/dist/es2017/main_thread/decrypt/set_server_certificate.js +4 -0
  124. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +0 -8
  125. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  126. package/dist/es2017/main_thread/init/media_source_content_initializer.js +58 -50
  127. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
  128. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
  129. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
  130. package/dist/es2017/main_thread/render_thumbnail.d.ts.map +1 -1
  131. package/dist/es2017/main_thread/render_thumbnail.js +4 -0
  132. package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
  133. package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.js +1 -0
  134. package/dist/es2017/main_thread/tracks_store/tracks_store.d.ts +1 -1
  135. package/dist/es2017/main_thread/tracks_store/tracks_store.js +1 -1
  136. package/dist/{commonjs/parsers/containers/isobmff/take_pssh_out.d.ts → es2017/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
  137. package/dist/es2017/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
  138. package/dist/es2017/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +21 -16
  139. package/dist/es2017/parsers/containers/isobmff/index.d.ts +2 -2
  140. package/dist/es2017/parsers/containers/isobmff/index.d.ts.map +1 -1
  141. package/dist/es2017/parsers/containers/isobmff/index.js +2 -2
  142. package/dist/es2017/playback_observer/core_playback_observer.d.ts +4 -4
  143. package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +1 -1
  144. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts +1 -2
  145. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  146. package/dist/es2017/transports/dash/segment_parser.js +2 -2
  147. package/dist/es2017/transports/local/segment_parser.js +2 -2
  148. package/dist/es2017/utils/test-utils.d.ts +30 -0
  149. package/dist/es2017/utils/test-utils.d.ts.map +1 -0
  150. package/dist/es2017/utils/test-utils.js +36 -0
  151. package/dist/mpd-parser.wasm +0 -0
  152. package/dist/worker.js +6 -6
  153. package/package.json +4 -2
  154. package/src/README.md +7 -7
  155. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  156. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  157. package/src/compat/__tests__/can_patch_out_pssh.test.ts +40 -0
  158. package/src/compat/can_patch_out_pssh.ts +53 -0
  159. package/src/compat/env_detector.ts +4 -0
  160. package/src/core/README.md +1 -1
  161. package/src/core/adaptive/README.md +3 -3
  162. package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +181 -110
  163. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +229 -123
  164. package/src/core/adaptive/__tests__/mocks.ts +100 -0
  165. package/src/core/adaptive/__tests__/network_analyzer.test.ts +152 -59
  166. package/src/core/adaptive/network_analyzer.ts +4 -4
  167. package/src/core/adaptive/utils/__tests__/filter_by_bitrate.test.ts +11 -19
  168. package/src/core/adaptive/utils/__tests__/filter_by_resolution.test.ts +12 -12
  169. package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +25 -12
  170. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +13 -9
  171. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +11 -11
  172. package/src/core/adaptive/utils/__tests__/select_optimal_representation.test.ts +13 -23
  173. package/src/core/adaptive/utils/representation_score_calculator.ts +2 -2
  174. package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +60 -15
  175. package/src/core/cmcd/cmcd_data_builder.ts +9 -3
  176. package/src/core/entry/README.md +2 -2
  177. package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +20 -0
  178. package/src/core/entry/content_preparer.ts +2 -5
  179. package/src/core/entry/core_entry.ts +6 -2
  180. package/src/core/entry/core_text_displayer_interface.ts +3 -3
  181. package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +52 -3
  182. package/src/core/fetchers/manifest/manifest_fetcher.ts +12 -0
  183. package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +70 -0
  184. package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +1 -1
  185. package/src/core/fetchers/utils/schedule_request.ts +5 -3
  186. package/src/core/segment_sinks/__tests__/garbage_collector.test.ts +434 -0
  187. package/src/core/segment_sinks/__tests__/mocks.ts +49 -0
  188. package/src/core/segment_sinks/garbage_collector.ts +0 -3
  189. package/src/core/segment_sinks/implementations/text/__tests__/text_segment_sink.test.ts +177 -0
  190. package/src/core/segment_sinks/implementations/text/text_segment_sink.ts +2 -2
  191. package/src/core/segment_sinks/inventory/__tests__/buffered_history.test.ts +215 -0
  192. package/src/core/segment_sinks/inventory/__tests__/segment_inventory.test.ts +448 -0
  193. package/src/core/stream/adaptation/__tests__/adaptation_stream.test.ts +973 -0
  194. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +283 -374
  195. package/src/core/stream/adaptation/adaptation_stream.ts +6 -8
  196. package/src/core/stream/orchestrator/README.md +4 -4
  197. package/src/core/stream/orchestrator/__tests__/stream_orchestrator.test.ts +707 -0
  198. package/src/core/stream/orchestrator/stream_orchestrator.ts +41 -46
  199. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +290 -220
  200. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +93 -63
  201. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +106 -63
  202. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +179 -204
  203. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +7 -7
  204. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +103 -60
  205. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +231 -165
  206. package/src/default_config.ts +6 -0
  207. package/src/experimental/README.md +1 -1
  208. package/src/features/README.md +3 -3
  209. package/src/main_thread/api/README.md +6 -7
  210. package/src/main_thread/api/public_api.ts +16 -10
  211. package/src/main_thread/decrypt/README.md +4 -4
  212. package/src/main_thread/decrypt/__tests__/__global__/content_decryptor.test.ts +135 -0
  213. package/src/main_thread/decrypt/__tests__/__global__/get_license.test.ts +70 -0
  214. package/src/main_thread/decrypt/__tests__/__global__/server_certificate.test.ts +44 -0
  215. package/src/main_thread/decrypt/__tests__/__global__/utils.ts +2 -2
  216. package/src/main_thread/decrypt/content_decryptor.ts +6 -1
  217. package/src/main_thread/decrypt/session_events_listener.ts +1 -1
  218. package/src/main_thread/decrypt/set_server_certificate.ts +5 -0
  219. package/src/main_thread/init/media_source_content_initializer.ts +69 -55
  220. package/src/main_thread/init/utils/__tests__/stream_events_emitter.test.ts +175 -0
  221. package/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts +90 -26
  222. package/src/main_thread/render_thumbnail.ts +4 -0
  223. package/src/main_thread/tracks_store/README.md +12 -0
  224. package/src/main_thread/tracks_store/__tests__/media_element_tracks_store.test.ts +25 -18
  225. package/src/main_thread/tracks_store/media_element_tracks_store.ts +1 -0
  226. package/src/main_thread/tracks_store/tracks_store.ts +1 -1
  227. package/src/manifest/classes/__tests__/mocks.ts +202 -0
  228. package/src/parsers/containers/isobmff/__tests__/extract_pssh.test.ts +199 -0
  229. package/src/parsers/containers/isobmff/{take_pssh_out.ts → extract_pssh.ts} +21 -17
  230. package/src/parsers/containers/isobmff/index.ts +2 -2
  231. package/src/parsers/manifest/dash/wasm-parser/README.md +9 -9
  232. package/src/playback_observer/__tests__/mocks.ts +152 -0
  233. package/src/playback_observer/core_playback_observer.ts +4 -4
  234. package/src/playback_observer/media_element_playback_observer.ts +1 -1
  235. package/src/tools/README.md +2 -2
  236. package/src/transports/README.md +5 -5
  237. package/src/transports/dash/segment_parser.ts +2 -2
  238. package/src/transports/local/segment_parser.ts +2 -2
  239. package/src/transports/metaplaylist/README.md +4 -4
  240. package/src/utils/README.md +3 -3
  241. package/src/utils/test-utils.ts +50 -0
  242. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
  243. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  244. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.js +0 -20
  245. package/dist/commonjs/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
  246. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
  247. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  248. package/dist/es2017/core/entry/synchronize_sinks_on_observation.js +0 -17
  249. package/dist/es2017/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
  250. package/src/core/adaptive/utils/__tests__/bandwith_estimator.test.ts +0 -117
  251. package/src/core/entry/synchronize_sinks_on_observation.ts +0 -22
@@ -0,0 +1,448 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import configHandler from "../../../../config";
3
+ import logger from "../../../../log";
4
+ import {
5
+ DummyAdaptation,
6
+ DummyPeriod,
7
+ DummyRepresentation,
8
+ createSegment,
9
+ } from "../../../../manifest/classes/__tests__/mocks";
10
+ import BufferedHistory from "../buffered_history";
11
+ import SegmentInventory, { ChunkStatus } from "../segment_inventory";
12
+ import type { IInsertedChunkInfos } from "../segment_inventory";
13
+
14
+ const { mockGetMonotonicTimeStamp } = vi.hoisted(() => {
15
+ return { mockGetMonotonicTimeStamp: vi.fn().mockReturnValue(1000) };
16
+ });
17
+
18
+ vi.mock("../../../../utils/monotonic_timestamp", () => ({
19
+ default: mockGetMonotonicTimeStamp,
20
+ }));
21
+
22
+ function makeChunkInfos(
23
+ overrides: Partial<IInsertedChunkInfos> = {},
24
+ segmentId: string,
25
+ ): IInsertedChunkInfos {
26
+ return {
27
+ adaptation: new DummyAdaptation(),
28
+ period: new DummyPeriod(),
29
+ representation: new DummyRepresentation(),
30
+ segment: createSegment({ id: segmentId, isInit: false, complete: true }),
31
+ chunkSize: 1000,
32
+ start: 0,
33
+ end: 10,
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ function spyConfig(overrides: Record<string, unknown> = {}) {
39
+ const originalConfig = configHandler.getCurrent();
40
+ vi.spyOn(configHandler, "getCurrent").mockReturnValue({
41
+ ...originalConfig,
42
+ MINIMUM_SEGMENT_SIZE: 0.2,
43
+ SEGMENT_SYNCHRONIZATION_DELAY: 500,
44
+ MISSING_DATA_TRIGGER_SYNC_DELAY: 0.1,
45
+ MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE: 0.5,
46
+ MAX_MANIFEST_BUFFERED_DURATION_DIFFERENCE: 0.5,
47
+ BUFFERED_HISTORY_RETENTION_TIME: 60000,
48
+ BUFFERED_HISTORY_MAXIMUM_ENTRIES: 100,
49
+ ...overrides,
50
+ });
51
+ }
52
+
53
+ describe("SegmentInventory", () => {
54
+ const mockGetHistoryFor = vi.fn().mockReturnValue([]);
55
+ const mockAddBufferedSegment = vi.fn();
56
+ beforeEach(() => {
57
+ vi.spyOn(logger, "debug").mockImplementation(vi.fn());
58
+ vi.spyOn(logger, "warn").mockImplementation(vi.fn());
59
+ vi.spyOn(logger, "info").mockImplementation(vi.fn());
60
+ vi.spyOn(logger, "error").mockImplementation(vi.fn());
61
+ vi.spyOn(BufferedHistory.prototype, "getHistoryFor").mockImplementation(
62
+ mockGetHistoryFor,
63
+ );
64
+ vi.spyOn(BufferedHistory.prototype, "addBufferedSegment").mockImplementation(
65
+ mockAddBufferedSegment,
66
+ );
67
+ spyConfig();
68
+ });
69
+ afterEach(() => {
70
+ vi.resetAllMocks();
71
+ spyConfig();
72
+ });
73
+
74
+ describe("reset()", () => {
75
+ it("clears all chunks from the inventory", () => {
76
+ const inv = new SegmentInventory();
77
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 100);
78
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 10 }, "2"), true, 200);
79
+ expect(inv.getInventory()).toHaveLength(2);
80
+ inv.reset();
81
+ expect(inv.getInventory()).toHaveLength(0);
82
+ });
83
+ });
84
+
85
+ describe("insertChunk()", () => {
86
+ it("does nothing for init segments", () => {
87
+ const inv = new SegmentInventory();
88
+ const infos = makeChunkInfos(
89
+ {
90
+ segment: createSegment({ isInit: true, id: "1" }),
91
+ },
92
+ "1",
93
+ );
94
+ inv.insertChunk(infos, true, 100);
95
+ expect(inv.getInventory()).toHaveLength(0);
96
+ });
97
+
98
+ it("discards chunks where start >= end", () => {
99
+ const inv = new SegmentInventory();
100
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 5 }, "1"), true, 100);
101
+ inv.insertChunk(makeChunkInfos({ start: 10, end: 5 }, "2"), true, 100);
102
+ expect(inv.getInventory()).toHaveLength(0);
103
+ });
104
+
105
+ it("inserts the first chunk correctly", () => {
106
+ const inv = new SegmentInventory();
107
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 100);
108
+ const chunks = inv.getInventory();
109
+ expect(chunks).toHaveLength(1);
110
+ expect(chunks[0].start).toBe(0);
111
+ expect(chunks[0].end).toBe(10);
112
+ expect(chunks[0].status).toBe(ChunkStatus.PartiallyPushed);
113
+ });
114
+
115
+ it("marks failed chunks with ChunkStatus.Failed", () => {
116
+ const inv = new SegmentInventory();
117
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), false, 100);
118
+ expect(inv.getInventory()[0].status).toBe(ChunkStatus.Failed);
119
+ });
120
+
121
+ it("appends a chunk strictly after an existing one (no overlap)", () => {
122
+ const inv = new SegmentInventory();
123
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 100);
124
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 10 }, "2"), true, 200);
125
+ const chunks = inv.getInventory();
126
+ expect(chunks).toHaveLength(2);
127
+ expect(chunks[0].start).toBe(0);
128
+ expect(chunks[1].start).toBe(5);
129
+ });
130
+
131
+ it("appends a chunk with a gap between", () => {
132
+ const inv = new SegmentInventory();
133
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 100);
134
+ inv.insertChunk(makeChunkInfos({ start: 7, end: 10 }, "2"), true, 200);
135
+ const chunks = inv.getInventory();
136
+ expect(chunks).toHaveLength(2);
137
+ expect(chunks[1].start).toBe(7);
138
+ });
139
+
140
+ it("inserts a chunk before all existing ones", () => {
141
+ const inv = new SegmentInventory();
142
+ inv.insertChunk(makeChunkInfos({ start: 10, end: 20 }, "1"), true, 100);
143
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "2"), true, 200);
144
+ const chunks = inv.getInventory();
145
+ expect(chunks).toHaveLength(2);
146
+ expect(chunks[0].start).toBe(0);
147
+ expect(chunks[1].start).toBe(10);
148
+ });
149
+
150
+ it("replaces an existing chunk with same start and smaller/equal end", () => {
151
+ const inv = new SegmentInventory();
152
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 100);
153
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "2"), true, 200);
154
+ expect(inv.getInventory()).toHaveLength(1);
155
+ expect(inv.getInventory()[0].insertionTs).toBe(200);
156
+ });
157
+
158
+ it("overlapping new chunk updates end of previous segment", () => {
159
+ const inv = new SegmentInventory();
160
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 100);
161
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 15 }, "2"), true, 200);
162
+ const chunks = inv.getInventory();
163
+ expect(chunks).toHaveLength(2);
164
+ expect(chunks[0].end).toBe(5);
165
+ expect(chunks[1].start).toBe(5);
166
+ expect(chunks[1].end).toBe(15);
167
+ });
168
+
169
+ it("new chunk contained in existing one splits the existing", () => {
170
+ const inv = new SegmentInventory();
171
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 20 }, "1"), true, 100);
172
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 10 }, "2"), true, 200);
173
+ const chunks = inv.getInventory();
174
+ expect(chunks).toHaveLength(3);
175
+ expect(chunks[0].start).toBe(0);
176
+ expect(chunks[0].end).toBe(5);
177
+ expect(chunks[1].start).toBe(5);
178
+ expect(chunks[1].end).toBe(10);
179
+ expect(chunks[2].start).toBe(10);
180
+ expect(chunks[2].end).toBe(20);
181
+ expect(chunks[0].splitted).toBe(true);
182
+ expect(chunks[2].splitted).toBe(true);
183
+ });
184
+
185
+ it("new chunk larger than existing one replaces it", () => {
186
+ const inv = new SegmentInventory();
187
+ inv.insertChunk(makeChunkInfos({ start: 2, end: 8 }, "1"), true, 100);
188
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "2"), true, 200);
189
+ const chunks = inv.getInventory();
190
+ expect(chunks).toHaveLength(1);
191
+ expect(chunks[0].start).toBe(0);
192
+ expect(chunks[0].end).toBe(10);
193
+ });
194
+
195
+ it("new chunk overlapping start of first segment trims the first", () => {
196
+ const inv = new SegmentInventory();
197
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 15 }, "1"), true, 100);
198
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 8 }, "2"), true, 200);
199
+ const chunks = inv.getInventory();
200
+ expect(chunks).toHaveLength(2);
201
+ expect(chunks[0].end).toBe(8);
202
+ expect(chunks[1].start).toBe(8);
203
+ });
204
+
205
+ it("new chunk that covers next segment removes the next one", () => {
206
+ const inv = new SegmentInventory();
207
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 100);
208
+ inv.insertChunk(makeChunkInfos({ start: 6, end: 9 }, "2"), true, 200);
209
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 20 }, "3"), true, 300);
210
+ const chunks = inv.getInventory();
211
+ expect(chunks).toHaveLength(1);
212
+ expect(chunks[0].start).toBe(0);
213
+ expect(chunks[0].end).toBe(20);
214
+ });
215
+ });
216
+
217
+ describe("completeSegment()", () => {
218
+ it("does nothing for init segments", () => {
219
+ const inv = new SegmentInventory();
220
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "0"), true, 100);
221
+ const content = {
222
+ adaptation: new DummyAdaptation(),
223
+ period: new DummyPeriod(),
224
+ representation: new DummyRepresentation(),
225
+ segment: createSegment({ isInit: true, id: "0" }),
226
+ };
227
+ inv.completeSegment(content);
228
+ // Still PartiallyPushed because it was ignored
229
+ expect(inv.getInventory()[0].status).toBe(ChunkStatus.PartiallyPushed);
230
+ });
231
+
232
+ it("updates matching chunk status to FullyLoaded", () => {
233
+ const inv = new SegmentInventory();
234
+ const infos = makeChunkInfos({ start: 0, end: 10 }, "1");
235
+ inv.insertChunk(infos, true, 100);
236
+ inv.completeSegment({
237
+ adaptation: infos.adaptation,
238
+ period: infos.period,
239
+ representation: infos.representation,
240
+ segment: infos.segment,
241
+ });
242
+ expect(inv.getInventory()[0].status).toBe(ChunkStatus.FullyLoaded);
243
+ });
244
+
245
+ it("merges multiple partial chunks for the same segment", () => {
246
+ const inv = new SegmentInventory();
247
+ const infos1 = makeChunkInfos({ start: 0, end: 5 }, "1");
248
+ const infos2 = makeChunkInfos({ start: 5, end: 10 }, "1");
249
+ inv.insertChunk(infos1, true, 100);
250
+ inv.insertChunk(infos2, true, 200);
251
+ expect(inv.getInventory()).toHaveLength(2);
252
+
253
+ // mockAreSameContent.mockReturnValue(true);
254
+ inv.completeSegment({
255
+ adaptation: infos1.adaptation,
256
+ period: infos1.period,
257
+ representation: infos1.representation,
258
+ segment: infos1.segment,
259
+ });
260
+ const chunks = inv.getInventory();
261
+ expect(chunks).toHaveLength(1);
262
+ expect(chunks[0].end).toBe(10);
263
+ expect(chunks[0].status).toBe(ChunkStatus.FullyLoaded);
264
+ });
265
+
266
+ it("calls addBufferedSegment when bufferedStart and bufferedEnd are known", () => {
267
+ const inv = new SegmentInventory();
268
+ const infos = makeChunkInfos({ start: 0, end: 10 }, "1");
269
+ inv.insertChunk(infos, true, 100);
270
+
271
+ // Manually set buffered positions to simulate a synchronization
272
+ const chunk = inv.getInventory()[0];
273
+ chunk.bufferedStart = 0;
274
+ chunk.bufferedEnd = 10;
275
+
276
+ // mockAreSameContent.mockReturnValue(true);
277
+ inv.completeSegment({
278
+ adaptation: infos.adaptation,
279
+ period: infos.period,
280
+ representation: infos.representation,
281
+ segment: infos.segment,
282
+ });
283
+ expect(mockAddBufferedSegment).toHaveBeenCalledOnce();
284
+ });
285
+
286
+ it("does not call addBufferedSegment when buffered positions are unknown", () => {
287
+ const inv = new SegmentInventory();
288
+ const infos = makeChunkInfos({ start: 0, end: 10 }, "1");
289
+ inv.insertChunk(infos, true, 100);
290
+ // mockAreSameContent.mockReturnValue(true);
291
+ inv.completeSegment({
292
+ adaptation: infos.adaptation,
293
+ period: infos.period,
294
+ representation: infos.representation,
295
+ segment: infos.segment,
296
+ });
297
+ expect(mockAddBufferedSegment).not.toHaveBeenCalled();
298
+ });
299
+ });
300
+
301
+ describe("synchronizeBuffered()", () => {
302
+ it("does nothing when inventory is empty", () => {
303
+ const inv = new SegmentInventory();
304
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
305
+ expect(inv.getInventory()).toHaveLength(0);
306
+ });
307
+
308
+ it("skips ranges that are too small", () => {
309
+ spyConfig({ MINIMUM_SEGMENT_SIZE: 1 });
310
+ const inv = new SegmentInventory();
311
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 100);
312
+ // Range of 0.1 < MINIMUM_SEGMENT_SIZE of 1
313
+ inv.synchronizeBuffered([{ start: 0, end: 0.1 }]);
314
+ // Chunk should remain since the range was skipped
315
+ expect(inv.getInventory()).toHaveLength(1);
316
+ });
317
+
318
+ it("sets bufferedStart and bufferedEnd for a single segment in a single range", () => {
319
+ const inv = new SegmentInventory();
320
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 0);
321
+ // insertionTs=0, now=1000 => well above SEGMENT_SYNCHRONIZATION_DELAY=500
322
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
323
+ const chunk = inv.getInventory()[0];
324
+ expect(chunk.bufferedStart).not.toBeUndefined();
325
+ expect(chunk.bufferedEnd).not.toBeUndefined();
326
+ });
327
+
328
+ it("garbage collects segments not covered by any range", () => {
329
+ const inv = new SegmentInventory();
330
+ // insertionTs=0, now=1000 => old enough to GC
331
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 0);
332
+ inv.insertChunk(makeChunkInfos({ start: 20, end: 30 }, "2"), true, 0);
333
+ inv.synchronizeBuffered([{ start: 20, end: 30 }]);
334
+ const chunks = inv.getInventory();
335
+ expect(chunks).toHaveLength(1);
336
+ expect(chunks[0].start).toBe(20);
337
+ });
338
+
339
+ it("does not GC segments that are too recently inserted", () => {
340
+ spyConfig({ SEGMENT_SYNCHRONIZATION_DELAY: 5000 });
341
+ // now=1000, insertionTs=500 => delta=500 < 5000
342
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
343
+ const inv = new SegmentInventory();
344
+ inv.insertChunk(makeChunkInfos({ start: 50, end: 60 }, "1"), true, 500);
345
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
346
+ // The segment at 50-60 should NOT be removed yet
347
+ expect(inv.getInventory()).toHaveLength(1);
348
+ });
349
+
350
+ it("updates bufferedStart based on range start", () => {
351
+ const inv = new SegmentInventory();
352
+ inv.insertChunk(makeChunkInfos({ start: 0.1, end: 10 }, "1"), true, 0);
353
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
354
+ expect(inv.getInventory()[0].bufferedStart).toBe(0);
355
+ });
356
+
357
+ it("updates bufferedEnd based on range end", () => {
358
+ const inv = new SegmentInventory();
359
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 0);
360
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
361
+ expect(inv.getInventory()[0].bufferedEnd).toBe(10);
362
+ });
363
+
364
+ it("handles multiple segments in one range (contiguous)", () => {
365
+ const inv = new SegmentInventory();
366
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 0);
367
+ inv.insertChunk(makeChunkInfos({ start: 5, end: 10 }, "2"), true, 0);
368
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
369
+ const chunks = inv.getInventory();
370
+ expect(chunks).toHaveLength(2);
371
+ expect(chunks[0].bufferedEnd).not.toBeUndefined();
372
+ expect(chunks[1].bufferedEnd).toBe(10);
373
+ });
374
+
375
+ it("handles multiple ranges mapping to multiple segments", () => {
376
+ const inv = new SegmentInventory();
377
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), true, 0);
378
+ inv.insertChunk(makeChunkInfos({ start: 10, end: 15 }, "2"), true, 0);
379
+ inv.synchronizeBuffered([
380
+ { start: 0, end: 5 },
381
+ { start: 10, end: 15 },
382
+ ]);
383
+ const chunks = inv.getInventory();
384
+ expect(chunks).toHaveLength(2);
385
+ expect(chunks[0].bufferedEnd).toBe(5);
386
+ expect(chunks[1].bufferedEnd).toBe(15);
387
+ });
388
+
389
+ it("trims bufferedStart when it falls before range start (partial GC at start)", () => {
390
+ const inv = new SegmentInventory();
391
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "1"), true, 0);
392
+ // Manually set an old bufferedStart that now falls before the range
393
+ inv.getInventory()[0].bufferedStart = 0;
394
+ inv.synchronizeBuffered([{ start: 2, end: 10 }]);
395
+ expect(inv.getInventory()[0].bufferedStart).toBe(2);
396
+ });
397
+
398
+ it("trims bufferedEnd when it exceeds range end (partial GC at end)", () => {
399
+ const inv = new SegmentInventory();
400
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 10 }, "2"), true, 0);
401
+ inv.getInventory()[0].bufferedEnd = 12;
402
+ inv.synchronizeBuffered([{ start: 0, end: 10 }]);
403
+ expect(inv.getInventory()[0].bufferedEnd).toBe(10);
404
+ });
405
+
406
+ it("calls addBufferedSegment for GC'd segments that were never synced", () => {
407
+ const inv = new SegmentInventory();
408
+ const chunkInfos = makeChunkInfos({ start: 0, end: 5 }, "3");
409
+ // insertionTs=0, now=1000 => old enough
410
+ inv.insertChunk(chunkInfos, true, 0);
411
+ inv.synchronizeBuffered([{ start: 10, end: 20 }]);
412
+ // The 0-5 chunk was GC'd with no bufferedStart/End => should call addBufferedSegment
413
+ expect(mockAddBufferedSegment).toHaveBeenCalledWith(
414
+ {
415
+ period: chunkInfos.period,
416
+ adaptation: chunkInfos.adaptation,
417
+ representation: chunkInfos.representation,
418
+ segment: chunkInfos.segment,
419
+ },
420
+ null,
421
+ );
422
+ });
423
+
424
+ it("does not call addBufferedSegment for GC'd Failed segments", () => {
425
+ const inv = new SegmentInventory();
426
+ inv.insertChunk(makeChunkInfos({ start: 0, end: 5 }, "1"), false, 0);
427
+ inv.synchronizeBuffered([{ start: 10, end: 20 }]);
428
+ expect(mockAddBufferedSegment).not.toHaveBeenCalled();
429
+ });
430
+ });
431
+
432
+ describe("getHistoryFor()", () => {
433
+ it("delegates to BufferedHistory.getHistoryFor", () => {
434
+ const inv = new SegmentInventory();
435
+ const context = {
436
+ adaptation: new DummyAdaptation(),
437
+ period: new DummyPeriod(),
438
+ representation: new DummyRepresentation(),
439
+ segment: createSegment({ isInit: false }),
440
+ };
441
+ const fakeHistory = [{ date: 1, buffered: null, context }];
442
+ mockGetHistoryFor.mockReturnValue(fakeHistory);
443
+ const result = inv.getHistoryFor(context);
444
+ expect(result).toBe(fakeHistory);
445
+ expect(mockGetHistoryFor).toHaveBeenCalledWith(context);
446
+ });
447
+ });
448
+ });