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,177 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import type { ITextDisplayerData } from "../../../../../main_thread/text_displayer/types";
3
+ import {
4
+ DummyAdaptation,
5
+ DummyPeriod,
6
+ DummyRepresentation,
7
+ createSegment,
8
+ } from "../../../../../manifest/classes/__tests__/mocks";
9
+ import type { IRange } from "../../../../../utils/ranges";
10
+ import SegmentInventory from "../../../inventory";
11
+ import type { IPushChunkInfos } from "../../types";
12
+ import { SegmentSinkOperation } from "../../types";
13
+ import TextSegmentSink, { type ITextDisplayerInterface } from "../text_segment_sink";
14
+
15
+ function createSender() {
16
+ const pushTextData = vi.fn<(infos: ITextDisplayerData) => Promise<IRange[]>>();
17
+ const remove = vi.fn<(start: number, end: number) => Promise<IRange[]>>();
18
+ const reset = vi.fn<() => void>();
19
+ const stop = vi.fn<(reason: string | undefined) => void>();
20
+ return {
21
+ pushTextData,
22
+ remove,
23
+ reset,
24
+ stop,
25
+ };
26
+ }
27
+
28
+ function createChunkInfos(): IPushChunkInfos<unknown> {
29
+ return {
30
+ data: {
31
+ chunk: {
32
+ data: "WEBVTT",
33
+ type: "vtt",
34
+ initTimescale: null,
35
+ start: 0,
36
+ end: 2,
37
+ },
38
+ timestampOffset: 0,
39
+ appendWindow: [undefined, undefined],
40
+ codec: undefined,
41
+ initSegmentUniqueId: null,
42
+ },
43
+ inventoryInfos: {
44
+ adaptation: new DummyAdaptation({ type: "text" }),
45
+ period: new DummyPeriod(),
46
+ representation: new DummyRepresentation({ trackType: "text" }),
47
+ segment: createSegment({ id: "seg-1", isInit: false, complete: true }),
48
+ chunkSize: 12,
49
+ start: 0,
50
+ end: 2,
51
+ },
52
+ };
53
+ }
54
+
55
+ function createCompleteSegmentInfo() {
56
+ return {
57
+ adaptation: new DummyAdaptation({ type: "text" }),
58
+ period: new DummyPeriod(),
59
+ representation: new DummyRepresentation({ trackType: "text" }),
60
+ segment: createSegment({ id: "seg-1", isInit: false, complete: true }),
61
+ };
62
+ }
63
+
64
+ function createDeferred<T>() {
65
+ let resolve!: (value: T | PromiseLike<T>) => void;
66
+ let reject!: (reason?: unknown) => void;
67
+ const promise = new Promise<T>((res, rej) => {
68
+ resolve = res;
69
+ reject = rej;
70
+ });
71
+ return { promise, resolve, reject };
72
+ }
73
+
74
+ class TestTextSegmentSink extends TextSegmentSink {
75
+ public constructor(sender: ITextDisplayerInterface, inventory: SegmentInventory) {
76
+ super(sender);
77
+ this._segmentInventory = inventory;
78
+ }
79
+ }
80
+
81
+ describe("TextSegmentSink", () => {
82
+ it("resets the text displayer on construction", () => {
83
+ const sender = createSender();
84
+ new TextSegmentSink(sender);
85
+
86
+ expect(sender.reset).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ it("forwards dispose to the text displayer stop method", () => {
90
+ const sender = createSender();
91
+
92
+ const sink = new TextSegmentSink(sender);
93
+ sink.dispose("test reason");
94
+
95
+ expect(sender.reset).toHaveBeenCalledTimes(1);
96
+ expect(sender.stop).toHaveBeenCalledTimes(1);
97
+ expect(sender.stop).toHaveBeenCalledWith("test reason");
98
+ });
99
+
100
+ it("pushes text data, tracks the pending operation, and updates inventory", async () => {
101
+ const sender = createSender();
102
+ const deferred = createDeferred<IRange[]>();
103
+ sender.pushTextData.mockReturnValue(deferred.promise);
104
+ const inventory = new SegmentInventory();
105
+ const sink = new TestTextSegmentSink(sender, inventory);
106
+ const infos = createChunkInfos();
107
+ const insertChunk = vi.spyOn(inventory, "insertChunk");
108
+ const synchronizeBuffered = vi.spyOn(inventory, "synchronizeBuffered");
109
+
110
+ const pushPromise = sink.pushChunk(infos);
111
+
112
+ expect(sender.pushTextData).toHaveBeenCalledWith(infos.data);
113
+ expect(sink.getPendingOperations()).toEqual([
114
+ { type: SegmentSinkOperation.Push, value: infos },
115
+ ]);
116
+
117
+ deferred.resolve([{ start: 0, end: 2 }]);
118
+ await expect(pushPromise).resolves.toEqual([{ start: 0, end: 2 }]);
119
+ expect(insertChunk).toHaveBeenCalledTimes(1);
120
+ expect(insertChunk).toHaveBeenCalledWith(
121
+ infos.inventoryInfos,
122
+ true,
123
+ expect.any(Number),
124
+ );
125
+ expect(synchronizeBuffered).toHaveBeenCalledWith([{ start: 0, end: 2 }]);
126
+ expect(sink.getPendingOperations()).toEqual([]);
127
+ });
128
+
129
+ it("removes text data, tracks the pending operation, and synchronizes inventory", async () => {
130
+ const sender = createSender();
131
+ const deferred = createDeferred<IRange[]>();
132
+ sender.remove.mockReturnValue(deferred.promise);
133
+ const inventory = new SegmentInventory();
134
+ const sink = new TestTextSegmentSink(sender, inventory);
135
+ const synchronizeBuffered = vi.spyOn(inventory, "synchronizeBuffered");
136
+
137
+ const removePromise = sink.removeBuffer(10, 20);
138
+
139
+ expect(sender.remove).toHaveBeenCalledWith(10, 20);
140
+ expect(sink.getPendingOperations()).toEqual([
141
+ { type: SegmentSinkOperation.Remove, value: { start: 10, end: 20 } },
142
+ ]);
143
+
144
+ deferred.resolve([{ start: 10, end: 20 }]);
145
+ await expect(removePromise).resolves.toEqual([{ start: 10, end: 20 }]);
146
+ expect(synchronizeBuffered).toHaveBeenCalledWith([{ start: 10, end: 20 }]);
147
+ expect(sink.getPendingOperations()).toEqual([]);
148
+ });
149
+
150
+ it("waits for the previous operation before completing a segment", async () => {
151
+ const sender = createSender();
152
+ const deferred = createDeferred<IRange[]>();
153
+ sender.pushTextData.mockReturnValue(deferred.promise);
154
+ const inventory = new SegmentInventory();
155
+ const sink = new TestTextSegmentSink(sender, inventory);
156
+ const completeSegment = vi.spyOn(inventory, "completeSegment");
157
+ const pushInfos = createChunkInfos();
158
+ const completionInfos = createCompleteSegmentInfo();
159
+
160
+ const pushPromise = sink.pushChunk(pushInfos);
161
+ const signalPromise = sink.signalSegmentComplete(completionInfos);
162
+
163
+ expect(sink.getPendingOperations()).toEqual([
164
+ { type: SegmentSinkOperation.Push, value: pushInfos },
165
+ { type: SegmentSinkOperation.SignalSegmentComplete, value: completionInfos },
166
+ ]);
167
+ expect(completeSegment).not.toHaveBeenCalled();
168
+
169
+ deferred.resolve([{ start: 0, end: 2 }]);
170
+ await pushPromise;
171
+ await signalPromise;
172
+
173
+ expect(completeSegment).toHaveBeenCalledTimes(1);
174
+ expect(completeSegment).toHaveBeenCalledWith(completionInfos);
175
+ expect(sink.getPendingOperations()).toEqual([]);
176
+ });
177
+ });
@@ -128,9 +128,9 @@ export default class TextSegmentSink extends SegmentSink {
128
128
  return this._pendingOperations.map((p) => p.operation);
129
129
  }
130
130
 
131
- public dispose(): void {
131
+ public dispose(reason: string | undefined): void {
132
132
  log.debug("Stream", "Disposing TextSegmentSink");
133
- this._sender.reset();
133
+ this._sender.stop(reason);
134
134
  }
135
135
 
136
136
  private _addToOperationQueue(
@@ -0,0 +1,215 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import {
3
+ DummyAdaptation,
4
+ DummyRepresentation,
5
+ DummyPeriod,
6
+ createSegment,
7
+ } from "../../../../manifest/classes/__tests__/mocks";
8
+ import BufferedHistory from "../buffered_history";
9
+ import type { IChunkContext } from "../types";
10
+
11
+ const { mockAreSameContent, mockGetMonotonicTimeStamp } = vi.hoisted(() => {
12
+ return { mockAreSameContent: vi.fn(), mockGetMonotonicTimeStamp: vi.fn() };
13
+ });
14
+
15
+ vi.mock("../../../../manifest", () => ({
16
+ areSameContent: mockAreSameContent,
17
+ }));
18
+
19
+ vi.mock("../../../../utils/monotonic_timestamp", () => ({
20
+ default: mockGetMonotonicTimeStamp,
21
+ }));
22
+
23
+ const makeContext = (id: string): IChunkContext => ({
24
+ adaptation: new DummyAdaptation({ id }),
25
+ representation: new DummyRepresentation({ id }),
26
+ period: new DummyPeriod({ id }),
27
+ segment: createSegment({ id }),
28
+ });
29
+
30
+ describe("BufferedHistory", () => {
31
+ beforeEach(() => {
32
+ vi.resetAllMocks();
33
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
34
+ mockAreSameContent.mockReturnValue(false);
35
+ });
36
+
37
+ describe("constructor", () => {
38
+ it("should initialize with an empty history", () => {
39
+ const history = new BufferedHistory(5000, 10);
40
+ expect(history.getHistoryFor(makeContext("1"))).toHaveLength(0);
41
+ });
42
+ });
43
+
44
+ describe("addBufferedSegment", () => {
45
+ it("should add an entry with the current timestamp and buffered range", () => {
46
+ mockGetMonotonicTimeStamp.mockReturnValue(2000);
47
+ mockAreSameContent.mockReturnValue(true);
48
+
49
+ const history = new BufferedHistory(5000, 10);
50
+ const context = makeContext("1");
51
+ const buffered = { start: 0, end: 10 };
52
+
53
+ history.addBufferedSegment(context, buffered);
54
+
55
+ const entries = history.getHistoryFor(context);
56
+ expect(entries).toHaveLength(1);
57
+ expect(entries[0].date).toBe(2000);
58
+ expect(entries[0].buffered).toEqual({ start: 0, end: 10 });
59
+ expect(entries[0].context).toBe(context);
60
+ });
61
+
62
+ it("should accept null as a buffered value", () => {
63
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
64
+ mockAreSameContent.mockReturnValue(true);
65
+
66
+ const history = new BufferedHistory(5000, 10);
67
+ const context = makeContext("1");
68
+
69
+ history.addBufferedSegment(context, null);
70
+
71
+ const entries = history.getHistoryFor(context);
72
+ expect(entries).toHaveLength(1);
73
+ expect(entries[0].buffered).toBeNull();
74
+ });
75
+
76
+ it("should store multiple entries in chronological order", () => {
77
+ const history = new BufferedHistory(5000, 10);
78
+ mockAreSameContent.mockReturnValue(true);
79
+
80
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
81
+ history.addBufferedSegment(makeContext("1"), { start: 0, end: 5 });
82
+
83
+ mockGetMonotonicTimeStamp.mockReturnValue(2000);
84
+ history.addBufferedSegment(makeContext("1"), { start: 5, end: 10 });
85
+
86
+ const entries = history.getHistoryFor(makeContext("1"));
87
+ expect(entries).toHaveLength(2);
88
+ expect(entries[0].date).toBe(1000);
89
+ expect(entries[1].date).toBe(2000);
90
+ });
91
+ });
92
+
93
+ describe("getHistoryFor", () => {
94
+ it("should return only entries matching the given context", () => {
95
+ const contextA = makeContext("1");
96
+ const contextB = makeContext("2");
97
+
98
+ mockAreSameContent.mockImplementation((a, b) => a === b);
99
+
100
+ const history = new BufferedHistory(5000, 10);
101
+ history.addBufferedSegment(contextA, { start: 0, end: 5 });
102
+ history.addBufferedSegment(contextB, { start: 5, end: 10 });
103
+ history.addBufferedSegment(contextA, { start: 10, end: 15 });
104
+
105
+ expect(history.getHistoryFor(contextA)).toHaveLength(2);
106
+ expect(history.getHistoryFor(contextB)).toHaveLength(1);
107
+ });
108
+
109
+ it("should return an empty array when no entry matches", () => {
110
+ mockAreSameContent.mockReturnValue(false);
111
+
112
+ const history = new BufferedHistory(5000, 10);
113
+ history.addBufferedSegment(makeContext("1"), { start: 0, end: 5 });
114
+
115
+ expect(history.getHistoryFor(makeContext("99"))).toHaveLength(0);
116
+ });
117
+ });
118
+
119
+ describe("_cleanHistory (lifetime enforcement)", () => {
120
+ it("should remove entries older than the lifetime on addBufferedSegment", () => {
121
+ mockAreSameContent.mockReturnValue(true);
122
+
123
+ const history = new BufferedHistory(500, 10);
124
+
125
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
126
+ history.addBufferedSegment(makeContext("1"), { start: 0, end: 5 });
127
+
128
+ mockGetMonotonicTimeStamp.mockReturnValue(1200);
129
+ history.addBufferedSegment(makeContext("1"), { start: 5, end: 10 });
130
+
131
+ // now = 1600, lifetime = 500 → entries before 1100 should be dropped
132
+ mockGetMonotonicTimeStamp.mockReturnValue(1600);
133
+ history.addBufferedSegment(makeContext("1"), { start: 10, end: 15 });
134
+
135
+ const entries = history.getHistoryFor(makeContext("1"));
136
+ expect(entries.every((e) => e.date >= 1100)).toBe(true);
137
+ // eslint-disable-next-line no-restricted-properties
138
+ expect(entries.find((e) => e.date === 1000)).toBeUndefined();
139
+ });
140
+
141
+ it("should keep entries whose date is exactly at the lifetime boundary", () => {
142
+ mockAreSameContent.mockReturnValue(true);
143
+
144
+ const history = new BufferedHistory(500, 10);
145
+
146
+ // entry at t=1000, boundary when now=1500 is exactly 1000 — NOT expired
147
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
148
+ history.addBufferedSegment(makeContext("1"), { start: 0, end: 5 });
149
+
150
+ mockGetMonotonicTimeStamp.mockReturnValue(1500);
151
+ history.addBufferedSegment(makeContext("1"), { start: 5, end: 10 });
152
+
153
+ const entries = history.getHistoryFor(makeContext("1"));
154
+ // eslint-disable-next-line no-restricted-properties
155
+ expect(entries.find((e) => e.date === 1000)).toBeDefined();
156
+ });
157
+ });
158
+
159
+ describe("_cleanHistory (maxHistoryLength enforcement)", () => {
160
+ it("should remove oldest entries when exceeding maxHistoryLength", () => {
161
+ mockAreSameContent.mockReturnValue(true);
162
+
163
+ const history = new BufferedHistory(99999, 3);
164
+
165
+ for (let i = 0; i < 4; i++) {
166
+ mockGetMonotonicTimeStamp.mockReturnValue(1000 + i);
167
+ history.addBufferedSegment(makeContext("1"), { start: i, end: i + 1 });
168
+ }
169
+
170
+ const entries = history.getHistoryFor(makeContext("1"));
171
+ expect(entries).toHaveLength(3);
172
+ // The oldest entry (date=1000) should have been evicted
173
+ // eslint-disable-next-line no-restricted-properties
174
+ expect(entries.find((e) => e.date === 1000)).toBeUndefined();
175
+ expect(entries[0].date).toBe(1001);
176
+ });
177
+
178
+ it("should keep exactly maxHistoryLength entries when at the limit", () => {
179
+ mockAreSameContent.mockReturnValue(true);
180
+
181
+ const history = new BufferedHistory(99999, 3);
182
+
183
+ for (let i = 0; i < 3; i++) {
184
+ mockGetMonotonicTimeStamp.mockReturnValue(1000 + i);
185
+ history.addBufferedSegment(makeContext("1"), { start: i, end: i + 1 });
186
+ }
187
+
188
+ const entries = history.getHistoryFor(makeContext("1"));
189
+ expect(entries).toHaveLength(3);
190
+ });
191
+ });
192
+
193
+ describe("combined lifetime and maxHistoryLength", () => {
194
+ it("should apply both constraints independently", () => {
195
+ mockAreSameContent.mockReturnValue(true);
196
+
197
+ const history = new BufferedHistory(500, 2);
198
+
199
+ mockGetMonotonicTimeStamp.mockReturnValue(1000);
200
+ history.addBufferedSegment(makeContext("1"), { start: 0, end: 1 });
201
+
202
+ mockGetMonotonicTimeStamp.mockReturnValue(1100);
203
+ history.addBufferedSegment(makeContext("1"), { start: 1, end: 2 });
204
+
205
+ // now=1600 → t=1000 expired by lifetime; then only 1 entry remains, under max
206
+ mockGetMonotonicTimeStamp.mockReturnValue(1600);
207
+ history.addBufferedSegment(makeContext("1"), { start: 2, end: 3 });
208
+
209
+ const entries = history.getHistoryFor(makeContext("1"));
210
+ expect(entries).toHaveLength(2);
211
+ // eslint-disable-next-line no-restricted-properties
212
+ expect(entries.find((e) => e.date === 1000)).toBeUndefined();
213
+ });
214
+ });
215
+ });