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

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 (108) hide show
  1. package/CHANGELOG.md +9 -1
  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/entry/FreezeResolver.js +2 -2
  11. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  12. package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
  13. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  14. package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  15. package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
  16. package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
  17. package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
  18. package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
  19. package/dist/commonjs/default_config.d.ts +5 -0
  20. package/dist/commonjs/default_config.d.ts.map +1 -1
  21. package/dist/commonjs/default_config.js +5 -0
  22. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  23. package/dist/commonjs/main_thread/api/public_api.js +27 -3
  24. package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  25. package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
  26. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  27. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
  28. package/dist/commonjs/manifest/classes/index.d.ts +2 -1
  29. package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
  30. package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
  31. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  32. package/dist/commonjs/manifest/classes/representation.js +2 -2
  33. package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
  34. package/dist/commonjs/utils/base64.d.ts +15 -1
  35. package/dist/commonjs/utils/base64.d.ts.map +1 -1
  36. package/dist/commonjs/utils/base64.js +6 -0
  37. package/dist/commonjs/utils/ranges.d.ts +1 -1
  38. package/dist/commonjs/utils/ranges.js +1 -1
  39. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  40. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  41. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  42. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  43. package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
  44. package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
  45. package/dist/es2017/compat/should_seek_at_zero.js +20 -0
  46. package/dist/es2017/core/entry/FreezeResolver.js +1 -1
  47. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  48. package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
  49. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
  50. package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
  51. package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
  52. package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
  53. package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
  54. package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
  55. package/dist/es2017/default_config.d.ts +5 -0
  56. package/dist/es2017/default_config.d.ts.map +1 -1
  57. package/dist/es2017/default_config.js +5 -0
  58. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  59. package/dist/es2017/main_thread/api/public_api.js +27 -3
  60. package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  61. package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
  62. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  63. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
  64. package/dist/es2017/manifest/classes/index.d.ts +2 -1
  65. package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
  66. package/dist/es2017/manifest/classes/representation.d.ts +1 -1
  67. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  68. package/dist/es2017/manifest/classes/representation.js +1 -1
  69. package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
  70. package/dist/es2017/utils/base64.d.ts +15 -1
  71. package/dist/es2017/utils/base64.d.ts.map +1 -1
  72. package/dist/es2017/utils/base64.js +6 -0
  73. package/dist/es2017/utils/ranges.d.ts +1 -1
  74. package/dist/es2017/utils/ranges.js +1 -1
  75. package/dist/mpd-parser.wasm +0 -0
  76. package/dist/worker.js +6 -6
  77. package/package.json +9 -3
  78. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  79. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  80. package/src/compat/should_seek_at_zero.ts +23 -0
  81. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +790 -0
  82. package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
  83. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +285 -0
  84. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +247 -0
  85. package/src/core/entry/FreezeResolver.ts +1 -1
  86. package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
  87. package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +868 -0
  88. package/src/core/entry/content_time_boundaries_observer.ts +10 -0
  89. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +592 -0
  90. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
  91. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
  92. package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
  93. package/src/core/stream/representation/representation_stream.ts +11 -49
  94. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
  95. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
  96. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
  97. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
  98. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
  99. package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
  100. package/src/default_config.ts +6 -0
  101. package/src/main_thread/api/public_api.ts +32 -3
  102. package/src/main_thread/init/directfile_content_initializer.ts +26 -1
  103. package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
  104. package/src/manifest/classes/index.ts +2 -0
  105. package/src/manifest/classes/representation.ts +2 -1
  106. package/src/manifest/classes/representation_index/types.ts +2 -2
  107. package/src/utils/base64.ts +30 -1
  108. package/src/utils/ranges.ts +1 -1
@@ -0,0 +1,531 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import type { IRepresentationProtectionData } from "../../../../manifest/classes";
3
+ import type { IProtectionDataInfo } from "../../../../transports";
4
+ import EncryptionDataNotifier from "../encryption_data_notifier";
5
+
6
+ /* eslint-disable @typescript-eslint/no-explicit-any */
7
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
8
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
9
+
10
+ vi.mock("../../../../manifest/classes", () => ({
11
+ Representation: vi.fn(),
12
+ }));
13
+
14
+ describe("EncryptionDataNotifier", () => {
15
+ let mockRepresentation: any;
16
+ let mockNotify: any;
17
+
18
+ beforeEach(() => {
19
+ mockNotify = vi.fn();
20
+ mockRepresentation = {
21
+ getEncryptionData: vi.fn(() => []),
22
+ addProtectionData: vi.fn(),
23
+ getAllEncryptionData: vi.fn(() => []),
24
+ };
25
+ });
26
+
27
+ describe("constructor - early notification", () => {
28
+ it("should send notification immediately when drmSystemId is provided and encryption data is complete", () => {
29
+ const mockEncryptionData: IRepresentationProtectionData[] = [
30
+ {
31
+ keyIds: [new Uint8Array([5, 6, 7, 8]), new Uint8Array([8, 7, 6, 5])],
32
+ values: [
33
+ {
34
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
35
+ data: new Uint8Array([1, 2, 3]),
36
+ },
37
+ ],
38
+ type: "cenc",
39
+ },
40
+ ];
41
+
42
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
43
+
44
+ new EncryptionDataNotifier({
45
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
46
+ representation: mockRepresentation,
47
+ notify: mockNotify,
48
+ });
49
+
50
+ expect(mockRepresentation.getEncryptionData).toHaveBeenCalledWith(
51
+ "edef8ba979d64acea3c827dcd51d21ed",
52
+ );
53
+ expect(mockNotify).toHaveBeenCalledWith(mockEncryptionData);
54
+ expect(mockNotify).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ it("should NOT send notification when encryption data exists but keyIds are undefined", () => {
58
+ const mockEncryptionData: IRepresentationProtectionData[] = [
59
+ {
60
+ keyIds: undefined,
61
+ values: [
62
+ {
63
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
64
+ data: new Uint8Array([1, 2, 3]),
65
+ },
66
+ ],
67
+ type: "cenc",
68
+ },
69
+ ];
70
+
71
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
72
+
73
+ new EncryptionDataNotifier({
74
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
75
+ representation: mockRepresentation,
76
+ notify: mockNotify,
77
+ });
78
+
79
+ expect(mockRepresentation.getEncryptionData).toHaveBeenCalledWith(
80
+ "edef8ba979d64acea3c827dcd51d21ed",
81
+ );
82
+ expect(mockNotify).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it("should NOT send notification when some items have keyIds and others don't", () => {
86
+ const mockEncryptionData: IRepresentationProtectionData[] = [
87
+ {
88
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
89
+ values: [
90
+ {
91
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
92
+ data: new Uint8Array([1, 2, 3]),
93
+ },
94
+ ],
95
+ type: "cenc",
96
+ },
97
+ {
98
+ keyIds: undefined,
99
+ values: [
100
+ {
101
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
102
+ data: new Uint8Array([1, 2, 3]),
103
+ },
104
+ ],
105
+ type: "cenc",
106
+ },
107
+ ];
108
+
109
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
110
+
111
+ new EncryptionDataNotifier({
112
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
113
+ representation: mockRepresentation,
114
+ notify: mockNotify,
115
+ });
116
+
117
+ expect(mockNotify).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it("should NOT send notification when encryption data array is empty", () => {
121
+ new EncryptionDataNotifier({
122
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
123
+ representation: mockRepresentation,
124
+ notify: mockNotify,
125
+ });
126
+
127
+ expect(mockNotify).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it("should NOT send notification when drmSystemId is undefined", () => {
131
+ new EncryptionDataNotifier({
132
+ drmSystemId: undefined,
133
+ representation: mockRepresentation,
134
+ notify: mockNotify,
135
+ });
136
+
137
+ expect(mockRepresentation.getEncryptionData).not.toHaveBeenCalled();
138
+ expect(mockNotify).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it("should handle empty keyIds array as valid", () => {
142
+ const mockEncryptionData: IRepresentationProtectionData[] = [
143
+ {
144
+ keyIds: [],
145
+ values: [
146
+ {
147
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
148
+ data: new Uint8Array([1, 2, 3]),
149
+ },
150
+ ],
151
+ type: "cenc",
152
+ },
153
+ ];
154
+
155
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
156
+
157
+ new EncryptionDataNotifier({
158
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
159
+ representation: mockRepresentation,
160
+ notify: mockNotify,
161
+ });
162
+
163
+ expect(mockNotify).toHaveBeenCalledWith(mockEncryptionData);
164
+ });
165
+ });
166
+
167
+ describe("onNewProtectionData - deferred notification", () => {
168
+ it("should add protection data to representation and send notification", () => {
169
+ const notifier = new EncryptionDataNotifier({
170
+ drmSystemId: undefined,
171
+ representation: mockRepresentation,
172
+ notify: mockNotify,
173
+ });
174
+
175
+ const protectionData: IProtectionDataInfo[] = [
176
+ {
177
+ keyId: new Uint8Array([1, 2, 3]),
178
+ initDataType: "cenc",
179
+ initData: [
180
+ {
181
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
182
+ data: new Uint8Array([4, 5, 6]),
183
+ },
184
+ ],
185
+ },
186
+ ];
187
+
188
+ const allEncryptionData: IRepresentationProtectionData[] = [
189
+ {
190
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
191
+ values: [
192
+ {
193
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
194
+ data: new Uint8Array([4, 5, 6]),
195
+ },
196
+ ],
197
+ type: "cenc",
198
+ },
199
+ ];
200
+ mockRepresentation.getAllEncryptionData.mockReturnValue(allEncryptionData);
201
+
202
+ notifier.onNewProtectionData(protectionData);
203
+
204
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalledWith(
205
+ "cenc",
206
+ new Uint8Array([1, 2, 3]),
207
+ [
208
+ {
209
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
210
+ data: new Uint8Array([4, 5, 6]),
211
+ },
212
+ ],
213
+ );
214
+ expect(mockRepresentation.getAllEncryptionData).toHaveBeenCalled();
215
+ expect(mockNotify).toHaveBeenCalledWith(allEncryptionData);
216
+ });
217
+
218
+ it("should add multiple protection data items to representation", () => {
219
+ const notifier = new EncryptionDataNotifier({
220
+ drmSystemId: undefined,
221
+ representation: mockRepresentation,
222
+ notify: mockNotify,
223
+ });
224
+
225
+ const protectionData: IProtectionDataInfo[] = [
226
+ {
227
+ initDataType: "cenc",
228
+ keyId: new Uint8Array([1, 2, 3]),
229
+ initData: [
230
+ {
231
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
232
+ data: new Uint8Array([4, 5, 6]),
233
+ },
234
+ ],
235
+ },
236
+ {
237
+ initDataType: "cenc",
238
+ keyId: new Uint8Array([7, 8, 9]),
239
+ initData: [
240
+ {
241
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
242
+ data: new Uint8Array([10, 11, 12]),
243
+ },
244
+ ],
245
+ },
246
+ ];
247
+
248
+ const allEncryptionData: IRepresentationProtectionData[] = [
249
+ {
250
+ type: "cenc",
251
+ keyIds: [new Uint8Array([5, 6, 7, 8]), new Uint8Array([8, 7, 6, 5])],
252
+ values: [
253
+ {
254
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
255
+ data: new Uint8Array([4, 5, 6]),
256
+ },
257
+ ],
258
+ },
259
+ ];
260
+
261
+ mockRepresentation.getAllEncryptionData.mockReturnValue(allEncryptionData);
262
+
263
+ notifier.onNewProtectionData(protectionData);
264
+
265
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalledTimes(2);
266
+ expect(mockRepresentation.addProtectionData).toHaveBeenNthCalledWith(
267
+ 1,
268
+ "cenc",
269
+ new Uint8Array([1, 2, 3]),
270
+ [
271
+ {
272
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
273
+ data: new Uint8Array([4, 5, 6]),
274
+ },
275
+ ],
276
+ );
277
+ expect(mockRepresentation.addProtectionData).toHaveBeenNthCalledWith(
278
+ 2,
279
+ "cenc",
280
+ new Uint8Array([7, 8, 9]),
281
+ [
282
+ {
283
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
284
+ data: new Uint8Array([10, 11, 12]),
285
+ },
286
+ ],
287
+ );
288
+ expect(mockNotify).toHaveBeenCalledWith(allEncryptionData);
289
+ });
290
+
291
+ it("should NOT send notification if no encryption data is available after adding", () => {
292
+ const notifier = new EncryptionDataNotifier({
293
+ drmSystemId: undefined,
294
+ representation: mockRepresentation,
295
+ notify: mockNotify,
296
+ });
297
+
298
+ const protectionData: IProtectionDataInfo[] = [
299
+ {
300
+ initDataType: "cenc",
301
+ keyId: new Uint8Array([1, 2, 3]),
302
+ initData: [
303
+ {
304
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
305
+ data: new Uint8Array([4, 5, 6]),
306
+ },
307
+ ],
308
+ },
309
+ ];
310
+ notifier.onNewProtectionData(protectionData);
311
+
312
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalled();
313
+ expect(mockRepresentation.getAllEncryptionData).toHaveBeenCalled();
314
+ expect(mockNotify).not.toHaveBeenCalled();
315
+ });
316
+
317
+ it("should handle empty protection data array", () => {
318
+ const notifier = new EncryptionDataNotifier({
319
+ drmSystemId: undefined,
320
+ representation: mockRepresentation,
321
+ notify: mockNotify,
322
+ });
323
+ notifier.onNewProtectionData([]);
324
+ expect(mockRepresentation.addProtectionData).not.toHaveBeenCalled();
325
+ expect(mockRepresentation.getAllEncryptionData).toHaveBeenCalled();
326
+ expect(mockNotify).not.toHaveBeenCalled();
327
+ });
328
+ });
329
+
330
+ describe("notification sent only once", () => {
331
+ it("should NOT send notification again after early notification", () => {
332
+ const mockEncryptionData: IRepresentationProtectionData[] = [
333
+ {
334
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
335
+ values: [
336
+ {
337
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
338
+ data: new Uint8Array([1, 2, 3]),
339
+ },
340
+ ],
341
+ type: "cenc",
342
+ },
343
+ ];
344
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
345
+
346
+ const notifier = new EncryptionDataNotifier({
347
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
348
+ representation: mockRepresentation,
349
+ notify: mockNotify,
350
+ });
351
+
352
+ // Early notification should have been sent
353
+ expect(mockNotify).toHaveBeenCalledTimes(1);
354
+
355
+ // Now call onNewProtectionData
356
+ const protectionData: IProtectionDataInfo[] = [
357
+ {
358
+ initDataType: "cenc",
359
+ keyId: new Uint8Array([4, 5, 6]),
360
+ initData: [
361
+ {
362
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
363
+ data: new Uint8Array([7, 8, 9]),
364
+ },
365
+ ],
366
+ },
367
+ ];
368
+
369
+ mockRepresentation.getAllEncryptionData.mockReturnValue([
370
+ ...mockEncryptionData,
371
+ {
372
+ keyIds: [new Uint8Array([8, 7, 6, 5])],
373
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
374
+ initData: new Uint8Array([7, 8, 9]),
375
+ initDataType: "cenc",
376
+ },
377
+ ]);
378
+
379
+ notifier.onNewProtectionData(protectionData);
380
+
381
+ // Should still be called only once
382
+ expect(mockNotify).toHaveBeenCalledTimes(1);
383
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalled();
384
+ });
385
+
386
+ it("should NOT send notification again on subsequent onNewProtectionData calls", () => {
387
+ const notifier = new EncryptionDataNotifier({
388
+ drmSystemId: undefined,
389
+ representation: mockRepresentation,
390
+ notify: mockNotify,
391
+ });
392
+
393
+ const protectionData1: IProtectionDataInfo[] = [
394
+ {
395
+ initDataType: "cenc",
396
+ keyId: new Uint8Array([1, 2, 3]),
397
+ initData: [
398
+ {
399
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
400
+ data: new Uint8Array([4, 5, 6]),
401
+ },
402
+ ],
403
+ },
404
+ ];
405
+ const allEncryptionData: IRepresentationProtectionData[] = [
406
+ {
407
+ type: "cenc",
408
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
409
+ values: [
410
+ {
411
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
412
+ data: new Uint8Array([4, 5, 6]),
413
+ },
414
+ ],
415
+ },
416
+ ];
417
+ mockRepresentation.getAllEncryptionData.mockReturnValue(allEncryptionData);
418
+
419
+ // First call should trigger notification
420
+ notifier.onNewProtectionData(protectionData1);
421
+ expect(mockNotify).toHaveBeenCalledTimes(1);
422
+
423
+ // Second call should NOT trigger notification
424
+ const protectionData2: IProtectionDataInfo[] = [
425
+ {
426
+ initDataType: "cenc",
427
+ keyId: new Uint8Array([7, 8, 9]),
428
+ initData: [
429
+ {
430
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
431
+ data: new Uint8Array([10, 11, 12]),
432
+ },
433
+ ],
434
+ },
435
+ ];
436
+
437
+ notifier.onNewProtectionData(protectionData2);
438
+ expect(mockNotify).toHaveBeenCalledTimes(1);
439
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalledTimes(2);
440
+ });
441
+ });
442
+
443
+ describe("edge cases", () => {
444
+ it("should handle multiple encryption data items with all keyIds defined", () => {
445
+ const mockEncryptionData: IRepresentationProtectionData[] = [
446
+ {
447
+ type: "cenc",
448
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
449
+ values: [
450
+ {
451
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
452
+ data: new Uint8Array([1, 2, 3]),
453
+ },
454
+ ],
455
+ },
456
+ {
457
+ type: "cenc",
458
+ keyIds: [new Uint8Array([8, 7, 6, 5]), new Uint8Array([8, 8, 8, 8])],
459
+ values: [
460
+ {
461
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
462
+ data: new Uint8Array([4, 5, 6]),
463
+ },
464
+ ],
465
+ },
466
+ ];
467
+
468
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
469
+
470
+ new EncryptionDataNotifier({
471
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
472
+ representation: mockRepresentation,
473
+ notify: mockNotify,
474
+ });
475
+
476
+ expect(mockNotify).toHaveBeenCalledWith(mockEncryptionData);
477
+ });
478
+
479
+ it("should add protection data even when notification was already sent", () => {
480
+ const mockEncryptionData: IRepresentationProtectionData[] = [
481
+ {
482
+ type: "cenc",
483
+ keyIds: [new Uint8Array([5, 6, 7, 8])],
484
+ values: [
485
+ {
486
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
487
+ data: new Uint8Array([1, 2, 3]),
488
+ },
489
+ ],
490
+ },
491
+ ];
492
+
493
+ mockRepresentation.getEncryptionData.mockReturnValue(mockEncryptionData);
494
+
495
+ const notifier = new EncryptionDataNotifier({
496
+ drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
497
+ representation: mockRepresentation,
498
+ notify: mockNotify,
499
+ });
500
+
501
+ expect(mockNotify).toHaveBeenCalledTimes(1);
502
+
503
+ const protectionData: IProtectionDataInfo[] = [
504
+ {
505
+ initDataType: "cenc",
506
+ keyId: new Uint8Array([7, 8, 9]),
507
+ initData: [
508
+ {
509
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
510
+ data: new Uint8Array([10, 11, 12]),
511
+ },
512
+ ],
513
+ },
514
+ ];
515
+
516
+ notifier.onNewProtectionData(protectionData);
517
+
518
+ // Data should still be added even though notification won't be sent again
519
+ expect(mockRepresentation.addProtectionData).toHaveBeenCalledWith(
520
+ "cenc",
521
+ new Uint8Array([7, 8, 9]),
522
+ [
523
+ {
524
+ systemId: "edef8ba979d64acea3c827dcd51d21ed",
525
+ data: new Uint8Array([10, 11, 12]),
526
+ },
527
+ ],
528
+ );
529
+ });
530
+ });
531
+ });
@@ -0,0 +1,122 @@
1
+ import type {
2
+ Representation,
3
+ IRepresentationProtectionData,
4
+ } from "../../../manifest/classes";
5
+ import type { IProtectionDataInfo } from "../../../transports";
6
+
7
+ /**
8
+ * Manages the one-time notification of encryption/DRM data for a Representation.
9
+ *
10
+ * This class collects encryption data from parsed segments and ensures that
11
+ * the encryption notification callback is triggered exactly once, as soon as
12
+ * sufficient data is available.
13
+ *
14
+ * The notification may occur in two ways:
15
+ * 1. **Early notification** (constructor): If the DRM system ID is known and
16
+ * complete encryption data (with all key IDs) is already available, the
17
+ * notification is sent immediately to enable faster license negotiation.
18
+ * 2. **Deferred notification** (onNewProtectionData): If early notification
19
+ * doesn't occur, encryption data is collected from parsed segments and
20
+ * the notification is sent once any encryption data becomes available.
21
+ *
22
+ * @example
23
+ * const notifier = new EncryptionDataNotifier({
24
+ * drmSystemId: "edef8ba979d64acea3c827dcd51d21ed",
25
+ * representation,
26
+ * notify: (data) => callbacks.encryptionDataEncountered(data),
27
+ * });
28
+ *
29
+ * // Later, when a segment is parsed:
30
+ * notifier.onNewProtectionData(parsedSegmentEvent.protectionData);
31
+ */
32
+ export default class EncryptionDataNotifier {
33
+ /**
34
+ * If `true` encryption data has already been sent through this
35
+ * `EncryptionDataNotifier`.
36
+ *
37
+ * For now we only send encryption data once per notifier at most as it was
38
+ * historically the only encountered case (encryption data in initialization
39
+ * segment).
40
+ * TODO: remove rule?
41
+ */
42
+ private _hasSentEncryptionData: boolean;
43
+ /**
44
+ * `Representation` linked to that `EncryptionDataNotifier`. Might be mutated
45
+ * to add encryption data information.
46
+ */
47
+ private _representation: Representation;
48
+ /** Callback to call when encryption information is first encountered. */
49
+ private _notify: (contentProtections: IRepresentationProtectionData[]) => void;
50
+
51
+ /**
52
+ * Create a new `EncryptionDataNotifier`.
53
+ * @param {Object} param0
54
+ * @param {string|undefined} param0.drmSystemId - If known the ID in hex
55
+ * format of the used DRM system (playready, widevine etc.).
56
+ * @param {Object} param0.representation - Object describing the
57
+ * Representation (including protection-related information). New encryption
58
+ * data might be added to it by this class.
59
+ * @param {Function} param0.notify - Function that will be called once new
60
+ * protection data has been detected.
61
+ */
62
+ constructor({
63
+ drmSystemId,
64
+ representation,
65
+ notify,
66
+ }: {
67
+ drmSystemId: string | undefined;
68
+ representation: Representation;
69
+ notify: (contentProtections: IRepresentationProtectionData[]) => void;
70
+ }) {
71
+ this._hasSentEncryptionData = false;
72
+ this._representation = representation;
73
+ this._notify = notify;
74
+
75
+ // If the DRM system id is already known, and if we already have encryption data
76
+ // for it, we may not need to wait until the initialization segment is loaded to
77
+ // signal required protection data, thus performing License negotiations sooner
78
+ if (drmSystemId !== undefined) {
79
+ const encryptionData = representation.getEncryptionData(drmSystemId);
80
+
81
+ // If some key ids are not known yet, it may be safer to wait for this initialization
82
+ // segment to be loaded first
83
+ if (
84
+ encryptionData.length > 0 &&
85
+ encryptionData.every((e) => e.keyIds !== undefined)
86
+ ) {
87
+ this._hasSentEncryptionData = true;
88
+ notify(encryptionData);
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Signal that new protection data information is available.
95
+ * If necessary, that data will be added to the Representation and sent
96
+ * through the `notifiy` callback passed in constructor.
97
+ * @param {Array.<Object>} data - The new protection data encountered.
98
+ */
99
+ public onNewProtectionData(data: IProtectionDataInfo[]): void {
100
+ // Supplementary encryption information might have been parsed.
101
+ for (const protInfo of data) {
102
+ // TODO better handle use cases like key rotation by not always grouping
103
+ // every protection data together? To check.
104
+ this._representation.addProtectionData(
105
+ protInfo.initDataType,
106
+ protInfo.keyId,
107
+ protInfo.initData,
108
+ );
109
+ }
110
+
111
+ // Now that the initialization segment has been parsed - which may have
112
+ // included encryption information - take care of the encryption event
113
+ // if not already done.
114
+ if (!this._hasSentEncryptionData) {
115
+ const allEncryptionData = this._representation.getAllEncryptionData();
116
+ if (allEncryptionData.length > 0) {
117
+ this._notify(allEncryptionData);
118
+ this._hasSentEncryptionData = true;
119
+ }
120
+ }
121
+ }
122
+ }