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
@@ -243,7 +243,7 @@ export default function createThumbnailFetcher(
243
243
  cdnMetadata,
244
244
  thumbnail,
245
245
  pipelineRequestOptions,
246
- cancellationSignal,
246
+ requestCanceller.signal,
247
247
  );
248
248
  }
249
249
 
@@ -20,7 +20,6 @@ import type { ICdnMetadata } from "../../../parsers/manifest";
20
20
  import cancellableSleep from "../../../utils/cancellable_sleep";
21
21
  import getFuzzedDelay from "../../../utils/get_fuzzed_delay";
22
22
  import getTimestamp from "../../../utils/monotonic_timestamp";
23
- import noop from "../../../utils/noop";
24
23
  import { RequestError } from "../../../utils/request";
25
24
  import type { CancellationSignal } from "../../../utils/task_canceller";
26
25
  import TaskCanceller from "../../../utils/task_canceller";
@@ -315,7 +314,7 @@ export async function scheduleRequestWithCdns<T>(
315
314
  () => {
316
315
  const updatedPrioritaryCdn = getCdnToRequest();
317
316
  if (cancellationSignal.isCancelled()) {
318
- throw cancellationSignal.cancellationError;
317
+ return;
319
318
  }
320
319
  if (updatedPrioritaryCdn === undefined) {
321
320
  return cleanAndReject(prevRequestError);
@@ -333,7 +332,10 @@ export async function scheduleRequestWithCdns<T>(
333
332
 
334
333
  cancellableSleep(blockedFor, canceller.signal).then(
335
334
  () => requestCdn(nextWantedCdn).then(cleanAndResolve, cleanAndReject),
336
- noop,
335
+ (err) =>
336
+ cleanAndReject(
337
+ cancellationSignal.isCancelled() ? cancellationSignal.cancellationError : err,
338
+ ),
337
339
  );
338
340
 
339
341
  function cleanAndResolve(response: T) {
@@ -0,0 +1,434 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import {
3
+ makeReadyOnlyPlaybackObserver,
4
+ DummyObservationPosition,
5
+ } from "../../../playback_observer/__tests__/mocks";
6
+ import type { IRange } from "../../../utils/ranges";
7
+ import SharedReference from "../../../utils/reference";
8
+ import TaskCanceller from "../../../utils/task_canceller";
9
+ import type { IStreamOrchestratorPlaybackObservation } from "../../stream";
10
+ import BufferGarbageCollector from "../garbage_collector";
11
+ import { DummySegmentSink } from "./mocks";
12
+
13
+ const mockLog = vi.hoisted(() => ({
14
+ debug: vi.fn(),
15
+ error: vi.fn(),
16
+ info: vi.fn(),
17
+ warn: vi.fn(),
18
+ }));
19
+ vi.mock("../../../log", () => ({ default: mockLog }));
20
+
21
+ describe("BufferGarbageCollector", () => {
22
+ const mockedPlaybackObserver = makeReadyOnlyPlaybackObserver<
23
+ Pick<IStreamOrchestratorPlaybackObservation, "position" | "buffered">
24
+ >({
25
+ position: new DummyObservationPosition({
26
+ getWanted: vi.fn(() => 0),
27
+ }),
28
+ buffered: { video: null, audio: null, text: null },
29
+ });
30
+ const mockRemoveBuffer = vi.spyOn(DummySegmentSink.prototype, "removeBuffer");
31
+
32
+ function emitObservation(position: number, videoBuffered: IRange[] | null) {
33
+ mockedPlaybackObserver.emit({
34
+ position: new DummyObservationPosition({
35
+ getWanted: () => position,
36
+ }),
37
+ buffered: { video: videoBuffered, audio: null, text: null },
38
+ });
39
+ }
40
+ beforeEach(() => {
41
+ mockRemoveBuffer.mockReturnValue(Promise.resolve(undefined));
42
+ });
43
+
44
+ afterEach(() => {
45
+ mockedPlaybackObserver.reset();
46
+ vi.resetAllMocks();
47
+ });
48
+
49
+ it("calls removeBuffer for data behind maxBufferBehind (outer range fully behind)", async () => {
50
+ emitObservation(100, [{ start: 0, end: 80 }]);
51
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
52
+ const maxBufferBehind = new SharedReference<number>(10);
53
+ const maxBufferAhead = new SharedReference<number>(Infinity);
54
+
55
+ BufferGarbageCollector(
56
+ {
57
+ segmentSink,
58
+ playbackObserver: mockedPlaybackObserver.observer,
59
+ maxBufferBehind,
60
+ maxBufferAhead,
61
+ },
62
+ new TaskCanceller("test BufferGarbageCollector").signal,
63
+ );
64
+
65
+ // Wait for async operations
66
+ await Promise.resolve();
67
+ await Promise.resolve();
68
+
69
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(0, 80);
70
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
71
+ });
72
+
73
+ it("calls removeBuffer for partial range behind maxBufferBehind (outer range partially behind)", async () => {
74
+ emitObservation(100, [{ start: 85, end: 95 }]);
75
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
76
+ const maxBufferBehind = new SharedReference<number>(10);
77
+ const maxBufferAhead = new SharedReference<number>(Infinity);
78
+
79
+ BufferGarbageCollector(
80
+ {
81
+ segmentSink,
82
+ playbackObserver: mockedPlaybackObserver.observer,
83
+ maxBufferBehind,
84
+ maxBufferAhead,
85
+ },
86
+ new TaskCanceller("test BufferGarbageCollector").signal,
87
+ );
88
+
89
+ await Promise.resolve();
90
+ await Promise.resolve();
91
+
92
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(85, 90);
93
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("calls removeBuffer for data inside innerRange when behind maxBufferBehind", async () => {
97
+ emitObservation(100, [{ start: 80, end: 110 }]);
98
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
99
+ const maxBufferBehind = new SharedReference<number>(5);
100
+ const maxBufferAhead = new SharedReference<number>(Infinity);
101
+
102
+ BufferGarbageCollector(
103
+ {
104
+ segmentSink,
105
+ playbackObserver: mockedPlaybackObserver.observer,
106
+ maxBufferBehind,
107
+ maxBufferAhead,
108
+ },
109
+ new TaskCanceller("test BufferGarbageCollector").signal,
110
+ );
111
+
112
+ await Promise.resolve();
113
+ await Promise.resolve();
114
+
115
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(80, 95);
116
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
117
+ });
118
+
119
+ it("calls removeBuffer for data ahead maxBufferAhead (outer range fully ahead)", async () => {
120
+ emitObservation(100, [{ start: 120, end: 150 }]);
121
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
122
+ const maxBufferBehind = new SharedReference<number>(Infinity);
123
+ const maxBufferAhead = new SharedReference<number>(10);
124
+
125
+ BufferGarbageCollector(
126
+ {
127
+ segmentSink,
128
+ playbackObserver: mockedPlaybackObserver.observer,
129
+ maxBufferBehind,
130
+ maxBufferAhead,
131
+ },
132
+ new TaskCanceller("test BufferGarbageCollector").signal,
133
+ );
134
+
135
+ await Promise.resolve();
136
+ await Promise.resolve();
137
+
138
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(120, 150);
139
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
140
+ });
141
+
142
+ it("calls removeBuffer for partial range ahead maxBufferAhead (outer range partially ahead)", async () => {
143
+ emitObservation(100, [{ start: 105, end: 120 }]);
144
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
145
+ const maxBufferBehind = new SharedReference<number>(Infinity);
146
+ const maxBufferAhead = new SharedReference<number>(10);
147
+
148
+ BufferGarbageCollector(
149
+ {
150
+ segmentSink,
151
+ playbackObserver: mockedPlaybackObserver.observer,
152
+ maxBufferBehind,
153
+ maxBufferAhead,
154
+ },
155
+ new TaskCanceller("test BufferGarbageCollector").signal,
156
+ );
157
+
158
+ await Promise.resolve();
159
+ await Promise.resolve();
160
+
161
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(110, 120);
162
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
163
+ });
164
+
165
+ it("calls removeBuffer for data inside innerRange when ahead maxBufferAhead", async () => {
166
+ emitObservation(100, [{ start: 80, end: 120 }]);
167
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
168
+ const maxBufferBehind = new SharedReference<number>(Infinity);
169
+ const maxBufferAhead = new SharedReference<number>(5);
170
+
171
+ BufferGarbageCollector(
172
+ {
173
+ segmentSink,
174
+ playbackObserver: mockedPlaybackObserver.observer,
175
+ maxBufferBehind,
176
+ maxBufferAhead,
177
+ },
178
+ new TaskCanceller("test BufferGarbageCollector").signal,
179
+ );
180
+
181
+ await Promise.resolve();
182
+ await Promise.resolve();
183
+
184
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(105, 120);
185
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
186
+ });
187
+
188
+ it("does not call removeBuffer when both maxBufferBehind and maxBufferAhead are Infinity", async () => {
189
+ emitObservation(100, [{ start: 0, end: 200 }]);
190
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
191
+ const maxBufferBehind = new SharedReference<number>(Infinity);
192
+ const maxBufferAhead = new SharedReference<number>(Infinity);
193
+
194
+ BufferGarbageCollector(
195
+ {
196
+ segmentSink,
197
+ playbackObserver: mockedPlaybackObserver.observer,
198
+ maxBufferBehind,
199
+ maxBufferAhead,
200
+ },
201
+ new TaskCanceller("test BufferGarbageCollector").signal,
202
+ );
203
+
204
+ await Promise.resolve();
205
+ await Promise.resolve();
206
+
207
+ expect(mockRemoveBuffer).not.toHaveBeenCalled();
208
+ });
209
+
210
+ it("does not call removeBuffer when buffered is null", async () => {
211
+ emitObservation(100, null);
212
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
213
+ const maxBufferBehind = new SharedReference<number>(10);
214
+ const maxBufferAhead = new SharedReference<number>(10);
215
+
216
+ BufferGarbageCollector(
217
+ {
218
+ segmentSink,
219
+ playbackObserver: mockedPlaybackObserver.observer,
220
+ maxBufferBehind,
221
+ maxBufferAhead,
222
+ },
223
+ new TaskCanceller("test BufferGarbageCollector").signal,
224
+ );
225
+
226
+ await Promise.resolve();
227
+ await Promise.resolve();
228
+
229
+ expect(mockRemoveBuffer).not.toHaveBeenCalled();
230
+ });
231
+
232
+ it("registers listeners on maxBufferBehind and maxBufferAhead", () => {
233
+ emitObservation(100, []);
234
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
235
+ const maxBufferBehind = new SharedReference<number>(10);
236
+ const maxBufferAhead = new SharedReference<number>(10);
237
+ const mbbUpdate = vi.spyOn(maxBufferBehind, "onUpdate");
238
+ const mbaUpdate = vi.spyOn(maxBufferAhead, "onUpdate");
239
+
240
+ BufferGarbageCollector(
241
+ {
242
+ segmentSink,
243
+ playbackObserver: mockedPlaybackObserver.observer,
244
+ maxBufferBehind,
245
+ maxBufferAhead,
246
+ },
247
+ new TaskCanceller("test BufferGarbageCollector").signal,
248
+ );
249
+
250
+ expect(mbbUpdate).toHaveBeenCalledTimes(1);
251
+ expect(mbaUpdate).toHaveBeenCalledTimes(1);
252
+ });
253
+
254
+ it("re-runs clean when maxBufferBehind changes", async () => {
255
+ emitObservation(100, [{ start: 0, end: 80 }]);
256
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
257
+ const maxBufferBehind = new SharedReference<number>(Infinity);
258
+ const maxBufferAhead = new SharedReference<number>(Infinity);
259
+
260
+ BufferGarbageCollector(
261
+ {
262
+ segmentSink,
263
+ playbackObserver: mockedPlaybackObserver.observer,
264
+ maxBufferBehind,
265
+ maxBufferAhead,
266
+ },
267
+ new TaskCanceller("test BufferGarbageCollector").signal,
268
+ );
269
+
270
+ await Promise.resolve();
271
+ await Promise.resolve();
272
+ expect(mockRemoveBuffer).not.toHaveBeenCalled();
273
+
274
+ maxBufferBehind.setValue(10);
275
+
276
+ await Promise.resolve();
277
+ await Promise.resolve();
278
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(0, 80);
279
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
280
+ });
281
+
282
+ it("re-runs clean when maxBufferAhead changes", async () => {
283
+ emitObservation(100, [{ start: 120, end: 150 }]);
284
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
285
+ const maxBufferBehind = new SharedReference<number>(Infinity);
286
+ const maxBufferAhead = new SharedReference<number>(Infinity);
287
+
288
+ BufferGarbageCollector(
289
+ {
290
+ segmentSink,
291
+ playbackObserver: mockedPlaybackObserver.observer,
292
+ maxBufferBehind,
293
+ maxBufferAhead,
294
+ },
295
+ new TaskCanceller("test BufferGarbageCollector").signal,
296
+ );
297
+
298
+ await Promise.resolve();
299
+ await Promise.resolve();
300
+ expect(mockRemoveBuffer).not.toHaveBeenCalled();
301
+
302
+ maxBufferAhead.setValue(10);
303
+
304
+ await Promise.resolve();
305
+ await Promise.resolve();
306
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(120, 150);
307
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
308
+ });
309
+
310
+ it("re-runs clean on new playback observation", async () => {
311
+ // Initially empty buffer, then buffered grows behind position
312
+ emitObservation(100, []);
313
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
314
+ const maxBufferBehind = new SharedReference<number>(10);
315
+ const maxBufferAhead = new SharedReference<number>(Infinity);
316
+
317
+ BufferGarbageCollector(
318
+ {
319
+ segmentSink,
320
+ playbackObserver: mockedPlaybackObserver.observer,
321
+ maxBufferBehind,
322
+ maxBufferAhead,
323
+ },
324
+ new TaskCanceller("test BufferGarbageCollector").signal,
325
+ );
326
+
327
+ await Promise.resolve();
328
+ await Promise.resolve();
329
+ expect(mockRemoveBuffer).not.toHaveBeenCalled();
330
+
331
+ // Simulate new observation: position=200, buffer=[0-180] (fully behind 200-10=190)
332
+ emitObservation(200, [{ start: 0, end: 180 }]);
333
+
334
+ await Promise.resolve();
335
+ await Promise.resolve();
336
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(0, 180);
337
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(1);
338
+
339
+ emitObservation(500, [{ start: 180, end: 300 }]);
340
+
341
+ await Promise.resolve();
342
+ await Promise.resolve();
343
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(180, 300);
344
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(2);
345
+ });
346
+
347
+ it("logs error when removeBuffer rejects (non-cancellation error)", async () => {
348
+ emitObservation(100, [{ start: 0, end: 80 }]);
349
+ const error = new Error("removeBuffer failed");
350
+ mockRemoveBuffer.mockReturnValueOnce(Promise.reject(error));
351
+
352
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
353
+ const maxBufferBehind = new SharedReference<number>(10);
354
+ const maxBufferAhead = new SharedReference<number>(Infinity);
355
+
356
+ BufferGarbageCollector(
357
+ {
358
+ segmentSink,
359
+ playbackObserver: mockedPlaybackObserver.observer,
360
+ maxBufferBehind,
361
+ maxBufferAhead,
362
+ },
363
+ new TaskCanceller("test BufferGarbageCollector").signal,
364
+ );
365
+
366
+ // Wait for rejection to propagate
367
+ await new Promise((r) => setTimeout(r, 0));
368
+
369
+ expect(mockLog.error).toHaveBeenCalledWith(
370
+ "Stream",
371
+ "Could not run BufferGarbageCollector:",
372
+ "removeBuffer failed",
373
+ );
374
+ });
375
+
376
+ it("does not log error when removeBuffer rejects with a cancellation error and signal is cancelled", async () => {
377
+ emitObservation(100, [{ start: 0, end: 80 }]);
378
+ const canceller = new TaskCanceller("test removeBuffer");
379
+ mockRemoveBuffer.mockImplementation(() => {
380
+ return new Promise((_res, rej) => {
381
+ canceller.signal.register((err) => rej(err));
382
+ });
383
+ });
384
+
385
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
386
+ const maxBufferBehind = new SharedReference<number>(10);
387
+ const maxBufferAhead = new SharedReference<number>(Infinity);
388
+
389
+ BufferGarbageCollector(
390
+ {
391
+ segmentSink,
392
+ playbackObserver: mockedPlaybackObserver.observer,
393
+ maxBufferBehind,
394
+ maxBufferAhead,
395
+ },
396
+ canceller.signal,
397
+ );
398
+
399
+ await new Promise((r) =>
400
+ setTimeout(() => {
401
+ canceller.cancel("cancelling remove");
402
+ r(undefined);
403
+ }, 0),
404
+ );
405
+ await new Promise((r) => setTimeout(r, 0));
406
+ expect(mockLog.error).not.toHaveBeenCalled();
407
+ });
408
+
409
+ it("handles both behind and ahead cleanup simultaneously", async () => {
410
+ emitObservation(100, [
411
+ { start: 0, end: 80 },
412
+ { start: 120, end: 150 },
413
+ ]);
414
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
415
+ const maxBufferBehind = new SharedReference<number>(10);
416
+ const maxBufferAhead = new SharedReference<number>(10);
417
+
418
+ BufferGarbageCollector(
419
+ {
420
+ segmentSink,
421
+ playbackObserver: mockedPlaybackObserver.observer,
422
+ maxBufferBehind,
423
+ maxBufferAhead,
424
+ },
425
+ new TaskCanceller("test BufferGarbageCollector").signal,
426
+ );
427
+
428
+ await new Promise((r) => setTimeout(r, 0));
429
+
430
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(0, 80);
431
+ expect(mockRemoveBuffer).toHaveBeenCalledWith(120, 150);
432
+ expect(mockRemoveBuffer).toHaveBeenCalledTimes(2);
433
+ });
434
+ });
@@ -0,0 +1,49 @@
1
+ import { makeMockedClass } from "../../../utils/test-utils";
2
+ import type { SegmentSink } from "../implementations";
3
+ import type SegmentInventory from "../inventory";
4
+ import type BufferedHistory from "../inventory/buffered_history";
5
+
6
+ export const DummySegmentSink = makeMockedClass<SegmentSink>(
7
+ {
8
+ removeBuffer: notImplemented("removeBuffer"),
9
+ declareInitSegment: notImplemented("declareInitSegment"),
10
+ pushChunk: notImplemented("pushChunk"),
11
+ freeInitSegment: notImplemented("freeInitSegment"),
12
+ synchronizeInventory: notImplemented("synchronizeInventory"),
13
+ getPendingOperations: notImplemented("getPendingOperations"),
14
+ getLastKnownInventory: notImplemented("getLastKnownInventory"),
15
+ getSegmentHistory: notImplemented("getSegmentHistory"),
16
+ signalSegmentComplete: notImplemented("signalSegmentComplete"),
17
+ dispose: notImplemented("dispose"),
18
+ },
19
+ {
20
+ bufferType: "video",
21
+ codec: "not implemented",
22
+ },
23
+ );
24
+
25
+ export const DummySegmentInventory = makeMockedClass<SegmentInventory>(
26
+ {
27
+ reset: notImplemented("reset"),
28
+ synchronizeBuffered: notImplemented("synchronizeBuffered"),
29
+ getInventory: notImplemented("getInventory"),
30
+ getHistoryFor: notImplemented("getHistoryFor"),
31
+ insertChunk: notImplemented("insertChunk"),
32
+ completeSegment: notImplemented("completeSegment"),
33
+ },
34
+ {},
35
+ );
36
+
37
+ export const DummyBufferedHistory = makeMockedClass<BufferedHistory>(
38
+ {
39
+ addBufferedSegment: notImplemented("addBufferedSegment"),
40
+ getHistoryFor: notImplemented("getHistoryFor"),
41
+ },
42
+ {},
43
+ );
44
+
45
+ function notImplemented(name: string): () => never {
46
+ return () => {
47
+ throw new Error(`${name} not implemented`);
48
+ };
49
+ }
@@ -47,8 +47,6 @@ export interface IGarbageCollectorArgument {
47
47
  *
48
48
  * @param {Object} opt
49
49
  * @param {Object} cancellationSignal
50
- *
51
- * TODO Move to main thread?
52
50
  */
53
51
  export default function BufferGarbageCollector(
54
52
  {
@@ -90,7 +88,6 @@ export default function BufferGarbageCollector(
90
88
  }
91
89
  maxBufferBehind.onUpdate(clean, { clearSignal: cancellationSignal });
92
90
  maxBufferAhead.onUpdate(clean, { clearSignal: cancellationSignal });
93
- clean();
94
91
  }
95
92
 
96
93
  /**