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

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 (222) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  5. package/dist/commonjs/compat/browser_compatibility_types.d.ts +16 -14
  6. package/dist/commonjs/compat/browser_compatibility_types.d.ts.map +1 -1
  7. package/dist/commonjs/compat/browser_compatibility_types.js +7 -4
  8. package/dist/commonjs/compat/browser_version.d.ts +2 -1
  9. package/dist/commonjs/compat/browser_version.d.ts.map +1 -1
  10. package/dist/commonjs/compat/browser_version.js +13 -0
  11. package/dist/commonjs/compat/disable_remote_playback_on_managed_media_source.d.ts.map +1 -1
  12. package/dist/commonjs/compat/disable_remote_playback_on_managed_media_source.js +1 -1
  13. package/dist/commonjs/compat/is_codec_supported.d.ts.map +1 -1
  14. package/dist/commonjs/compat/is_codec_supported.js +4 -3
  15. package/dist/commonjs/compat/should_patch_out_dolby_vision_config_data_from_mp4.d.ts +26 -0
  16. package/dist/commonjs/compat/should_patch_out_dolby_vision_config_data_from_mp4.d.ts.map +1 -0
  17. package/dist/commonjs/compat/should_patch_out_dolby_vision_config_data_from_mp4.js +45 -0
  18. package/dist/commonjs/core/entry/utils.js +3 -3
  19. package/dist/commonjs/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
  20. package/dist/commonjs/core/fetchers/segment/segment_fetcher.js +2 -1
  21. package/dist/commonjs/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.d.ts.map +1 -1
  22. package/dist/commonjs/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.js +2 -1
  23. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/defaultCodecsFinder.d.ts.map +1 -1
  24. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/defaultCodecsFinder.js +8 -6
  25. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/mediaContentType.d.ts.map +1 -1
  26. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/mediaContentType.js +4 -3
  27. package/dist/commonjs/initialize_worker.js +1 -1
  28. package/dist/commonjs/main_thread/api/debug/modules/segment_buffer_content.js +3 -3
  29. package/dist/commonjs/main_thread/api/public_api.js +2 -2
  30. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
  31. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.js +24 -12
  32. package/dist/commonjs/manifest/classes/adaptation.d.ts.map +1 -1
  33. package/dist/commonjs/manifest/classes/adaptation.js +3 -1
  34. package/dist/commonjs/manifest/classes/representation.d.ts +6 -2
  35. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  36. package/dist/commonjs/manifest/classes/representation.js +45 -23
  37. package/dist/commonjs/manifest/types.d.ts +14 -6
  38. package/dist/commonjs/manifest/types.d.ts.map +1 -1
  39. package/dist/commonjs/manifest/utils.js +4 -4
  40. package/dist/commonjs/mse/main_media_source_interface.d.ts.map +1 -1
  41. package/dist/commonjs/mse/main_media_source_interface.js +3 -2
  42. package/dist/commonjs/mse/worker_media_source_interface.d.ts.map +1 -1
  43. package/dist/commonjs/parsers/containers/isobmff/get_box.d.ts +12 -1
  44. package/dist/commonjs/parsers/containers/isobmff/get_box.d.ts.map +1 -1
  45. package/dist/commonjs/parsers/containers/isobmff/get_box.js +16 -0
  46. package/dist/commonjs/parsers/containers/isobmff/index.d.ts +2 -1
  47. package/dist/commonjs/parsers/containers/isobmff/index.d.ts.map +1 -1
  48. package/dist/commonjs/parsers/containers/isobmff/index.js +3 -1
  49. package/dist/commonjs/parsers/containers/isobmff/remove_dolby_vision_config_data.d.ts +13 -0
  50. package/dist/commonjs/parsers/containers/isobmff/remove_dolby_vision_config_data.d.ts.map +1 -0
  51. package/dist/commonjs/parsers/containers/isobmff/remove_dolby_vision_config_data.js +180 -0
  52. package/dist/commonjs/parsers/manifest/metaplaylist/metaplaylist_parser.js +4 -4
  53. package/dist/commonjs/transports/dash/segment_parser.d.ts.map +1 -1
  54. package/dist/commonjs/transports/dash/segment_parser.js +6 -0
  55. package/dist/commonjs/transports/dash/text_parser.d.ts.map +1 -1
  56. package/dist/commonjs/transports/dash/text_parser.js +5 -1
  57. package/dist/commonjs/transports/local/text_parser.d.ts.map +1 -1
  58. package/dist/commonjs/transports/local/text_parser.js +5 -1
  59. package/dist/commonjs/transports/metaplaylist/pipelines.d.ts.map +1 -1
  60. package/dist/commonjs/transports/metaplaylist/pipelines.js +2 -1
  61. package/dist/commonjs/transports/smooth/pipelines.js +3 -3
  62. package/dist/commonjs/transports/types.d.ts +28 -2
  63. package/dist/commonjs/transports/types.d.ts.map +1 -1
  64. package/dist/commonjs/transports/utils/parse_text_track.d.ts +9 -2
  65. package/dist/commonjs/transports/utils/parse_text_track.d.ts.map +1 -1
  66. package/dist/commonjs/transports/utils/parse_text_track.js +1 -1
  67. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  68. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  69. package/dist/es2017/compat/browser_compatibility_types.d.ts +16 -14
  70. package/dist/es2017/compat/browser_compatibility_types.d.ts.map +1 -1
  71. package/dist/es2017/compat/browser_compatibility_types.js +7 -1
  72. package/dist/es2017/compat/browser_version.d.ts +2 -1
  73. package/dist/es2017/compat/browser_version.d.ts.map +1 -1
  74. package/dist/es2017/compat/browser_version.js +13 -1
  75. package/dist/es2017/compat/disable_remote_playback_on_managed_media_source.d.ts.map +1 -1
  76. package/dist/es2017/compat/disable_remote_playback_on_managed_media_source.js +2 -2
  77. package/dist/es2017/compat/is_codec_supported.d.ts.map +1 -1
  78. package/dist/es2017/compat/is_codec_supported.js +2 -1
  79. package/dist/es2017/compat/should_patch_out_dolby_vision_config_data_from_mp4.d.ts +26 -0
  80. package/dist/es2017/compat/should_patch_out_dolby_vision_config_data_from_mp4.d.ts.map +1 -0
  81. package/dist/es2017/compat/should_patch_out_dolby_vision_config_data_from_mp4.js +42 -0
  82. package/dist/es2017/core/entry/utils.js +4 -4
  83. package/dist/es2017/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
  84. package/dist/es2017/core/fetchers/segment/segment_fetcher.js +2 -1
  85. package/dist/es2017/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.d.ts.map +1 -1
  86. package/dist/es2017/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.js +2 -1
  87. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/defaultCodecsFinder.d.ts.map +1 -1
  88. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/defaultCodecsFinder.js +3 -1
  89. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/mediaContentType.d.ts.map +1 -1
  90. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/mediaContentType.js +2 -1
  91. package/dist/es2017/initialize_worker.js +1 -1
  92. package/dist/es2017/main_thread/api/debug/modules/segment_buffer_content.js +3 -3
  93. package/dist/es2017/main_thread/api/public_api.js +2 -2
  94. package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
  95. package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.js +9 -7
  96. package/dist/es2017/manifest/classes/adaptation.d.ts.map +1 -1
  97. package/dist/es2017/manifest/classes/adaptation.js +3 -1
  98. package/dist/es2017/manifest/classes/representation.d.ts +6 -2
  99. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  100. package/dist/es2017/manifest/classes/representation.js +45 -23
  101. package/dist/es2017/manifest/types.d.ts +14 -6
  102. package/dist/es2017/manifest/types.d.ts.map +1 -1
  103. package/dist/es2017/manifest/utils.js +4 -4
  104. package/dist/es2017/mse/main_media_source_interface.d.ts.map +1 -1
  105. package/dist/es2017/mse/main_media_source_interface.js +2 -1
  106. package/dist/es2017/mse/worker_media_source_interface.d.ts.map +1 -1
  107. package/dist/es2017/parsers/containers/isobmff/get_box.d.ts +12 -1
  108. package/dist/es2017/parsers/containers/isobmff/get_box.d.ts.map +1 -1
  109. package/dist/es2017/parsers/containers/isobmff/get_box.js +16 -1
  110. package/dist/es2017/parsers/containers/isobmff/index.d.ts +2 -1
  111. package/dist/es2017/parsers/containers/isobmff/index.d.ts.map +1 -1
  112. package/dist/es2017/parsers/containers/isobmff/index.js +2 -1
  113. package/dist/es2017/parsers/containers/isobmff/remove_dolby_vision_config_data.d.ts +13 -0
  114. package/dist/es2017/parsers/containers/isobmff/remove_dolby_vision_config_data.d.ts.map +1 -0
  115. package/dist/es2017/parsers/containers/isobmff/remove_dolby_vision_config_data.js +155 -0
  116. package/dist/es2017/parsers/manifest/metaplaylist/metaplaylist_parser.js +4 -4
  117. package/dist/es2017/transports/dash/segment_parser.d.ts.map +1 -1
  118. package/dist/es2017/transports/dash/segment_parser.js +7 -1
  119. package/dist/es2017/transports/dash/text_parser.d.ts.map +1 -1
  120. package/dist/es2017/transports/dash/text_parser.js +5 -1
  121. package/dist/es2017/transports/local/text_parser.d.ts.map +1 -1
  122. package/dist/es2017/transports/local/text_parser.js +5 -1
  123. package/dist/es2017/transports/metaplaylist/pipelines.d.ts.map +1 -1
  124. package/dist/es2017/transports/metaplaylist/pipelines.js +2 -1
  125. package/dist/es2017/transports/smooth/pipelines.js +3 -3
  126. package/dist/es2017/transports/types.d.ts +28 -2
  127. package/dist/es2017/transports/types.d.ts.map +1 -1
  128. package/dist/es2017/transports/utils/parse_text_track.d.ts +9 -2
  129. package/dist/es2017/transports/utils/parse_text_track.d.ts.map +1 -1
  130. package/dist/es2017/transports/utils/parse_text_track.js +1 -1
  131. package/dist/worker.js +6 -6
  132. package/eslint.config.mjs +1 -14
  133. package/package.json +1 -1
  134. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  135. package/src/compat/__tests__/browser_compatibility_types.test.ts +15 -26
  136. package/src/compat/__tests__/browser_version.test.ts +57 -18
  137. package/src/compat/__tests__/clear_element_src.test.ts +20 -40
  138. package/src/compat/__tests__/is_codec_supported.test.ts +39 -44
  139. package/src/compat/__tests__/is_vtt_cue.test.ts +5 -8
  140. package/src/compat/__tests__/make_vtt_cue.test.ts +20 -31
  141. package/src/compat/__tests__/remove_cue.test.ts +8 -20
  142. package/src/compat/__tests__/should_patch_out_dolby_vision_config_data_from_mp4.test.ts +78 -0
  143. package/src/compat/browser_compatibility_types.ts +8 -1
  144. package/src/compat/browser_version.ts +17 -1
  145. package/src/compat/disable_remote_playback_on_managed_media_source.ts +2 -2
  146. package/src/compat/is_codec_supported.ts +2 -1
  147. package/src/compat/should_patch_out_dolby_vision_config_data_from_mp4.ts +50 -0
  148. package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +68 -28
  149. package/src/core/adaptive/__tests__/buffer_based_chooser.test.ts +26 -84
  150. package/src/core/entry/__tests__/utils.test.ts +7 -1
  151. package/src/core/entry/utils.ts +4 -4
  152. package/src/core/fetchers/segment/segment_fetcher.ts +2 -1
  153. package/src/errors/__tests__/format_error.test.ts +23 -24
  154. package/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts +2 -1
  155. package/src/experimental/tools/mediaCapabilitiesProber/__tests__/probers/mediaDisplayInfos.test.ts +106 -0
  156. package/src/experimental/tools/mediaCapabilitiesProber/probers/__tests__/HDCPPolicy.test.ts +19 -42
  157. package/src/experimental/tools/mediaCapabilitiesProber/probers/__tests__/decodingInfo.test.ts +33 -40
  158. package/src/experimental/tools/mediaCapabilitiesProber/probers/__tests__/defaultCodecsFinder.test.ts +44 -68
  159. package/src/experimental/tools/mediaCapabilitiesProber/probers/__tests__/mediaContentType.test.ts +45 -80
  160. package/src/experimental/tools/mediaCapabilitiesProber/probers/defaultCodecsFinder.ts +3 -1
  161. package/src/experimental/tools/mediaCapabilitiesProber/probers/mediaContentType.ts +2 -1
  162. package/src/features/__tests__/add_features.test.ts +12 -27
  163. package/src/initialize_worker.ts +1 -1
  164. package/src/main_thread/api/__tests__/option_utils.test.ts +38 -67
  165. package/src/main_thread/api/__tests__/public_api.test.ts +56 -138
  166. package/src/main_thread/api/debug/modules/segment_buffer_content.ts +3 -3
  167. package/src/main_thread/api/public_api.ts +2 -2
  168. package/src/main_thread/decrypt/__tests__/__global__/find_key_system.test.ts +46 -28
  169. package/src/main_thread/decrypt/__tests__/__global__/get_license.test.ts +40 -10
  170. package/src/main_thread/decrypt/__tests__/__global__/init_data.test.ts +112 -129
  171. package/src/main_thread/decrypt/__tests__/__global__/media_key_system_access.test.ts +282 -377
  172. package/src/main_thread/decrypt/__tests__/__global__/media_keys.test.ts +70 -63
  173. package/src/main_thread/decrypt/__tests__/__global__/server_certificate.test.ts +73 -54
  174. package/src/main_thread/decrypt/__tests__/__global__/utils.ts +43 -83
  175. package/src/main_thread/decrypt/__tests__/find_key_system.test.ts +36 -17
  176. package/src/main_thread/decrypt/utils/__tests__/clean_old_stored_persistent_info.test.ts +44 -48
  177. package/src/main_thread/init/utils/__tests__/are_same_stream_events.test.ts +7 -22
  178. package/src/main_thread/init/utils/__tests__/refresh_scheduled_events_list.test.ts +3 -6
  179. package/src/main_thread/init/utils/__tests__/update_manifest_codec_support.test.ts +42 -19
  180. package/src/main_thread/init/utils/update_manifest_codec_support.ts +8 -6
  181. package/src/manifest/classes/__tests__/adaptation.test.ts +69 -125
  182. package/src/manifest/classes/__tests__/manifest.test.ts +88 -95
  183. package/src/manifest/classes/__tests__/period.test.ts +167 -117
  184. package/src/manifest/classes/__tests__/representation.test.ts +84 -25
  185. package/src/manifest/classes/__tests__/update_periods.test.ts +96 -225
  186. package/src/manifest/classes/adaptation.ts +4 -1
  187. package/src/manifest/classes/representation.ts +59 -27
  188. package/src/manifest/types.ts +14 -6
  189. package/src/manifest/utils.ts +4 -4
  190. package/src/mse/__tests__/main_media_source_interface.test.ts +3 -1
  191. package/src/mse/__tests__/worker_media_source_interface.test.ts +608 -0
  192. package/src/mse/main_media_source_interface.ts +2 -1
  193. package/src/mse/worker_media_source_interface.ts +2 -2
  194. package/src/parsers/containers/isobmff/__tests__/get_box.test.ts +449 -0
  195. package/src/parsers/containers/isobmff/__tests__/remove_dolby_vision_config_data.test.ts +282 -0
  196. package/src/parsers/containers/isobmff/get_box.ts +17 -0
  197. package/src/parsers/containers/isobmff/index.ts +8 -1
  198. package/src/parsers/containers/isobmff/remove_dolby_vision_config_data.ts +179 -0
  199. package/src/parsers/manifest/dash/common/__tests__/get_clock_offset.test.ts +19 -25
  200. package/src/parsers/manifest/dash/common/__tests__/get_http_utc-timing_url.test.ts +7 -19
  201. package/src/parsers/manifest/dash/js-parser/node_parsers/__tests__/ContentProtection.test.ts +26 -37
  202. package/src/parsers/manifest/dash/js-parser/node_parsers/__tests__/Initialization.test.ts +24 -53
  203. package/src/parsers/manifest/dash/js-parser/node_parsers/__tests__/SegmentURL.test.ts +31 -78
  204. package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +6 -4
  205. package/src/parsers/manifest/utils/__tests__/update_segment_timeline.test.ts +79 -87
  206. package/src/parsers/texttracks/ttml/__tests__/resolve_styles_inheritance.test.ts +20 -26
  207. package/src/parsers/texttracks/ttml/html/__tests__/__global__/html_ttml_parser.test.ts +2 -7
  208. package/src/parsers/texttracks/webvtt/__tests__/parse_cue_block.test.ts +33 -30
  209. package/src/parsers/texttracks/webvtt/__tests__/parse_style_block.test.ts +33 -100
  210. package/src/parsers/texttracks/webvtt/html/__tests__/convert_payload_to_html.test.ts +16 -18
  211. package/src/parsers/texttracks/webvtt/html/__tests__/parse_webvtt_to_div.test.ts +73 -78
  212. package/src/parsers/texttracks/webvtt/html/__tests__/to_html.test.ts +36 -37
  213. package/src/transports/dash/segment_parser.ts +13 -0
  214. package/src/transports/dash/text_parser.ts +5 -1
  215. package/src/transports/local/text_parser.ts +5 -1
  216. package/src/transports/metaplaylist/pipelines.ts +2 -1
  217. package/src/transports/smooth/isobmff/__tests__/create_boxes.test.ts +150 -127
  218. package/src/transports/smooth/pipelines.ts +3 -3
  219. package/src/transports/types.ts +28 -2
  220. package/src/transports/utils/__tests__/check_isobmff_integrity.test.ts +49 -75
  221. package/src/transports/utils/parse_text_track.ts +10 -3
  222. package/src/utils/__tests__/object_assign.test.ts +2 -2
@@ -0,0 +1,608 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { CoreMessageType } from "../../core/types";
3
+ import { SourceBufferError } from "../../errors";
4
+ import { CancellationError } from "../../utils/task_canceller";
5
+ import { SourceBufferType } from "../types";
6
+ import WorkerMediaSourceInterface, {
7
+ WorkerSourceBufferInterface,
8
+ } from "../worker_media_source_interface";
9
+
10
+ const { mockLog } = vi.hoisted(() => {
11
+ return {
12
+ mockLog: {
13
+ warn: vi.fn(),
14
+ info: vi.fn(),
15
+ debug: vi.fn(),
16
+ error: vi.fn(),
17
+ },
18
+ };
19
+ });
20
+
21
+ vi.mock("../../log", () => ({ default: mockLog }));
22
+
23
+ function makeMessageSender() {
24
+ return vi.fn();
25
+ }
26
+
27
+ function makeWmsi(messageSender = makeMessageSender()) {
28
+ return {
29
+ wmsi: new WorkerMediaSourceInterface("ms-id-1", "content-1", messageSender),
30
+ messageSender,
31
+ };
32
+ }
33
+
34
+ function makeWsbi(messageSender = makeMessageSender()) {
35
+ const wsbi = new WorkerSourceBufferInterface(
36
+ SourceBufferType.Video,
37
+ "video/mp4; codecs=avc1",
38
+ "ms-id-1",
39
+ messageSender,
40
+ );
41
+ return { wsbi, messageSender };
42
+ }
43
+
44
+ /**
45
+ * Returns the first argument of the mock's most recent call, cast to T.
46
+ * Throws if the mock was never called, so tests fail clearly rather than
47
+ * silently receiving `undefined`.
48
+ */
49
+ function getLastCallFirstArg<T>(fn: ReturnType<typeof vi.fn>): T {
50
+ if (fn.mock.lastCall === undefined) {
51
+ throw new Error("Mock was never called");
52
+ }
53
+ return fn.mock.lastCall[0] as T;
54
+ }
55
+
56
+ describe("WorkerMediaSourceInterface", () => {
57
+ afterEach(() => {
58
+ vi.resetAllMocks();
59
+ });
60
+
61
+ it("sends a CreateMediaSource message on construction", () => {
62
+ const messageSender = makeMessageSender();
63
+ new WorkerMediaSourceInterface("ms-id-1", "content-1", messageSender);
64
+ expect(messageSender).toHaveBeenCalledOnce();
65
+ expect(messageSender).toHaveBeenCalledWith({
66
+ contentId: "content-1",
67
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
68
+ mediaSourceId: expect.stringMatching(/\d+/),
69
+ type: CoreMessageType.CreateMediaSource,
70
+ });
71
+ });
72
+
73
+ it("initialises readyState as 'closed' and sourceBuffers as []", () => {
74
+ const { wmsi } = makeWmsi();
75
+ expect(wmsi.readyState).toBe("closed");
76
+ expect(wmsi.sourceBuffers).toEqual([]);
77
+ });
78
+
79
+ describe("onMediaSourceReadyStateChanged", () => {
80
+ it("sets readyState to 'open' and triggers mediaSourceOpen", async () => {
81
+ const { wmsi } = makeWmsi();
82
+ const prom = new Promise<void>((res, rej) => {
83
+ wmsi.addEventListener("mediaSourceOpen", () => {
84
+ wmsi.dispose("testend");
85
+ res();
86
+ });
87
+ wmsi.addEventListener("mediaSourceEnded", () => {
88
+ rej();
89
+ });
90
+ wmsi.addEventListener("mediaSourceClose", () => {
91
+ rej();
92
+ });
93
+ });
94
+ wmsi.onMediaSourceReadyStateChanged("open");
95
+ expect(wmsi.readyState).toBe("open");
96
+ await prom;
97
+ });
98
+
99
+ it("sets readyState to 'ended' and triggers mediaSourceEnded", async () => {
100
+ const { wmsi } = makeWmsi();
101
+ const prom = new Promise<void>((res, rej) => {
102
+ wmsi.addEventListener("mediaSourceOpen", () => {
103
+ rej();
104
+ });
105
+ wmsi.addEventListener("mediaSourceEnded", () => {
106
+ wmsi.dispose("testend");
107
+ res();
108
+ });
109
+ wmsi.addEventListener("mediaSourceClose", () => {
110
+ rej();
111
+ });
112
+ });
113
+ wmsi.onMediaSourceReadyStateChanged("ended");
114
+ expect(wmsi.readyState).toBe("ended");
115
+ await prom;
116
+ });
117
+
118
+ it("sets readyState to 'closed' and triggers mediaSourceClose", async () => {
119
+ const { wmsi } = makeWmsi();
120
+ const prom = new Promise<void>((res, rej) => {
121
+ let hasReceivedOpen = false;
122
+ wmsi.addEventListener("mediaSourceOpen", () => {
123
+ hasReceivedOpen = true;
124
+ });
125
+ wmsi.addEventListener("mediaSourceEnded", () => {
126
+ rej();
127
+ });
128
+ wmsi.addEventListener("mediaSourceClose", () => {
129
+ expect(hasReceivedOpen).toEqual(true);
130
+ wmsi.dispose("testend");
131
+ res();
132
+ });
133
+ });
134
+ wmsi.onMediaSourceReadyStateChanged("open");
135
+ wmsi.onMediaSourceReadyStateChanged("closed");
136
+ expect(wmsi.readyState).toBe("closed");
137
+ await prom;
138
+ });
139
+ });
140
+
141
+ describe("addSourceBuffer", () => {
142
+ it("sends an AddSourceBuffer message with correct fields", () => {
143
+ const { wmsi, messageSender } = makeWmsi();
144
+ messageSender.mockClear();
145
+ wmsi.addSourceBuffer(SourceBufferType.Video, "video/mp4; codecs=avc1");
146
+ expect(messageSender).toHaveBeenCalledOnce();
147
+ expect(messageSender).toHaveBeenCalledWith({
148
+ type: CoreMessageType.AddSourceBuffer,
149
+ mediaSourceId: "ms-id-1",
150
+ value: {
151
+ sourceBufferType: SourceBufferType.Video,
152
+ codec: "video/mp4; codecs=avc1",
153
+ },
154
+ });
155
+ });
156
+
157
+ it("pushes a WorkerSourceBufferInterface into sourceBuffers and returns it", () => {
158
+ const { wmsi } = makeWmsi();
159
+ const sb = wmsi.addSourceBuffer(
160
+ SourceBufferType.Audio,
161
+ "audio/mp4; codecs=mp4a.40.2",
162
+ );
163
+ expect(wmsi.sourceBuffers).toHaveLength(1);
164
+ expect(wmsi.sourceBuffers[0]).toBe(sb);
165
+ expect(sb).toBeInstanceOf(WorkerSourceBufferInterface);
166
+ });
167
+ });
168
+
169
+ describe("setDuration", () => {
170
+ it("sends an UpdateMediaSourceDuration message", () => {
171
+ const { wmsi, messageSender } = makeWmsi();
172
+ messageSender.mockClear();
173
+ wmsi.setDuration(120.5, true);
174
+ expect(messageSender).toHaveBeenCalledOnce();
175
+ expect(messageSender).toHaveBeenCalledWith({
176
+ type: CoreMessageType.UpdateMediaSourceDuration,
177
+ mediaSourceId: "ms-id-1",
178
+ value: {
179
+ duration: 120.5,
180
+ isRealEndKnown: true,
181
+ },
182
+ });
183
+ });
184
+ });
185
+
186
+ describe("interruptDurationSetting", () => {
187
+ it("sends an InterruptMediaSourceDurationUpdate message", () => {
188
+ const { wmsi, messageSender } = makeWmsi();
189
+ messageSender.mockClear();
190
+ wmsi.interruptDurationSetting();
191
+ expect(messageSender).toHaveBeenCalledOnce();
192
+ expect(messageSender).toHaveBeenCalledWith({
193
+ type: CoreMessageType.InterruptMediaSourceDurationUpdate,
194
+ mediaSourceId: "ms-id-1",
195
+ value: null,
196
+ });
197
+ });
198
+ });
199
+
200
+ describe("maintainEndOfStream", () => {
201
+ it("sends an EndOfStream message", () => {
202
+ const { wmsi, messageSender } = makeWmsi();
203
+ messageSender.mockClear();
204
+ wmsi.maintainEndOfStream();
205
+ expect(messageSender).toHaveBeenCalledOnce();
206
+ expect(messageSender).toHaveBeenCalledWith({
207
+ type: CoreMessageType.EndOfStream,
208
+ mediaSourceId: "ms-id-1",
209
+ value: null,
210
+ });
211
+ });
212
+ });
213
+
214
+ describe("stopEndOfStream", () => {
215
+ it("sends an InterruptEndOfStream message", () => {
216
+ const { wmsi, messageSender } = makeWmsi();
217
+ messageSender.mockClear();
218
+ wmsi.stopEndOfStream();
219
+ expect(messageSender).toHaveBeenCalledOnce();
220
+ expect(messageSender).toHaveBeenCalledWith({
221
+ type: CoreMessageType.InterruptEndOfStream,
222
+ mediaSourceId: "ms-id-1",
223
+ value: null,
224
+ });
225
+ });
226
+ });
227
+
228
+ describe("dispose", () => {
229
+ it("sends a DisposeMediaSource message", () => {
230
+ const { wmsi, messageSender } = makeWmsi();
231
+ messageSender.mockClear();
232
+ wmsi.dispose("test reason");
233
+ expect(messageSender).toHaveBeenCalledWith({
234
+ mediaSourceId: "ms-id-1",
235
+ type: CoreMessageType.DisposeMediaSource,
236
+ value: null,
237
+ });
238
+ });
239
+
240
+ it("calls dispose on every child source buffer", () => {
241
+ const { wmsi, messageSender } = makeWmsi();
242
+ wmsi.addSourceBuffer(SourceBufferType.Video, "video/mp4");
243
+ wmsi.addSourceBuffer(SourceBufferType.Audio, "audio/mp4");
244
+ messageSender.mockClear();
245
+ wmsi.dispose("cleanup");
246
+ expect(messageSender).toHaveBeenCalledWith({
247
+ mediaSourceId: "ms-id-1",
248
+ type: CoreMessageType.AbortSourceBuffer,
249
+ sourceBufferType: SourceBufferType.Video,
250
+ value: null,
251
+ });
252
+ expect(messageSender).toHaveBeenCalledWith({
253
+ mediaSourceId: "ms-id-1",
254
+ type: CoreMessageType.AbortSourceBuffer,
255
+ sourceBufferType: SourceBufferType.Audio,
256
+ value: null,
257
+ });
258
+ });
259
+ });
260
+ });
261
+
262
+ describe("WorkerSourceBufferInterface", () => {
263
+ afterEach(() => {
264
+ vi.resetAllMocks();
265
+ });
266
+
267
+ it("initialises with correct type, codec, empty queues", () => {
268
+ const { wsbi } = makeWsbi();
269
+ expect(wsbi.type).toBe(SourceBufferType.Video);
270
+ expect(wsbi.codec).toBe("video/mp4; codecs=avc1");
271
+ expect(wsbi._queuedOperations).toHaveLength(0);
272
+ expect(wsbi._pendingOperations.size).toBe(0);
273
+ });
274
+
275
+ describe("appendBuffer", () => {
276
+ it("sends a SourceBufferAppend message with transferable ArrayBuffer", async () => {
277
+ const { wsbi, messageSender } = makeWsbi();
278
+ const buffer = new ArrayBuffer(8);
279
+ const promise = wsbi.appendBuffer(buffer, { codec: "video/mp4" });
280
+
281
+ expect(messageSender).toHaveBeenCalledOnce();
282
+ expect(messageSender).toHaveBeenCalledWith(
283
+ {
284
+ type: CoreMessageType.SourceBufferAppend,
285
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
286
+ operationId: expect.stringMatching(/\d+/),
287
+ mediaSourceId: "ms-id-1",
288
+ sourceBufferType: SourceBufferType.Video,
289
+ value: { data: buffer, params: { codec: "video/mp4" } },
290
+ },
291
+ [buffer],
292
+ );
293
+
294
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
295
+ const ranges = [{ start: 0, end: 10 }];
296
+ wsbi.onOperationSuccess(operationId, ranges);
297
+ await expect(promise).resolves.toEqual(ranges);
298
+ });
299
+
300
+ it("handles a TypedArray whose buffer is larger (slices correctly)", async () => {
301
+ const { wsbi, messageSender } = makeWsbi();
302
+ const bigBuffer = new ArrayBuffer(16);
303
+ // byteOffset=4, byteLength=4 → buffer.byteLength(16) !== byteLength(4)
304
+ const view = new Uint8Array(bigBuffer, 4, 4);
305
+ const promise = wsbi.appendBuffer(view, { codec: "video/mp4" });
306
+
307
+ expect(messageSender).toHaveBeenCalledOnce();
308
+ expect(messageSender).toHaveBeenCalledWith(
309
+ {
310
+ mediaSourceId: "ms-id-1",
311
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
312
+ operationId: expect.stringMatching(/\d+/),
313
+ sourceBufferType: SourceBufferType.Video,
314
+ type: CoreMessageType.SourceBufferAppend,
315
+ value: { data: bigBuffer, params: { codec: "video/mp4" } },
316
+ },
317
+ [bigBuffer],
318
+ );
319
+
320
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
321
+ wsbi.onOperationSuccess(operationId, []);
322
+ await expect(promise).resolves.toEqual([]);
323
+ });
324
+
325
+ it("handles a TypedArray whose byteLength equals buffer.byteLength", async () => {
326
+ const { wsbi, messageSender } = makeWsbi();
327
+ const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).buffer;
328
+ const view = new Uint8Array(buffer); // byteLength === buffer.byteLength
329
+ const promise = wsbi.appendBuffer(view, { codec: "video/mp4" });
330
+
331
+ expect(messageSender).toHaveBeenCalledWith(
332
+ {
333
+ mediaSourceId: "ms-id-1",
334
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
335
+ operationId: expect.stringMatching(/\d+/),
336
+ sourceBufferType: SourceBufferType.Video,
337
+ type: CoreMessageType.SourceBufferAppend,
338
+ value: { data: buffer, params: { codec: "video/mp4" } },
339
+ },
340
+ [buffer],
341
+ );
342
+
343
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
344
+ wsbi.onOperationSuccess(operationId, []);
345
+ await promise;
346
+ });
347
+
348
+ it("queues a second appendBuffer call while one is pending (MAX_QUEUE not reached)", async () => {
349
+ const { wsbi, messageSender } = makeWsbi();
350
+ const buf1 = new ArrayBuffer(4);
351
+ const buf2 = new ArrayBuffer(4);
352
+
353
+ const promise1 = wsbi.appendBuffer(buf1, { codec: "video/mp4" });
354
+ expect(messageSender).toHaveBeenCalledTimes(1);
355
+
356
+ const promise2 = wsbi.appendBuffer(buf2, { codec: "video/mp4" });
357
+ // With MAX = Infinity and no prior queued operations, both calls go through directly.
358
+ expect(messageSender).toHaveBeenCalledTimes(2);
359
+
360
+ // Two distinct calls happened: extract both operationIds in one typed cast.
361
+ const calls = messageSender.mock.calls as Array<[{ operationId: string }]>;
362
+ const op1 = calls[0][0].operationId;
363
+ const op2 = calls[1][0].operationId;
364
+ wsbi.onOperationSuccess(op1, []);
365
+ wsbi.onOperationSuccess(op2, []);
366
+ await promise1;
367
+ await promise2;
368
+ });
369
+ });
370
+
371
+ describe("remove", () => {
372
+ it("sends a SourceBufferRemove message and resolves with ranges", async () => {
373
+ const { wsbi, messageSender } = makeWsbi();
374
+ const promise = wsbi.remove(5, 10);
375
+
376
+ expect(messageSender).toHaveBeenCalledOnce();
377
+ expect(messageSender).toHaveBeenCalledWith({
378
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
379
+ operationId: expect.stringMatching(/\d+/),
380
+ type: CoreMessageType.SourceBufferRemove,
381
+ mediaSourceId: "ms-id-1",
382
+ sourceBufferType: SourceBufferType.Video,
383
+ value: { start: 5, end: 10 },
384
+ });
385
+
386
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
387
+ const ranges = [{ start: 0, end: 5 }];
388
+ wsbi.onOperationSuccess(operationId, ranges);
389
+ await expect(promise).resolves.toEqual(ranges);
390
+ });
391
+ });
392
+
393
+ describe("abort", () => {
394
+ it("sends an AbortSourceBuffer message", () => {
395
+ const { wsbi, messageSender } = makeWsbi();
396
+ wsbi.abort();
397
+ expect(messageSender).toHaveBeenCalledOnce();
398
+ expect(messageSender).toHaveBeenCalledWith({
399
+ type: CoreMessageType.AbortSourceBuffer,
400
+ mediaSourceId: "ms-id-1",
401
+ sourceBufferType: SourceBufferType.Video,
402
+ value: null,
403
+ });
404
+ });
405
+ });
406
+
407
+ describe("dispose", () => {
408
+ it("calls abort (sends AbortSourceBuffer)", () => {
409
+ const { wsbi, messageSender } = makeWsbi();
410
+ wsbi.dispose("test reason");
411
+ expect(messageSender).toHaveBeenCalledOnce();
412
+ expect(messageSender).toHaveBeenCalledWith({
413
+ mediaSourceId: "ms-id-1",
414
+ sourceBufferType: SourceBufferType.Video,
415
+ type: CoreMessageType.AbortSourceBuffer,
416
+ value: null,
417
+ });
418
+ });
419
+
420
+ it("rejects all pending operations with a CancellationError on dispose", async () => {
421
+ const { wsbi } = makeWsbi();
422
+ const buf1 = new ArrayBuffer(4);
423
+ const buf2 = new ArrayBuffer(4);
424
+ const promise1 = wsbi.appendBuffer(buf1, { codec: "video/mp4" });
425
+ const promise2 = wsbi.remove(0, 10);
426
+
427
+ // buf2 sits in the queue behind the two already-pending operations
428
+ wsbi._queuedOperations.push({
429
+ operationName: 0 /* SbiOperationName.Push */,
430
+ params: [buf2, { codec: "video/mp4" }],
431
+ resolve: vi.fn(),
432
+ reject: vi.fn(),
433
+ });
434
+
435
+ wsbi.dispose("cleanup");
436
+
437
+ await expect(promise1).rejects.toBeInstanceOf(CancellationError);
438
+ await expect(promise2).rejects.toBeInstanceOf(CancellationError);
439
+ });
440
+ });
441
+
442
+ describe("getBuffered", () => {
443
+ it("returns undefined", () => {
444
+ const { wsbi } = makeWsbi();
445
+ expect(wsbi.getBuffered()).toBeUndefined();
446
+ });
447
+ });
448
+
449
+ describe("onOperationSuccess", () => {
450
+ it("resolves the matching pending operation and removes it from the map", async () => {
451
+ const { wsbi, messageSender } = makeWsbi();
452
+ const buf = new ArrayBuffer(4);
453
+ const promise = wsbi.appendBuffer(buf, { codec: "video/mp4" });
454
+
455
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
456
+ expect(wsbi._pendingOperations.size).toBe(1);
457
+ wsbi.onOperationSuccess(operationId, [{ start: 0, end: 4 }]);
458
+ await expect(promise).resolves.toEqual([{ start: 0, end: 4 }]);
459
+ expect(wsbi._pendingOperations.size).toBe(0);
460
+ });
461
+
462
+ it("logs a warning when the operationId is unknown", () => {
463
+ const { wsbi } = makeWsbi();
464
+ wsbi.onOperationSuccess("unknown-op-id", []);
465
+ expect(mockLog.warn).toHaveBeenCalledWith(
466
+ "mse",
467
+ "unknown SourceBuffer operation succeeded",
468
+ { type: "video" },
469
+ );
470
+ });
471
+
472
+ it("dequeues and sends the next queued push operation after success", async () => {
473
+ const { wsbi, messageSender } = makeWsbi();
474
+ const buf1 = new Uint8Array([0, 1, 2, 3]).buffer;
475
+ const buf2 = new Uint8Array([9, 8, 7, 6]).buffer;
476
+
477
+ const promise1 = wsbi.appendBuffer(buf1, { codec: "video/mp4" });
478
+ const { operationId: firstOpId } = getLastCallFirstArg<{ operationId: string }>(
479
+ messageSender,
480
+ );
481
+
482
+ wsbi._queuedOperations.push({
483
+ operationName: 0 /* SbiOperationName.Push */,
484
+ params: [buf2, { codec: "video/mp4" }],
485
+ resolve: vi.fn(),
486
+ reject: vi.fn(),
487
+ });
488
+
489
+ messageSender.mockClear();
490
+ wsbi.onOperationSuccess(firstOpId, []);
491
+ await promise1;
492
+
493
+ expect(messageSender).toHaveBeenCalledOnce();
494
+ expect(messageSender).toHaveBeenCalledWith(
495
+ {
496
+ mediaSourceId: "ms-id-1",
497
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
498
+ operationId: expect.stringMatching(/\d+/),
499
+ sourceBufferType: SourceBufferType.Video,
500
+ type: CoreMessageType.SourceBufferAppend,
501
+ value: { data: buf2, params: { codec: "video/mp4" } },
502
+ },
503
+ [buf2],
504
+ );
505
+ });
506
+
507
+ it("dequeues and sends the next queued remove operation after success", async () => {
508
+ const { wsbi, messageSender } = makeWsbi();
509
+ const buf = new ArrayBuffer(4);
510
+
511
+ const promise1 = wsbi.appendBuffer(buf, { codec: "video/mp4" });
512
+ const { operationId: firstOpId } = getLastCallFirstArg<{ operationId: string }>(
513
+ messageSender,
514
+ );
515
+
516
+ wsbi._queuedOperations.push({
517
+ operationName: 1 /* SbiOperationName.Remove */,
518
+ params: [0, 10],
519
+ resolve: vi.fn(),
520
+ reject: vi.fn(),
521
+ });
522
+
523
+ messageSender.mockClear();
524
+ wsbi.onOperationSuccess(firstOpId, []);
525
+ await promise1;
526
+
527
+ expect(messageSender).toHaveBeenCalledOnce();
528
+ expect(messageSender).toHaveBeenCalledWith({
529
+ type: CoreMessageType.SourceBufferRemove,
530
+ mediaSourceId: "ms-id-1",
531
+ sourceBufferType: SourceBufferType.Video,
532
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
533
+ operationId: expect.stringMatching(/\d+/),
534
+ value: { start: 0, end: 10 },
535
+ });
536
+ });
537
+ });
538
+
539
+ describe("onOperationFailure", () => {
540
+ it("rejects the matching pending operation with a SourceBufferError", async () => {
541
+ const { wsbi, messageSender } = makeWsbi();
542
+ const buf = new ArrayBuffer(4);
543
+ const promise = wsbi.appendBuffer(buf, { codec: "video/mp4" });
544
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
545
+
546
+ wsbi.onOperationFailure(operationId, {
547
+ errorName: "SourceBufferError",
548
+ message: "Buffer full",
549
+ isBufferFull: true,
550
+ });
551
+
552
+ await expect(promise).rejects.toBeInstanceOf(SourceBufferError);
553
+ });
554
+
555
+ it("rejects the matching pending operation with a CancellationError", async () => {
556
+ const { wsbi, messageSender } = makeWsbi();
557
+ const buf = new ArrayBuffer(4);
558
+ const promise = wsbi.appendBuffer(buf, { codec: "video/mp4" });
559
+ const { operationId } = getLastCallFirstArg<{ operationId: string }>(messageSender);
560
+
561
+ wsbi.onOperationFailure(operationId, { errorName: "CancellationError" });
562
+
563
+ await expect(promise).rejects.toBeInstanceOf(CancellationError);
564
+ });
565
+
566
+ it("logs info when the operationId is unknown", () => {
567
+ const { wsbi } = makeWsbi();
568
+ wsbi.onOperationFailure("unknown-op-id", {
569
+ errorName: "SourceBufferError",
570
+ message: "Buffer full",
571
+ isBufferFull: true,
572
+ });
573
+ expect(mockLog.info).toHaveBeenCalledWith(
574
+ "mse",
575
+ "unknown SourceBuffer operation failed",
576
+ { type: "video" },
577
+ new SourceBufferError("SourceBufferError", "Buffer full", true),
578
+ );
579
+ });
580
+
581
+ it("clears the queue and rejects all queued operations on failure", async () => {
582
+ const { wsbi, messageSender } = makeWsbi();
583
+ const buf = new ArrayBuffer(4);
584
+ const promise1 = wsbi.appendBuffer(buf, { codec: "video/mp4" });
585
+ const { operationId: firstOpId } = getLastCallFirstArg<{ operationId: string }>(
586
+ messageSender,
587
+ );
588
+
589
+ const reject2 = vi.fn();
590
+ wsbi._queuedOperations.push({
591
+ operationName: 0 /* Push */,
592
+ params: [new ArrayBuffer(4), { codec: "video/mp4" }],
593
+ resolve: vi.fn(),
594
+ reject: reject2,
595
+ });
596
+
597
+ wsbi.onOperationFailure(firstOpId, {
598
+ errorName: "SourceBufferError",
599
+ message: "full",
600
+ isBufferFull: true,
601
+ });
602
+
603
+ await expect(promise1).rejects.toBeInstanceOf(SourceBufferError);
604
+ expect(reject2).toHaveBeenCalledOnce();
605
+ expect(wsbi._queuedOperations).toHaveLength(0);
606
+ });
607
+ });
608
+ });
@@ -1,5 +1,5 @@
1
1
  import type { IMediaSource, ISourceBuffer } from "../compat/browser_compatibility_types";
2
- import { MediaSource_ } from "../compat/browser_compatibility_types";
2
+ import BROWSER_GLOBALS from "../compat/browser_compatibility_types";
3
3
  import tryToChangeSourceBufferType from "../compat/change_source_buffer_type";
4
4
  import { onSourceClose, onSourceEnded, onSourceOpen } from "../compat/event_listeners";
5
5
  import { MediaError, SourceBufferError } from "../errors";
@@ -87,6 +87,7 @@ export default class MainMediaSourceInterface
87
87
  this.sourceBuffers = [];
88
88
  this._canceller = new TaskCanceller("MainMediaSourceInterface");
89
89
 
90
+ const { MediaSource_ } = BROWSER_GLOBALS;
90
91
  if (isNullOrUndefined(MediaSource_)) {
91
92
  throw new MediaError(
92
93
  "MEDIA_SOURCE_NOT_SUPPORTED",
@@ -472,8 +472,8 @@ type ISbiQueuedOperation = IQueuedSbiPush | IQueuedSbiRemove;
472
472
  * "operations".
473
473
  */
474
474
  const enum SbiOperationName {
475
- Push,
476
- Remove,
475
+ Push = 0,
476
+ Remove = 1,
477
477
  }
478
478
 
479
479
  interface IQueuedSbiPush {