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

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 (180) hide show
  1. package/CHANGELOG.md +21 -3
  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/should_seek_at_zero.d.ts +12 -0
  8. package/dist/commonjs/compat/should_seek_at_zero.d.ts.map +1 -0
  9. package/dist/commonjs/compat/should_seek_at_zero.js +23 -0
  10. package/dist/commonjs/core/cmcd/cmcd_data_builder.js +2 -2
  11. package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
  12. package/dist/commonjs/core/entry/content_preparer.d.ts +1 -6
  13. package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
  14. package/dist/commonjs/core/entry/content_preparer.js +2 -125
  15. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  16. package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
  17. package/dist/commonjs/core/entry/core_entry.d.ts +1 -1
  18. package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
  19. package/dist/commonjs/core/entry/core_entry.js +2 -7
  20. package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +1 -1
  21. package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -1
  22. package/dist/commonjs/core/entry/index.d.ts +1 -1
  23. package/dist/commonjs/core/entry/index.d.ts.map +1 -1
  24. package/dist/commonjs/core/entry/utils.d.ts +64 -0
  25. package/dist/commonjs/core/entry/utils.d.ts.map +1 -1
  26. package/dist/commonjs/core/entry/utils.js +165 -0
  27. package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts +5 -6
  28. package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  29. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
  30. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +34 -30
  31. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  32. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  33. package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
  34. package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
  35. package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
  36. package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
  37. package/dist/commonjs/default_config.d.ts +5 -0
  38. package/dist/commonjs/default_config.d.ts.map +1 -1
  39. package/dist/commonjs/default_config.js +5 -0
  40. package/dist/commonjs/experimental/features/worker/index.d.ts +2 -1
  41. package/dist/commonjs/experimental/features/worker/index.d.ts.map +1 -1
  42. package/dist/commonjs/experimental/features/worker/index.js +4 -3
  43. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  44. package/dist/commonjs/main_thread/api/public_api.js +28 -4
  45. package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  46. package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
  47. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  48. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
  49. package/dist/commonjs/manifest/classes/index.d.ts +2 -1
  50. package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
  51. package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
  52. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  53. package/dist/commonjs/manifest/classes/representation.js +2 -2
  54. package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
  55. package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
  56. package/dist/commonjs/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
  57. package/dist/commonjs/utils/base64.d.ts +15 -1
  58. package/dist/commonjs/utils/base64.d.ts.map +1 -1
  59. package/dist/commonjs/utils/base64.js +6 -0
  60. package/dist/commonjs/utils/ranges.d.ts +1 -1
  61. package/dist/commonjs/utils/ranges.js +1 -1
  62. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  63. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  64. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  65. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  66. package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
  67. package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
  68. package/dist/es2017/compat/should_seek_at_zero.js +20 -0
  69. package/dist/es2017/core/cmcd/cmcd_data_builder.js +2 -2
  70. package/dist/es2017/core/entry/FreezeResolver.js +1 -1
  71. package/dist/es2017/core/entry/content_preparer.d.ts +1 -6
  72. package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
  73. package/dist/es2017/core/entry/content_preparer.js +1 -76
  74. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  75. package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
  76. package/dist/es2017/core/entry/core_entry.d.ts +1 -1
  77. package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
  78. package/dist/es2017/core/entry/core_entry.js +2 -7
  79. package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +1 -1
  80. package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -1
  81. package/dist/es2017/core/entry/index.d.ts +1 -1
  82. package/dist/es2017/core/entry/index.d.ts.map +1 -1
  83. package/dist/es2017/core/entry/utils.d.ts +64 -0
  84. package/dist/es2017/core/entry/utils.d.ts.map +1 -1
  85. package/dist/es2017/core/entry/utils.js +98 -0
  86. package/dist/es2017/core/fetchers/segment/segment_queue.d.ts +5 -6
  87. package/dist/es2017/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  88. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.d.ts.map +1 -1
  89. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +21 -16
  90. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  91. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  92. package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
  93. package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
  94. package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
  95. package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
  96. package/dist/es2017/default_config.d.ts +5 -0
  97. package/dist/es2017/default_config.d.ts.map +1 -1
  98. package/dist/es2017/default_config.js +5 -0
  99. package/dist/es2017/experimental/features/worker/index.d.ts +2 -1
  100. package/dist/es2017/experimental/features/worker/index.d.ts.map +1 -1
  101. package/dist/es2017/experimental/features/worker/index.js +2 -3
  102. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  103. package/dist/es2017/main_thread/api/public_api.js +28 -4
  104. package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  105. package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
  106. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  107. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
  108. package/dist/es2017/manifest/classes/index.d.ts +2 -1
  109. package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
  110. package/dist/es2017/manifest/classes/representation.d.ts +1 -1
  111. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  112. package/dist/es2017/manifest/classes/representation.js +1 -1
  113. package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
  114. package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.d.ts.map +1 -1
  115. package/dist/es2017/parsers/texttracks/ttml/html/apply_line_height.js +16 -1
  116. package/dist/es2017/utils/base64.d.ts +15 -1
  117. package/dist/es2017/utils/base64.d.ts.map +1 -1
  118. package/dist/es2017/utils/base64.js +6 -0
  119. package/dist/es2017/utils/ranges.d.ts +1 -1
  120. package/dist/es2017/utils/ranges.js +1 -1
  121. package/dist/mpd-parser.wasm +0 -0
  122. package/dist/worker.js +8 -8
  123. package/package.json +26 -23
  124. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  125. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  126. package/src/compat/should_seek_at_zero.ts +23 -0
  127. package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +734 -0
  128. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +793 -0
  129. package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
  130. package/src/core/adaptive/utils/__tests__/bandwidth_estimator.test.ts +117 -0
  131. package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +76 -0
  132. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +288 -0
  133. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +249 -0
  134. package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +464 -0
  135. package/src/core/cmcd/cmcd_data_builder.ts +2 -2
  136. package/src/core/entry/FreezeResolver.ts +1 -1
  137. package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
  138. package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +867 -0
  139. package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +205 -0
  140. package/src/core/entry/__tests__/get_buffered_data_per_media_buffer.test.ts +95 -0
  141. package/src/core/entry/__tests__/track_choice_setter.test.ts +288 -0
  142. package/src/core/entry/__tests__/utils.test.ts +309 -0
  143. package/src/core/entry/content_preparer.ts +7 -118
  144. package/src/core/entry/content_time_boundaries_observer.ts +10 -0
  145. package/src/core/entry/core_entry.ts +7 -14
  146. package/src/core/entry/core_text_displayer_interface.ts +3 -1
  147. package/src/core/entry/index.ts +1 -1
  148. package/src/core/entry/utils.ts +151 -0
  149. package/src/core/fetchers/__tests__/cdn_prioritizer.test.ts +166 -0
  150. package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +803 -0
  151. package/src/core/fetchers/segment/__tests__/segment_fetcher.test.ts +872 -0
  152. package/src/core/fetchers/segment/__tests__/segment_queue.test.ts +557 -0
  153. package/src/core/fetchers/segment/segment_queue.ts +5 -6
  154. package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +435 -0
  155. package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +28 -20
  156. package/src/core/fetchers/utils/__tests__/schedule_request.test.ts +331 -0
  157. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +593 -0
  158. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
  159. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
  160. package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
  161. package/src/core/stream/representation/representation_stream.ts +11 -49
  162. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
  163. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
  164. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
  165. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
  166. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
  167. package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
  168. package/src/default_config.ts +6 -0
  169. package/src/experimental/features/worker/index.ts +2 -4
  170. package/src/main_thread/api/public_api.ts +33 -4
  171. package/src/main_thread/init/directfile_content_initializer.ts +26 -1
  172. package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
  173. package/src/manifest/classes/index.ts +2 -0
  174. package/src/manifest/classes/representation.ts +2 -1
  175. package/src/manifest/classes/representation_index/types.ts +2 -2
  176. package/src/mse/__tests__/main_media_source_interface.test.ts +846 -0
  177. package/src/parsers/texttracks/ttml/html/apply_line_height.ts +16 -1
  178. package/src/utils/__tests__/base64.test.ts +159 -191
  179. package/src/utils/base64.ts +30 -1
  180. package/src/utils/ranges.ts +1 -1
@@ -0,0 +1,209 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import pushInitSegment from "../push_init_segment";
3
+
4
+ /* eslint-disable @typescript-eslint/no-explicit-any */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
7
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
8
+
9
+ const mockAppendSegmentToBuffer = vi.hoisted(() => vi.fn());
10
+
11
+ vi.mock("../append_segment_to_buffer", () => ({
12
+ default: mockAppendSegmentToBuffer,
13
+ }));
14
+
15
+ describe("pushInitSegment", () => {
16
+ let mockPlaybackObserver: any;
17
+ let mockSegmentSink: any;
18
+ let mockBufferGoal: any;
19
+ let mockCancelSignal: any;
20
+ let mockContent: any;
21
+ let mockSegment: any;
22
+
23
+ beforeEach(() => {
24
+ vi.resetAllMocks();
25
+
26
+ mockPlaybackObserver = {};
27
+
28
+ mockSegmentSink = {};
29
+
30
+ mockBufferGoal = {
31
+ getValue: vi.fn().mockReturnValue(30),
32
+ };
33
+
34
+ mockCancelSignal = {
35
+ signal: { aborted: false },
36
+ cancellationError: null,
37
+ };
38
+
39
+ mockContent = {
40
+ adaptation: { id: "adaptation-1" },
41
+ manifest: { id: "manifest-1" },
42
+ period: { id: "period-1" },
43
+ representation: {
44
+ id: "rep-1",
45
+ getMimeTypeString: vi.fn().mockReturnValue('video/mp4; codecs="avc1.42E01E"'),
46
+ },
47
+ };
48
+
49
+ mockSegment = {
50
+ id: "init-segment",
51
+ isInit: true,
52
+ time: 0,
53
+ duration: 0,
54
+ };
55
+ });
56
+
57
+ it("should call appendSegmentToBuffer with correct data structure", async () => {
58
+ mockAppendSegmentToBuffer.mockResolvedValue({
59
+ start: 0,
60
+ end: 10,
61
+ });
62
+
63
+ const initSegmentUniqueId = "unique-init-123";
64
+
65
+ await pushInitSegment(
66
+ {
67
+ playbackObserver: mockPlaybackObserver,
68
+ content: mockContent,
69
+ initSegmentUniqueId,
70
+ segmentData: null,
71
+ segment: mockSegment,
72
+ segmentSink: mockSegmentSink,
73
+ bufferGoal: mockBufferGoal,
74
+ },
75
+ mockCancelSignal,
76
+ );
77
+
78
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledTimes(1);
79
+
80
+ const callArgs = mockAppendSegmentToBuffer.mock.calls[0];
81
+ expect(callArgs[0]).toBe(mockPlaybackObserver);
82
+ expect(callArgs[1]).toBe(mockSegmentSink);
83
+ expect(callArgs[3]).toBe(mockBufferGoal);
84
+ expect(callArgs[4]).toBe(mockCancelSignal);
85
+
86
+ const { data, inventoryInfos } = callArgs[2];
87
+ expect(data).toEqual({
88
+ initSegmentUniqueId,
89
+ chunk: null,
90
+ timestampOffset: 0,
91
+ appendWindow: [undefined, undefined],
92
+ codec: 'video/mp4; codecs="avc1.42E01E"',
93
+ });
94
+
95
+ expect(inventoryInfos).toMatchObject({
96
+ segment: mockSegment,
97
+ chunkSize: undefined,
98
+ start: 0,
99
+ end: 0,
100
+ adaptation: mockContent.adaptation,
101
+ manifest: mockContent.manifest,
102
+ period: mockContent.period,
103
+ representation: mockContent.representation,
104
+ });
105
+ });
106
+
107
+ it("should return correct payload with buffered result", async () => {
108
+ const mockBuffered = { start: 0, end: 10 };
109
+ mockAppendSegmentToBuffer.mockResolvedValue(mockBuffered);
110
+
111
+ const result = await pushInitSegment(
112
+ {
113
+ playbackObserver: mockPlaybackObserver,
114
+ content: mockContent,
115
+ initSegmentUniqueId: "init-456",
116
+ segmentData: null,
117
+ segment: mockSegment,
118
+ segmentSink: mockSegmentSink,
119
+ bufferGoal: mockBufferGoal,
120
+ },
121
+ mockCancelSignal,
122
+ );
123
+
124
+ expect(result).toEqual({
125
+ content: mockContent,
126
+ segment: mockSegment,
127
+ buffered: mockBuffered,
128
+ });
129
+ });
130
+
131
+ it("should call getMimeTypeString on representation", async () => {
132
+ mockAppendSegmentToBuffer.mockResolvedValue({ start: 0, end: 0 });
133
+
134
+ await pushInitSegment(
135
+ {
136
+ playbackObserver: mockPlaybackObserver,
137
+ content: mockContent,
138
+ initSegmentUniqueId: "init-789",
139
+ segmentData: null,
140
+ segment: mockSegment,
141
+ segmentSink: mockSegmentSink,
142
+ bufferGoal: mockBufferGoal,
143
+ },
144
+ mockCancelSignal,
145
+ );
146
+
147
+ expect(mockContent.representation.getMimeTypeString).toHaveBeenCalledTimes(1);
148
+ });
149
+
150
+ it("should set chunk to null for init segments", async () => {
151
+ mockAppendSegmentToBuffer.mockResolvedValue({});
152
+
153
+ await pushInitSegment(
154
+ {
155
+ playbackObserver: mockPlaybackObserver,
156
+ content: mockContent,
157
+ initSegmentUniqueId: "init-abc",
158
+ segmentData: { someData: "value" },
159
+ segment: mockSegment,
160
+ segmentSink: mockSegmentSink,
161
+ bufferGoal: mockBufferGoal,
162
+ },
163
+ mockCancelSignal,
164
+ );
165
+
166
+ const { data } = mockAppendSegmentToBuffer.mock.calls[0][2];
167
+ expect(data.chunk).toBeNull();
168
+ });
169
+
170
+ it("should set timestampOffset to 0", async () => {
171
+ mockAppendSegmentToBuffer.mockResolvedValue({});
172
+
173
+ await pushInitSegment(
174
+ {
175
+ playbackObserver: mockPlaybackObserver,
176
+ content: mockContent,
177
+ initSegmentUniqueId: "init-def",
178
+ segmentData: null,
179
+ segment: mockSegment,
180
+ segmentSink: mockSegmentSink,
181
+ bufferGoal: mockBufferGoal,
182
+ },
183
+ mockCancelSignal,
184
+ );
185
+
186
+ const { data } = mockAppendSegmentToBuffer.mock.calls[0][2];
187
+ expect(data.timestampOffset).toBe(0);
188
+ });
189
+
190
+ it("should set appendWindow to [undefined, undefined]", async () => {
191
+ mockAppendSegmentToBuffer.mockResolvedValue({});
192
+
193
+ await pushInitSegment(
194
+ {
195
+ playbackObserver: mockPlaybackObserver,
196
+ content: mockContent,
197
+ initSegmentUniqueId: "init-ghi",
198
+ segmentData: null,
199
+ segment: mockSegment,
200
+ segmentSink: mockSegmentSink,
201
+ bufferGoal: mockBufferGoal,
202
+ },
203
+ mockCancelSignal,
204
+ );
205
+
206
+ const { data } = mockAppendSegmentToBuffer.mock.calls[0][2];
207
+ expect(data.appendWindow).toEqual([undefined, undefined]);
208
+ });
209
+ });
@@ -0,0 +1,343 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import type { CancellationSignal } from "../../../../../utils/task_canceller";
3
+ import pushMediaSegment from "../push_media_segment";
4
+
5
+ /* eslint-disable @typescript-eslint/no-explicit-any */
6
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
7
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
8
+
9
+ // Mock dependencies with vi.hoisted
10
+ const mockGetCurrent = vi.hoisted(() => vi.fn());
11
+ const mockObjectAssign = vi.hoisted(() => vi.fn());
12
+ const mockAppendSegmentToBuffer = vi.hoisted(() => vi.fn());
13
+
14
+ vi.mock("../../../../../config", () => ({
15
+ default: {
16
+ getCurrent: mockGetCurrent,
17
+ },
18
+ }));
19
+
20
+ vi.mock("../../../../../utils/object_assign", () => ({
21
+ default: mockObjectAssign,
22
+ }));
23
+
24
+ vi.mock("../append_segment_to_buffer.ts", () => ({
25
+ default: mockAppendSegmentToBuffer,
26
+ }));
27
+
28
+ describe("pushMediaSegment", () => {
29
+ const mockPlaybackObserver = {} as any;
30
+ const mockBufferGoal = { getValue: () => 30 } as any;
31
+ const mockCancelSignal = {} as CancellationSignal;
32
+ const mockSegmentSink = {} as any;
33
+
34
+ const mockContent = {
35
+ adaptation: {} as any,
36
+ manifest: {} as any,
37
+ period: {} as any,
38
+ representation: {
39
+ getMimeTypeString: vi.fn(() => 'video/mp4; codecs="avc1.42E01E"'),
40
+ } as any,
41
+ };
42
+
43
+ const mockSegment = {
44
+ time: 10,
45
+ duration: 5,
46
+ } as any;
47
+
48
+ beforeEach(() => {
49
+ vi.resetAllMocks();
50
+ mockGetCurrent.mockReturnValue({
51
+ APPEND_WINDOW_SECURITIES: {
52
+ START: 0.1,
53
+ END: 0.2,
54
+ },
55
+ });
56
+ mockObjectAssign.mockImplementation((target, ...sources) =>
57
+ // eslint-disable-next-line no-restricted-properties
58
+ Object.assign(target, ...sources),
59
+ );
60
+ });
61
+
62
+ it("should return null when chunkData is null", async () => {
63
+ const parsedSegment: any = {
64
+ chunkData: null,
65
+ chunkInfos: null,
66
+ chunkOffset: 0,
67
+ chunkSize: 0,
68
+ appendWindow: [undefined, undefined] as [number | undefined, number | undefined],
69
+ };
70
+
71
+ const result = await pushMediaSegment(
72
+ {
73
+ playbackObserver: mockPlaybackObserver,
74
+ bufferGoal: mockBufferGoal,
75
+ content: mockContent,
76
+ initSegmentUniqueId: "init-123",
77
+ parsedSegment,
78
+ segment: mockSegment,
79
+ segmentSink: mockSegmentSink,
80
+ },
81
+ mockCancelSignal,
82
+ );
83
+
84
+ expect(result).toBeNull();
85
+ expect(mockAppendSegmentToBuffer).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it("should append segment with basic data when chunkData is present", async () => {
89
+ const mockChunkData = new Uint8Array([1, 2, 3]);
90
+ const parsedSegment: any = {
91
+ chunkData: mockChunkData,
92
+ chunkInfos: null,
93
+ chunkOffset: 2.5,
94
+ chunkSize: 1024,
95
+ appendWindow: [undefined, undefined] as [number | undefined, number | undefined],
96
+ };
97
+
98
+ const mockBuffered = [{ start: 10, end: 15 }];
99
+ mockAppendSegmentToBuffer.mockResolvedValue(mockBuffered);
100
+
101
+ const result = await pushMediaSegment(
102
+ {
103
+ playbackObserver: mockPlaybackObserver,
104
+ bufferGoal: mockBufferGoal,
105
+ content: mockContent,
106
+ initSegmentUniqueId: "init-123",
107
+ parsedSegment,
108
+ segment: mockSegment,
109
+ segmentSink: mockSegmentSink,
110
+ },
111
+ mockCancelSignal,
112
+ );
113
+
114
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
115
+ mockPlaybackObserver,
116
+ mockSegmentSink,
117
+ {
118
+ data: {
119
+ initSegmentUniqueId: "init-123",
120
+ chunk: mockChunkData,
121
+ timestampOffset: 2.5,
122
+ appendWindow: [undefined, undefined],
123
+ codec: 'video/mp4; codecs="avc1.42E01E"',
124
+ },
125
+ inventoryInfos: expect.objectContaining({
126
+ segment: mockSegment,
127
+ chunkSize: 1024,
128
+ start: 10,
129
+ end: 15,
130
+ }),
131
+ },
132
+ mockBufferGoal,
133
+ mockCancelSignal,
134
+ );
135
+
136
+ expect(result).toEqual({
137
+ content: mockContent,
138
+ segment: mockSegment,
139
+ buffered: mockBuffered,
140
+ });
141
+ });
142
+
143
+ it("should use chunkInfos for time calculations when available", async () => {
144
+ const mockChunkData = new Uint8Array([1, 2, 3]);
145
+ const parsedSegment: any = {
146
+ chunkData: mockChunkData,
147
+ chunkInfos: { time: 20, duration: 8 },
148
+ chunkOffset: 0,
149
+ chunkSize: 2048,
150
+ appendWindow: [undefined, undefined] as [number | undefined, number | undefined],
151
+ };
152
+
153
+ mockAppendSegmentToBuffer.mockResolvedValue([]);
154
+
155
+ await pushMediaSegment(
156
+ {
157
+ playbackObserver: mockPlaybackObserver,
158
+ bufferGoal: mockBufferGoal,
159
+ content: mockContent,
160
+ initSegmentUniqueId: null,
161
+ parsedSegment,
162
+ segment: mockSegment,
163
+ segmentSink: mockSegmentSink,
164
+ },
165
+ mockCancelSignal,
166
+ );
167
+
168
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
169
+ expect.anything(),
170
+ expect.anything(),
171
+ expect.objectContaining({
172
+ inventoryInfos: expect.objectContaining({
173
+ start: 20,
174
+ end: 28,
175
+ }),
176
+ }),
177
+ expect.anything(),
178
+ expect.anything(),
179
+ );
180
+ });
181
+
182
+ it("should apply append window securities to start window", async () => {
183
+ const mockChunkData = new Uint8Array([1, 2, 3]);
184
+ const parsedSegment: any = {
185
+ chunkData: mockChunkData,
186
+ chunkInfos: null,
187
+ chunkOffset: 0,
188
+ chunkSize: 1024,
189
+ appendWindow: [5, undefined] as [number | undefined, number | undefined],
190
+ };
191
+
192
+ mockAppendSegmentToBuffer.mockResolvedValue([]);
193
+
194
+ await pushMediaSegment(
195
+ {
196
+ playbackObserver: mockPlaybackObserver,
197
+ bufferGoal: mockBufferGoal,
198
+ content: mockContent,
199
+ initSegmentUniqueId: "init-123",
200
+ parsedSegment,
201
+ segment: mockSegment,
202
+ segmentSink: mockSegmentSink,
203
+ },
204
+ mockCancelSignal,
205
+ );
206
+
207
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
208
+ expect.anything(),
209
+ expect.anything(),
210
+ expect.objectContaining({
211
+ data: expect.objectContaining({
212
+ appendWindow: [4.9, undefined], // 5 - 0.1
213
+ }),
214
+ inventoryInfos: expect.objectContaining({
215
+ start: 10, // max(10, 4.9)
216
+ }),
217
+ }),
218
+ expect.anything(),
219
+ expect.anything(),
220
+ );
221
+ });
222
+
223
+ it("should apply append window securities to end window", async () => {
224
+ const mockChunkData = new Uint8Array([1, 2, 3]);
225
+ const parsedSegment: any = {
226
+ chunkData: mockChunkData,
227
+ chunkInfos: null,
228
+ chunkOffset: 0,
229
+ chunkSize: 1024,
230
+ appendWindow: [undefined, 20] as [number | undefined, number | undefined],
231
+ };
232
+
233
+ mockAppendSegmentToBuffer.mockResolvedValue([]);
234
+
235
+ await pushMediaSegment(
236
+ {
237
+ playbackObserver: mockPlaybackObserver,
238
+ bufferGoal: mockBufferGoal,
239
+ content: mockContent,
240
+ initSegmentUniqueId: "init-123",
241
+ parsedSegment,
242
+ segment: mockSegment,
243
+ segmentSink: mockSegmentSink,
244
+ },
245
+ mockCancelSignal,
246
+ );
247
+
248
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
249
+ expect.anything(),
250
+ expect.anything(),
251
+ expect.objectContaining({
252
+ data: expect.objectContaining({
253
+ appendWindow: [undefined, 20.2], // 20 + 0.2
254
+ }),
255
+ inventoryInfos: expect.objectContaining({
256
+ end: 15, // min(15, 20.2)
257
+ }),
258
+ }),
259
+ expect.anything(),
260
+ expect.anything(),
261
+ );
262
+ });
263
+
264
+ it("should apply both start and end append windows", async () => {
265
+ const mockChunkData = new Uint8Array([1, 2, 3]);
266
+ const parsedSegment: any = {
267
+ chunkData: mockChunkData,
268
+ chunkInfos: { time: 8, duration: 4 },
269
+ chunkOffset: 0,
270
+ chunkSize: 1024,
271
+ appendWindow: [9, 11] as [number | undefined, number | undefined],
272
+ };
273
+
274
+ mockAppendSegmentToBuffer.mockResolvedValue([]);
275
+
276
+ await pushMediaSegment(
277
+ {
278
+ playbackObserver: mockPlaybackObserver,
279
+ bufferGoal: mockBufferGoal,
280
+ content: mockContent,
281
+ initSegmentUniqueId: "init-123",
282
+ parsedSegment,
283
+ segment: mockSegment,
284
+ segmentSink: mockSegmentSink,
285
+ },
286
+ mockCancelSignal,
287
+ );
288
+
289
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
290
+ expect.anything(),
291
+ expect.anything(),
292
+ expect.objectContaining({
293
+ data: expect.objectContaining({
294
+ appendWindow: [8.9, 11.2], // [9 - 0.1, 11 + 0.2]
295
+ }),
296
+ inventoryInfos: expect.objectContaining({
297
+ start: 8.9, // max(8, 8.9)
298
+ end: 11.2, // min(12, 11.2)
299
+ }),
300
+ }),
301
+ expect.anything(),
302
+ expect.anything(),
303
+ );
304
+ });
305
+
306
+ it("should not go below 0 for start append window", async () => {
307
+ const mockChunkData = new Uint8Array([1, 2, 3]);
308
+ const parsedSegment: any = {
309
+ chunkData: mockChunkData,
310
+ chunkInfos: null,
311
+ chunkOffset: 0,
312
+ chunkSize: 1024,
313
+ appendWindow: [0.05, undefined] as [number | undefined, number | undefined],
314
+ };
315
+
316
+ mockAppendSegmentToBuffer.mockResolvedValue([]);
317
+
318
+ await pushMediaSegment(
319
+ {
320
+ playbackObserver: mockPlaybackObserver,
321
+ bufferGoal: mockBufferGoal,
322
+ content: mockContent,
323
+ initSegmentUniqueId: "init-123",
324
+ parsedSegment,
325
+ segment: mockSegment,
326
+ segmentSink: mockSegmentSink,
327
+ },
328
+ mockCancelSignal,
329
+ );
330
+
331
+ expect(mockAppendSegmentToBuffer).toHaveBeenCalledWith(
332
+ expect.anything(),
333
+ expect.anything(),
334
+ expect.objectContaining({
335
+ data: expect.objectContaining({
336
+ appendWindow: [0, undefined], // max(0, 0.05 - 0.1)
337
+ }),
338
+ }),
339
+ expect.anything(),
340
+ expect.anything(),
341
+ );
342
+ });
343
+ });
@@ -326,7 +326,7 @@ function getAvailableBufferSize(
326
326
  /**
327
327
  * From the given array of buffered chunks (`bufferedSegments`) returns the last
328
328
  * buffered chunk contiguous with the one at the `startIndex` index given.
329
- * @param {Array.<Object>}
329
+ * @param {Array.<Object>} bufferedSegments
330
330
  * @param {number} startIndex
331
331
  * @returns {Object}
332
332
  */
@@ -1333,6 +1333,12 @@ const DEFAULT_CONFIG = {
1333
1333
  * announcing the content as loaded.
1334
1334
  */
1335
1335
  FORCE_WAIT_FOR_HAVE_ENOUGH_DATA: false,
1336
+
1337
+ /**
1338
+ * If `true`, forces an initial seek to 0. Mainly needed for Safari HLS,
1339
+ * where the player may start at the live position instead of 0.
1340
+ */
1341
+ FORCE_INITIAL_SEEK_TO_ZERO: false,
1336
1342
  };
1337
1343
 
1338
1344
  export type IDefaultConfig = typeof DEFAULT_CONFIG;
@@ -1,5 +1,3 @@
1
- export { DASH, DASH_WASM } from "../../../features/list";
1
+ export { DASH, DASH_WASM, SMOOTH } from "../../../features/list";
2
2
  export { LOCAL_MANIFEST } from "../local";
3
-
4
- // TODO: `SMOOTH` (has to be made worker-compatible)
5
- // TODO: `METAPLAYLIST` (once every transport is ported)
3
+ export { METAPLAYLIST } from "../metaplaylist";
@@ -437,7 +437,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
437
437
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
438
438
  videoElement.preload = "auto";
439
439
 
440
- this.version = /* PLAYER_VERSION */ "4.5.0-dev.2026012302";
440
+ this.version = /* PLAYER_VERSION */ "4.5.0-dev.2026031700";
441
441
  this.log = log;
442
442
  this.state = "STOPPED";
443
443
  this.videoElement = videoElement;
@@ -2772,6 +2772,13 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2772
2772
  }
2773
2773
 
2774
2774
  if (this._priv_contentInfos.isDirectFile) {
2775
+ if (this.videoElement === null) {
2776
+ log.error("API", "getMinimumPosition() called on a disposed player");
2777
+ return 0;
2778
+ }
2779
+ if (this.videoElement.seekable.length > 0) {
2780
+ return this.videoElement.seekable.start(0);
2781
+ }
2775
2782
  return 0;
2776
2783
  }
2777
2784
 
@@ -2818,8 +2825,14 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2818
2825
 
2819
2826
  if (isDirectFile) {
2820
2827
  if (this.videoElement === null) {
2821
- throw new Error("Disposed player");
2828
+ log.error("API", "getMaximumPosition() called on a disposed player");
2829
+ return null;
2822
2830
  }
2831
+
2832
+ if (this.videoElement.seekable.length > 0) {
2833
+ return this.videoElement.seekable.end(this.videoElement.seekable.length - 1);
2834
+ }
2835
+ // if for some reason seekable has no entry, fallback on duration
2823
2836
  return this.videoElement.duration;
2824
2837
  }
2825
2838
 
@@ -3526,6 +3539,22 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3526
3539
  if (startDate !== undefined) {
3527
3540
  positionData.wallClockTime = startDate + observation.position.getPolled();
3528
3541
  }
3542
+
3543
+ let directFileMaximumPosition;
3544
+ if (this.videoElement.seekable.length > 0) {
3545
+ directFileMaximumPosition = this.videoElement.seekable.end(
3546
+ this.videoElement.seekable.length - 1,
3547
+ );
3548
+ }
3549
+
3550
+ if (directFileMaximumPosition !== undefined && !isNaN(directFileMaximumPosition)) {
3551
+ positionData.maximumPosition = directFileMaximumPosition;
3552
+ // infinity duration means the content is live
3553
+ if (this.videoElement.duration === Infinity) {
3554
+ positionData.liveGap =
3555
+ directFileMaximumPosition - this.videoElement.currentTime;
3556
+ }
3557
+ }
3529
3558
  }
3530
3559
  this.trigger("positionUpdate", positionData);
3531
3560
  }
@@ -3766,7 +3795,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3766
3795
  // TODO: Make it work with multithread?
3767
3796
  return false;
3768
3797
  }
3769
- if (options.transport !== "dash" && options.transport !== "local") {
3798
+ if (options.transport === "directfile") {
3770
3799
  return false;
3771
3800
  }
3772
3801
  if (
@@ -3802,7 +3831,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3802
3831
  return true;
3803
3832
  }
3804
3833
  }
3805
- Player.version = /* PLAYER_VERSION */ "4.5.0-dev.2026012302";
3834
+ Player.version = /* PLAYER_VERSION */ "4.5.0-dev.2026031700";
3806
3835
 
3807
3836
  /** Every events sent by the RxPlayer's public API. */
3808
3837
  interface IPublicAPIEvent {
@@ -21,6 +21,7 @@
21
21
 
22
22
  import type { IMediaElement } from "../../compat/browser_compatibility_types";
23
23
  import clearElementSrc from "../../compat/clear_element_src";
24
+ import getStartDate from "../../compat/get_start_date";
24
25
  import type { MediaError } from "../../errors";
25
26
  import log from "../../log";
26
27
  import type { IMediaElementPlaybackObserver } from "../../playback_observer";
@@ -307,7 +308,31 @@ function getDirectFileInitialTime(
307
308
  if (!isNullOrUndefined(startAt.position)) {
308
309
  return startAt.position;
309
310
  } else if (!isNullOrUndefined(startAt.wallClockTime)) {
310
- return startAt.wallClockTime;
311
+ /**
312
+ * Special case for Safari with HLS in directfile mode:
313
+ *
314
+ * Safari's native HLS implementation uses `#EXT-X-PROGRAM-DATE-TIME` from the manifest
315
+ * as the time reference. This means `mediaElement.currentTime` represents time relative
316
+ * to the playlist's start date.
317
+ *
318
+ * When seeking to a specific `wallClockTime`, we calculate the position by subtracting
319
+ * the playlist's start date from `startPosition.wallClockTime`:
320
+ * seekPosition = wallClockTime - startDate
321
+ *
322
+ * The startDate is retrieved via `mediaElement.getStartDate()`, which only returns
323
+ * a valid date once `mediaElement.readyState >= HAVE_CURRENT_DATA` (≥ 2).
324
+ * If unavailable, we return undefined.
325
+ */
326
+ const startDate = getStartDate(mediaElement);
327
+ if (startDate === undefined) {
328
+ log.warn(
329
+ "Init",
330
+ "startAt.wallClockTime set but startDate is not known, either it's too soon to seek" +
331
+ " or the browser has no API to get the startDate",
332
+ );
333
+ return undefined;
334
+ }
335
+ return startAt.wallClockTime - startDate;
311
336
  } else if (!isNullOrUndefined(startAt.fromFirstPosition)) {
312
337
  return startAt.fromFirstPosition;
313
338
  }