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,973 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type {
3
+ IAdaptation,
4
+ IManifest,
5
+ IPeriod,
6
+ IRepresentation,
7
+ } from "../../../../manifest";
8
+ import {
9
+ DummyAdaptation,
10
+ DummyManifest,
11
+ DummyPeriod,
12
+ DummyRepresentation,
13
+ createSegment,
14
+ } from "../../../../manifest/classes/__tests__/mocks";
15
+ import type { IPeriodsUpdateResult } from "../../../../manifest/classes/update_periods";
16
+ import type { ObservationPosition } from "../../../../playback_observer";
17
+ import {
18
+ DummyObservationPosition,
19
+ makeReadyOnlyPlaybackObserver,
20
+ } from "../../../../playback_observer/__tests__/mocks";
21
+ import SharedReference from "../../../../utils/reference";
22
+ import TaskCanceller, { CancellationError } from "../../../../utils/task_canceller";
23
+ import { makeMockedClass } from "../../../../utils/test-utils";
24
+ import type {
25
+ IRepresentationEstimator,
26
+ IRepresentationEstimatorCallbacks,
27
+ } from "../../../adaptive";
28
+ import type { SegmentQueueCreator } from "../../../fetchers";
29
+ import type SegmentQueue from "../../../fetchers/segment/segment_queue";
30
+ import type { SegmentSink } from "../../../segment_sinks";
31
+ import { DummySegmentSink } from "../../../segment_sinks/__tests__/mocks";
32
+ import type {
33
+ IRepresentationsChoice,
34
+ IRepresentationStreamCallbacks,
35
+ } from "../../representation";
36
+ import AdaptationStream from "../adaptation_stream";
37
+ import type { IAdaptationStreamCallbacks } from "../types";
38
+
39
+ const {
40
+ mockGetRepresentationsSwitchingStrategy,
41
+ mockRepresentationStream,
42
+ mockCancellableSleep,
43
+ mockFormatError,
44
+ } = vi.hoisted(() => ({
45
+ mockGetRepresentationsSwitchingStrategy: vi.fn(),
46
+ mockRepresentationStream: vi.fn(),
47
+ mockCancellableSleep: vi.fn(),
48
+ mockFormatError: vi.fn(),
49
+ }));
50
+
51
+ vi.mock("../get_representations_switch_strategy", () => ({
52
+ default: mockGetRepresentationsSwitchingStrategy,
53
+ }));
54
+
55
+ vi.mock("../../representation", () => ({
56
+ default: mockRepresentationStream,
57
+ }));
58
+
59
+ vi.mock("../../../../utils/cancellable_sleep", () => ({
60
+ default: mockCancellableSleep,
61
+ }));
62
+
63
+ vi.mock("../../../../errors", () => ({
64
+ formatError: mockFormatError,
65
+ }));
66
+
67
+ const DummySegmentQueue = makeMockedClass<SegmentQueue<unknown>>(
68
+ {
69
+ addEventListener: notImplemented("addEventListener"),
70
+ removeEventListener: notImplemented("removeEventListener"),
71
+ getRequestedInitSegment: notImplemented("getRequestedInitSegment"),
72
+ getRequestedMediaSegment: notImplemented("getRequestedMediaSegment"),
73
+ resetForContent: notImplemented("resetForContent"),
74
+ stop: notImplemented("stop"),
75
+ },
76
+ {},
77
+ );
78
+
79
+ const DummySegmentQueueCreator = makeMockedClass<SegmentQueueCreator>(
80
+ {
81
+ createSegmentQueue: notImplemented("createSegmentQueue"),
82
+ },
83
+ {},
84
+ );
85
+
86
+ describe("AdaptationStream", () => {
87
+ let manifest: IManifest;
88
+ interface IPlaybackObservation {
89
+ position: ObservationPosition;
90
+ paused: { last: boolean; pending: undefined };
91
+ speed: number;
92
+ canStream: boolean;
93
+ bufferGap: number;
94
+ duration: number;
95
+ maximumPosition: number;
96
+ }
97
+ interface IEstimate {
98
+ representation: IRepresentation | null;
99
+ bitrate: number | undefined;
100
+ knownStableBitrate?: number | undefined;
101
+ urgent: boolean;
102
+ }
103
+ let period: IPeriod;
104
+ let adaptation: IAdaptation;
105
+ let representation: IRepresentation;
106
+ let parentCanceller: TaskCanceller;
107
+ let playbackObserver: ReturnType<
108
+ typeof makeReadyOnlyPlaybackObserver<IPlaybackObservation>
109
+ >;
110
+ let manifestUpdateListener: ((updates: IPeriodsUpdateResult) => void) | undefined;
111
+
112
+ beforeEach(() => {
113
+ manifest = new DummyManifest();
114
+ period = new DummyPeriod({ id: "period-1", start: 0, end: 100 });
115
+ adaptation = new DummyAdaptation({ id: "adaptation-1", type: "video" });
116
+ representation = new DummyRepresentation({ id: "rep-1", bitrate: 1000 });
117
+ parentCanceller = new TaskCanceller("test");
118
+ manifestUpdateListener = undefined;
119
+ const initialObservation: IPlaybackObservation = {
120
+ position: new DummyObservationPosition({
121
+ getPolled: () => 10,
122
+ getWanted: () => 10,
123
+ }),
124
+ paused: { last: false, pending: undefined },
125
+ speed: 1,
126
+ canStream: true,
127
+ bufferGap: 5,
128
+ duration: 100,
129
+ maximumPosition: 100,
130
+ };
131
+ playbackObserver = makeReadyOnlyPlaybackObserver(initialObservation);
132
+ vi.spyOn(playbackObserver.observer, "getCurrentTime").mockReturnValue(10);
133
+ vi.spyOn(playbackObserver.observer, "getReadyState").mockReturnValue(4);
134
+ vi.spyOn(manifest, "addEventListener").mockImplementation((eventName, listener) => {
135
+ if (eventName === "manifestUpdate") {
136
+ manifestUpdateListener = (updates) => {
137
+ listener(updates);
138
+ };
139
+ }
140
+ });
141
+ mockGetRepresentationsSwitchingStrategy.mockReset();
142
+ mockRepresentationStream.mockReset();
143
+ mockCancellableSleep.mockReset();
144
+ mockFormatError.mockReset();
145
+ mockCancellableSleep.mockResolvedValue(undefined);
146
+ mockFormatError.mockImplementation((err: Error) => err);
147
+ });
148
+
149
+ afterEach(() => {
150
+ parentCanceller.cancel("cleanup");
151
+ playbackObserver.reset();
152
+ vi.resetAllMocks();
153
+ });
154
+
155
+ // Test an actual complex issue we had at some point
156
+ it("should ignore cancellation errors from a superseded representation choice", async () => {
157
+ let resolve!: (value: undefined | PromiseLike<undefined>) => void;
158
+ let reject!: (reason?: unknown) => void;
159
+ const promise = new Promise<undefined>((res, rej) => {
160
+ resolve = res;
161
+ reject = rej;
162
+ });
163
+ const firstRemoval = { promise, resolve, reject };
164
+ const representations = new SharedReference<IRepresentationsChoice>({
165
+ switchingMode: "direct",
166
+ representationIds: ["rep-1"],
167
+ });
168
+ const { callbacks } = createCallbacks();
169
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
170
+ const removeBuffer = vi
171
+ .spyOn(segmentSink, "removeBuffer")
172
+ .mockImplementationOnce(() => firstRemoval.promise)
173
+ .mockResolvedValue(undefined);
174
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
175
+ type: "flush-buffer",
176
+ value: [{ start: 0, end: 1 }],
177
+ });
178
+
179
+ AdaptationStream(
180
+ createArgs({
181
+ representations,
182
+ segmentSink,
183
+ estimateRefValue: { representation: null, bitrate: undefined, urgent: false },
184
+ }),
185
+ callbacks,
186
+ parentCanceller.signal,
187
+ );
188
+
189
+ expect(removeBuffer).toHaveBeenCalledTimes(1);
190
+
191
+ representations.setValue({
192
+ switchingMode: "direct",
193
+ representationIds: ["rep-1", "rep-2"],
194
+ });
195
+ expect(removeBuffer).toHaveBeenCalledTimes(2);
196
+
197
+ firstRemoval.reject(new CancellationError("old choice", "cancelled"));
198
+ await Promise.resolve();
199
+ await Promise.resolve();
200
+
201
+ expect(callbacks.error).not.toHaveBeenCalled();
202
+ });
203
+
204
+ it("should not ignore non-cancellation errors from a superseded representation choice", async () => {
205
+ let resolve!: (value: undefined | PromiseLike<undefined>) => void;
206
+ let reject!: (reason?: unknown) => void;
207
+ const promise = new Promise<undefined>((res, rej) => {
208
+ resolve = res;
209
+ reject = rej;
210
+ });
211
+ const firstRemoval = { promise, resolve, reject };
212
+ const representations = new SharedReference<IRepresentationsChoice>({
213
+ switchingMode: "direct",
214
+ representationIds: ["rep-1"],
215
+ });
216
+ const { callbacks } = createCallbacks();
217
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
218
+ const removeBuffer = vi
219
+ .spyOn(segmentSink, "removeBuffer")
220
+ .mockImplementationOnce(() => firstRemoval.promise)
221
+ .mockResolvedValue(undefined);
222
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
223
+ type: "flush-buffer",
224
+ value: [{ start: 0, end: 1 }],
225
+ });
226
+
227
+ AdaptationStream(
228
+ createArgs({
229
+ representations,
230
+ segmentSink,
231
+ estimateRefValue: { representation: null, bitrate: undefined, urgent: false },
232
+ }),
233
+ callbacks,
234
+ parentCanceller.signal,
235
+ );
236
+
237
+ expect(removeBuffer).toHaveBeenCalledTimes(1);
238
+
239
+ representations.setValue({
240
+ switchingMode: "direct",
241
+ representationIds: ["rep-1", "rep-2"],
242
+ });
243
+ expect(removeBuffer).toHaveBeenCalledTimes(2);
244
+
245
+ firstRemoval.reject(new Error("old choice"));
246
+ await Promise.resolve();
247
+ await Promise.resolve();
248
+
249
+ expect(callbacks.error).toHaveBeenCalled();
250
+ });
251
+
252
+ it("should keep requesting a media-source reload until the switch is cancelled", async () => {
253
+ const { callbacks, waitingMediaSourceReload } = createCallbacks();
254
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
255
+ type: "needs-reload",
256
+ value: undefined,
257
+ });
258
+
259
+ AdaptationStream(
260
+ createArgs({
261
+ estimateRefValue: { representation: null, bitrate: undefined, urgent: false },
262
+ }),
263
+ callbacks,
264
+ parentCanceller.signal,
265
+ );
266
+
267
+ await Promise.resolve();
268
+ expect(waitingMediaSourceReload).toHaveBeenCalledTimes(1);
269
+
270
+ playbackObserver.emit({
271
+ position: new DummyObservationPosition({
272
+ getPolled: () => 11,
273
+ getWanted: () => 11,
274
+ }),
275
+ paused: { last: false, pending: undefined },
276
+ speed: 1,
277
+ canStream: true,
278
+ bufferGap: 5,
279
+ duration: 100,
280
+ maximumPosition: 100,
281
+ });
282
+ await Promise.resolve();
283
+
284
+ expect(waitingMediaSourceReload).toHaveBeenCalledTimes(2);
285
+ });
286
+
287
+ it("should reduce infinite buffer goals to 5 minutes before applying the ratio", async () => {
288
+ const { callbacks } = createCallbacks();
289
+ const observedBufferGoals: number[] = [];
290
+ let representationStreamCallCount = 0;
291
+
292
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
293
+ type: "continue",
294
+ value: undefined,
295
+ });
296
+ mockFormatError.mockReturnValue({ code: "BUFFER_FULL_ERROR" });
297
+ mockRepresentationStream.mockImplementation(
298
+ (
299
+ args: { options: { bufferGoal: SharedReference<number> } },
300
+ repCallbacks: IRepresentationStreamCallbacks,
301
+ ) => {
302
+ representationStreamCallCount++;
303
+ observedBufferGoals.push(args.options.bufferGoal.getValue());
304
+ if (representationStreamCallCount === 1) {
305
+ repCallbacks.error(new Error("buffer full"));
306
+ }
307
+ },
308
+ );
309
+
310
+ AdaptationStream(
311
+ createArgs({
312
+ estimateRefValue: {
313
+ representation,
314
+ bitrate: undefined,
315
+ knownStableBitrate: undefined,
316
+ urgent: false,
317
+ },
318
+ wantedBufferAhead: new SharedReference(Infinity),
319
+ }),
320
+ callbacks,
321
+ parentCanceller.signal,
322
+ );
323
+
324
+ await Promise.resolve();
325
+ await Promise.resolve();
326
+
327
+ expect(observedBufferGoals).toEqual([Infinity, 210]);
328
+ });
329
+
330
+ it("should create a representation stream directly on a continue strategy", () => {
331
+ const { callbacks, representationChange } = createCallbacks();
332
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
333
+ type: "continue",
334
+ value: undefined,
335
+ });
336
+
337
+ AdaptationStream(createArgs({}), callbacks, parentCanceller.signal);
338
+
339
+ expect(mockRepresentationStream).toHaveBeenCalledTimes(1);
340
+ expect(representationChange).toHaveBeenCalledWith({
341
+ type: "video",
342
+ adaptation,
343
+ period,
344
+ representation,
345
+ });
346
+ });
347
+
348
+ it("should not create a representation stream when the estimate selects no representation", () => {
349
+ const { callbacks, representationChange } = createCallbacks();
350
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
351
+ type: "continue",
352
+ value: undefined,
353
+ });
354
+
355
+ AdaptationStream(
356
+ createArgs({
357
+ estimateRefValue: { representation: null, bitrate: undefined, urgent: false },
358
+ }),
359
+ callbacks,
360
+ parentCanceller.signal,
361
+ );
362
+
363
+ expect(mockRepresentationStream).not.toHaveBeenCalled();
364
+ expect(representationChange).not.toHaveBeenCalled();
365
+ });
366
+
367
+ it("should clean buffers without flushing when strategy type is clean-buffer", async () => {
368
+ const { callbacks, needsBufferFlush } = createCallbacks();
369
+ const segmentSink = new DummySegmentSink({ bufferType: "video" });
370
+ const removeBuffer = vi
371
+ .spyOn(segmentSink, "removeBuffer")
372
+ .mockResolvedValue(undefined);
373
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
374
+ type: "clean-buffer",
375
+ value: [
376
+ { start: 1, end: 2 },
377
+ { start: 3, end: 4 },
378
+ ],
379
+ });
380
+
381
+ AdaptationStream(
382
+ createArgs({
383
+ segmentSink,
384
+ }),
385
+ callbacks,
386
+ parentCanceller.signal,
387
+ );
388
+
389
+ await Promise.resolve();
390
+ await Promise.resolve();
391
+
392
+ expect(removeBuffer).toHaveBeenNthCalledWith(1, 1, 2);
393
+ expect(removeBuffer).toHaveBeenNthCalledWith(2, 3, 4);
394
+ expect(needsBufferFlush).not.toHaveBeenCalled();
395
+ expect(mockRepresentationStream).toHaveBeenCalledTimes(1);
396
+ });
397
+
398
+ it("should emit bitrate changes only when the estimate changes to a defined bitrate", () => {
399
+ const { callbacks, bitrateEstimateChange } = createCallbacks();
400
+ const estimates = new SharedReference<IEstimate>({
401
+ representation,
402
+ bitrate: undefined,
403
+ knownStableBitrate: undefined,
404
+ urgent: false,
405
+ });
406
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
407
+ type: "continue",
408
+ value: undefined,
409
+ });
410
+
411
+ AdaptationStream(
412
+ createArgs({
413
+ representationEstimator: createRepresentationEstimator(estimates),
414
+ }),
415
+ callbacks,
416
+ parentCanceller.signal,
417
+ );
418
+
419
+ estimates.setValue({
420
+ representation,
421
+ bitrate: 100,
422
+ knownStableBitrate: 120,
423
+ urgent: false,
424
+ });
425
+ estimates.setValue({
426
+ representation,
427
+ bitrate: 100,
428
+ knownStableBitrate: 130,
429
+ urgent: false,
430
+ });
431
+ estimates.setValue({
432
+ representation,
433
+ bitrate: 150,
434
+ knownStableBitrate: 160,
435
+ urgent: false,
436
+ });
437
+
438
+ expect(bitrateEstimateChange).toHaveBeenNthCalledWith(1, {
439
+ type: "video",
440
+ bitrate: 100,
441
+ });
442
+ expect(bitrateEstimateChange).toHaveBeenNthCalledWith(2, {
443
+ type: "video",
444
+ bitrate: 150,
445
+ });
446
+ expect(bitrateEstimateChange).toHaveBeenCalledTimes(2);
447
+ });
448
+
449
+ it("should update fast-switch threshold from stable bitrate estimates", () => {
450
+ const { callbacks } = createCallbacks();
451
+ const estimates = new SharedReference<IEstimate>({
452
+ representation,
453
+ bitrate: 100,
454
+ knownStableBitrate: 120,
455
+ urgent: false,
456
+ });
457
+ let fastSwitchThresholdRef: SharedReference<number | undefined> | undefined;
458
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
459
+ type: "continue",
460
+ value: undefined,
461
+ });
462
+ mockRepresentationStream.mockImplementation(
463
+ (args: {
464
+ options: { fastSwitchThreshold: SharedReference<number | undefined> };
465
+ }) => {
466
+ fastSwitchThresholdRef = args.options.fastSwitchThreshold;
467
+ },
468
+ );
469
+
470
+ AdaptationStream(
471
+ createArgs({
472
+ options: {
473
+ drmSystemId: undefined,
474
+ enableFastSwitching: true,
475
+ },
476
+ representationEstimator: createRepresentationEstimator(estimates),
477
+ }),
478
+ callbacks,
479
+ parentCanceller.signal,
480
+ );
481
+
482
+ expect(fastSwitchThresholdRef?.getValue()).toBe(120);
483
+
484
+ estimates.setValue({
485
+ representation,
486
+ bitrate: 130,
487
+ knownStableBitrate: 240,
488
+ urgent: false,
489
+ });
490
+
491
+ expect(fastSwitchThresholdRef?.getValue()).toBe(240);
492
+ });
493
+
494
+ it("should update segment queue interruption state from playback observations", () => {
495
+ const { callbacks } = createCallbacks();
496
+ const segmentQueueCreator = new DummySegmentQueueCreator();
497
+ let interruptionRef: SharedReference<boolean> | undefined;
498
+ vi.spyOn(segmentQueueCreator, "createSegmentQueue").mockImplementation(
499
+ (_bufferType, _eventListeners, isMediaSegmentQueueInterrupted) => {
500
+ interruptionRef = isMediaSegmentQueueInterrupted;
501
+ return new DummySegmentQueue();
502
+ },
503
+ );
504
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
505
+ type: "continue",
506
+ value: undefined,
507
+ });
508
+
509
+ AdaptationStream(
510
+ createArgs({
511
+ segmentQueueCreator,
512
+ }),
513
+ callbacks,
514
+ parentCanceller.signal,
515
+ );
516
+
517
+ if (interruptionRef === undefined) {
518
+ throw new Error("Missing interruption reference");
519
+ }
520
+ expect(interruptionRef.getValue()).toBe(false);
521
+
522
+ playbackObserver.emit({
523
+ position: new DummyObservationPosition({
524
+ getPolled: () => 12,
525
+ getWanted: () => 12,
526
+ }),
527
+ paused: { last: false, pending: undefined },
528
+ speed: 1,
529
+ canStream: false,
530
+ bufferGap: 5,
531
+ duration: 100,
532
+ maximumPosition: 100,
533
+ });
534
+ expect(interruptionRef.getValue()).toBe(true);
535
+
536
+ playbackObserver.emit({
537
+ position: new DummyObservationPosition({
538
+ getPolled: () => 13,
539
+ getWanted: () => 13,
540
+ }),
541
+ paused: { last: false, pending: undefined },
542
+ speed: 1,
543
+ canStream: true,
544
+ bufferGap: 5,
545
+ duration: 100,
546
+ maximumPosition: 100,
547
+ });
548
+ expect(interruptionRef.getValue()).toBe(false);
549
+ });
550
+
551
+ it("should propagate urgent and non-urgent estimate changes to the terminate reference", () => {
552
+ const { callbacks } = createCallbacks();
553
+ const otherRepresentation = new DummyRepresentation({ id: "rep-2", bitrate: 2000 });
554
+ const estimates = new SharedReference<IEstimate>({
555
+ representation,
556
+ bitrate: 100,
557
+ knownStableBitrate: undefined,
558
+ urgent: false,
559
+ });
560
+ let terminateRef: SharedReference<unknown> | undefined;
561
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
562
+ type: "continue",
563
+ value: undefined,
564
+ });
565
+ mockRepresentationStream.mockImplementation(
566
+ (args: { terminate: SharedReference<unknown> }) => {
567
+ terminateRef = args.terminate;
568
+ },
569
+ );
570
+
571
+ AdaptationStream(
572
+ createArgs({
573
+ representationEstimator: createRepresentationEstimator(estimates),
574
+ }),
575
+ callbacks,
576
+ parentCanceller.signal,
577
+ );
578
+
579
+ estimates.setValue({
580
+ representation: otherRepresentation,
581
+ bitrate: 200,
582
+ knownStableBitrate: undefined,
583
+ urgent: false,
584
+ });
585
+ expect(terminateRef?.getValue()).toEqual({
586
+ urgent: false,
587
+ reason: "Non-urgent Representation switch",
588
+ });
589
+
590
+ estimates.setValue({
591
+ representation,
592
+ bitrate: 110,
593
+ knownStableBitrate: undefined,
594
+ urgent: false,
595
+ });
596
+ estimates.setValue({
597
+ representation: otherRepresentation,
598
+ bitrate: 300,
599
+ knownStableBitrate: undefined,
600
+ urgent: true,
601
+ });
602
+ expect(terminateRef?.getValue()).toEqual({
603
+ urgent: true,
604
+ reason: "Urgent Representation switch",
605
+ });
606
+ });
607
+
608
+ it("should recreate the representation stream when the previous one terminates", () => {
609
+ const { callbacks, representationChange } = createCallbacks();
610
+ const otherRepresentation = new DummyRepresentation({ id: "rep-2", bitrate: 2000 });
611
+ const estimates = new SharedReference<IEstimate>({
612
+ representation,
613
+ bitrate: 100,
614
+ knownStableBitrate: undefined,
615
+ urgent: false,
616
+ });
617
+ const streamCallbacks: IRepresentationStreamCallbacks[] = [];
618
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
619
+ type: "continue",
620
+ value: undefined,
621
+ });
622
+ mockRepresentationStream.mockImplementation(
623
+ (_args: unknown, repCallbacks: IRepresentationStreamCallbacks) => {
624
+ streamCallbacks.push(repCallbacks);
625
+ },
626
+ );
627
+
628
+ AdaptationStream(
629
+ createArgs({
630
+ representationEstimator: createRepresentationEstimator(estimates),
631
+ }),
632
+ callbacks,
633
+ parentCanceller.signal,
634
+ );
635
+
636
+ estimates.setValue({
637
+ representation: otherRepresentation,
638
+ bitrate: 200,
639
+ knownStableBitrate: undefined,
640
+ urgent: false,
641
+ });
642
+ streamCallbacks[0].terminating();
643
+
644
+ expect(mockRepresentationStream).toHaveBeenCalledTimes(2);
645
+ expect(representationChange).toHaveBeenNthCalledWith(2, {
646
+ type: "video",
647
+ adaptation,
648
+ period,
649
+ representation: otherRepresentation,
650
+ });
651
+ });
652
+
653
+ it("should forward added segments to the ABR callbacks", () => {
654
+ const { callbacks } = createCallbacks();
655
+ const addedSegment = vi.fn();
656
+ const payload = {
657
+ content: { period, adaptation, representation },
658
+ segment: createSegment({ time: 0, duration: 4 }),
659
+ buffered: [{ start: 0, end: 4 }],
660
+ };
661
+ let streamCallbacks: IRepresentationStreamCallbacks | undefined;
662
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
663
+ type: "continue",
664
+ value: undefined,
665
+ });
666
+ mockRepresentationStream.mockImplementation(
667
+ (_args: unknown, repCallbacks: IRepresentationStreamCallbacks) => {
668
+ streamCallbacks = repCallbacks;
669
+ },
670
+ );
671
+
672
+ AdaptationStream(
673
+ createArgs({
674
+ representationEstimator: createRepresentationEstimator(
675
+ new SharedReference<IEstimate>({
676
+ representation,
677
+ bitrate: 100,
678
+ knownStableBitrate: undefined,
679
+ urgent: false,
680
+ }),
681
+ { addedSegment },
682
+ ),
683
+ }),
684
+ callbacks,
685
+ parentCanceller.signal,
686
+ );
687
+
688
+ streamCallbacks?.addedSegment(payload);
689
+
690
+ expect(addedSegment).toHaveBeenCalledWith(payload);
691
+ });
692
+
693
+ it("should forward non-buffer-full errors to the adaptation callbacks", () => {
694
+ const { callbacks } = createCallbacks();
695
+ let streamCallbacks: IRepresentationStreamCallbacks | undefined;
696
+ const fatalError = new Error("fatal");
697
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
698
+ type: "continue",
699
+ value: undefined,
700
+ });
701
+ mockRepresentationStream.mockImplementation(
702
+ (_args: unknown, repCallbacks: IRepresentationStreamCallbacks) => {
703
+ streamCallbacks = repCallbacks;
704
+ },
705
+ );
706
+ mockFormatError.mockReturnValue({ code: "OTHER_ERROR" });
707
+
708
+ AdaptationStream(createArgs({}), callbacks, parentCanceller.signal);
709
+
710
+ streamCallbacks?.error(fatalError);
711
+
712
+ expect(callbacks.error).toHaveBeenCalledWith(fatalError);
713
+ });
714
+
715
+ it("should stop retrying buffer-full errors once the reduced goal is too small", () => {
716
+ const { callbacks } = createCallbacks();
717
+ let streamCallbacks: IRepresentationStreamCallbacks | undefined;
718
+ const formattedError = { code: "BUFFER_FULL_ERROR", reason: "full" };
719
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
720
+ type: "continue",
721
+ value: undefined,
722
+ });
723
+ mockRepresentationStream.mockImplementation(
724
+ (_args: unknown, repCallbacks: IRepresentationStreamCallbacks) => {
725
+ streamCallbacks = repCallbacks;
726
+ },
727
+ );
728
+ mockFormatError.mockReturnValue(formattedError);
729
+
730
+ AdaptationStream(
731
+ createArgs({
732
+ wantedBufferAhead: new SharedReference(2),
733
+ }),
734
+ callbacks,
735
+ parentCanceller.signal,
736
+ );
737
+
738
+ streamCallbacks?.error(new Error("buffer full"));
739
+
740
+ expect(mockCancellableSleep).not.toHaveBeenCalled();
741
+ expect(callbacks.error).toHaveBeenCalledWith(formattedError);
742
+ });
743
+
744
+ it("should request a reload when the active representation is removed from the manifest", () => {
745
+ const { callbacks, waitingMediaSourceReload } = createCallbacks();
746
+ const estimates = new SharedReference<IEstimate>({
747
+ representation,
748
+ bitrate: 100,
749
+ knownStableBitrate: undefined,
750
+ urgent: false,
751
+ });
752
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
753
+ type: "continue",
754
+ value: undefined,
755
+ });
756
+
757
+ AdaptationStream(
758
+ createArgs({
759
+ representationEstimator: createRepresentationEstimator(estimates),
760
+ }),
761
+ callbacks,
762
+ parentCanceller.signal,
763
+ );
764
+
765
+ if (manifestUpdateListener === undefined) {
766
+ throw new Error("Missing manifestUpdate listener");
767
+ }
768
+ manifestUpdateListener({
769
+ updatedPeriods: [
770
+ {
771
+ period: {
772
+ id: period.id,
773
+ start: period.start,
774
+ end: period.end,
775
+ duration: undefined,
776
+ streamEvents: [],
777
+ },
778
+ result: {
779
+ updatedAdaptations: [
780
+ {
781
+ adaptation: adaptation.id,
782
+ trackType: "video",
783
+ updatedRepresentations: [],
784
+ removedRepresentations: [representation.id],
785
+ addedRepresentations: [],
786
+ },
787
+ ],
788
+ removedAdaptations: [],
789
+ addedAdaptations: [],
790
+ updatedThumbnailTracks: [],
791
+ removedThumbnailTracks: [],
792
+ addedThumbnailTracks: [],
793
+ },
794
+ },
795
+ ],
796
+ addedPeriods: [],
797
+ removedPeriods: [],
798
+ });
799
+
800
+ expect(waitingMediaSourceReload).toHaveBeenCalledWith({
801
+ bufferType: "video",
802
+ period,
803
+ timeOffset: 0,
804
+ stayInPeriod: true,
805
+ });
806
+ });
807
+
808
+ it("should not request a reload for manifest updates unrelated to the active representation", () => {
809
+ const { callbacks, waitingMediaSourceReload } = createCallbacks();
810
+ mockGetRepresentationsSwitchingStrategy.mockReturnValue({
811
+ type: "continue",
812
+ value: undefined,
813
+ });
814
+
815
+ AdaptationStream(createArgs({}), callbacks, parentCanceller.signal);
816
+
817
+ if (manifestUpdateListener === undefined) {
818
+ throw new Error("Missing manifestUpdate listener");
819
+ }
820
+ manifestUpdateListener({
821
+ updatedPeriods: [
822
+ {
823
+ period: {
824
+ id: period.id,
825
+ start: period.start,
826
+ end: period.end,
827
+ duration: undefined,
828
+ streamEvents: [],
829
+ },
830
+ result: {
831
+ updatedAdaptations: [
832
+ {
833
+ adaptation: adaptation.id,
834
+ trackType: "video",
835
+ updatedRepresentations: [],
836
+ removedRepresentations: ["other"],
837
+ addedRepresentations: [],
838
+ },
839
+ ],
840
+ removedAdaptations: [],
841
+ addedAdaptations: [],
842
+ updatedThumbnailTracks: [],
843
+ removedThumbnailTracks: [],
844
+ addedThumbnailTracks: [],
845
+ },
846
+ },
847
+ ],
848
+ addedPeriods: [],
849
+ removedPeriods: [],
850
+ });
851
+
852
+ expect(waitingMediaSourceReload).not.toHaveBeenCalled();
853
+ });
854
+
855
+ function createArgs({
856
+ representations = new SharedReference<IRepresentationsChoice>({
857
+ switchingMode: "direct",
858
+ representationIds: ["rep-1"],
859
+ }),
860
+ segmentSink = new DummySegmentSink({ bufferType: "video" }),
861
+ estimateRefValue = {
862
+ representation,
863
+ bitrate: undefined,
864
+ knownStableBitrate: undefined,
865
+ urgent: false,
866
+ },
867
+ wantedBufferAhead = new SharedReference(30),
868
+ representationEstimator = createRepresentationEstimator(
869
+ new SharedReference(estimateRefValue),
870
+ ),
871
+ segmentQueueCreator = createSegmentQueueCreator(),
872
+ options = {
873
+ drmSystemId: undefined,
874
+ enableFastSwitching: false,
875
+ },
876
+ }: {
877
+ representations?: SharedReference<IRepresentationsChoice>;
878
+ segmentSink?: SegmentSink;
879
+ estimateRefValue?: IEstimate;
880
+ wantedBufferAhead?: SharedReference<number>;
881
+ representationEstimator?: IRepresentationEstimator;
882
+ segmentQueueCreator?: SegmentQueueCreator;
883
+ options?: {
884
+ drmSystemId: string | undefined;
885
+ enableFastSwitching: boolean;
886
+ };
887
+ }) {
888
+ return {
889
+ playbackObserver: playbackObserver.observer,
890
+ content: {
891
+ manifest,
892
+ period,
893
+ adaptation,
894
+ representations,
895
+ },
896
+ options,
897
+ representationEstimator,
898
+ segmentSink,
899
+ segmentQueueCreator,
900
+ wantedBufferAhead,
901
+ maxVideoBufferSize: new SharedReference(Infinity),
902
+ };
903
+ }
904
+
905
+ function createRepresentationEstimator(
906
+ estimates: SharedReference<IEstimate>,
907
+ callbacks: Partial<IRepresentationEstimatorCallbacks> = {},
908
+ ): IRepresentationEstimator {
909
+ return (
910
+ _context,
911
+ _currentRepresentation,
912
+ _representations,
913
+ _playbackObserver,
914
+ _stopAllEstimates,
915
+ ) => ({
916
+ estimates,
917
+ callbacks: {
918
+ requestBegin: vi.fn(),
919
+ requestEnd: vi.fn(),
920
+ requestProgress: vi.fn(),
921
+ metrics: vi.fn(),
922
+ addedSegment: vi.fn(),
923
+ ...callbacks,
924
+ },
925
+ });
926
+ }
927
+
928
+ function createSegmentQueueCreator(): SegmentQueueCreator {
929
+ const segmentQueueCreator = new DummySegmentQueueCreator();
930
+ vi.spyOn(segmentQueueCreator, "createSegmentQueue").mockImplementation(() => {
931
+ return new DummySegmentQueue();
932
+ });
933
+ return segmentQueueCreator;
934
+ }
935
+
936
+ function createCallbacks(): {
937
+ callbacks: IAdaptationStreamCallbacks;
938
+ bitrateEstimateChange: ReturnType<typeof vi.fn>;
939
+ needsBufferFlush: ReturnType<typeof vi.fn>;
940
+ representationChange: ReturnType<typeof vi.fn>;
941
+ waitingMediaSourceReload: ReturnType<typeof vi.fn>;
942
+ } {
943
+ const bitrateEstimateChange = vi.fn();
944
+ const needsBufferFlush = vi.fn();
945
+ const representationChange = vi.fn();
946
+ const waitingMediaSourceReload = vi.fn();
947
+ return {
948
+ bitrateEstimateChange,
949
+ needsBufferFlush,
950
+ representationChange,
951
+ waitingMediaSourceReload,
952
+ callbacks: {
953
+ bitrateEstimateChange,
954
+ representationChange,
955
+ waitingMediaSourceReload,
956
+ needsBufferFlush,
957
+ streamStatusUpdate: vi.fn(),
958
+ encryptionDataEncountered: vi.fn(),
959
+ manifestMightBeOufOfSync: vi.fn(),
960
+ needsManifestRefresh: vi.fn(),
961
+ inbandEvent: vi.fn(),
962
+ warning: vi.fn(),
963
+ error: vi.fn(),
964
+ },
965
+ };
966
+ }
967
+ });
968
+
969
+ function notImplemented(name: string): () => never {
970
+ return () => {
971
+ throw new Error(`${name} not implemented`);
972
+ };
973
+ }