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.
- package/CHANGELOG.md +9 -1
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/commonjs/compat/should_seek_at_zero.d.ts +12 -0
- package/dist/commonjs/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/commonjs/compat/should_seek_at_zero.js +23 -0
- package/dist/commonjs/core/entry/FreezeResolver.js +2 -2
- package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/commonjs/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/commonjs/core/stream/representation/encryption_data_notifier.js +107 -0
- package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/representation_stream.js +12 -60
- package/dist/commonjs/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/commonjs/default_config.d.ts +5 -0
- package/dist/commonjs/default_config.d.ts.map +1 -1
- package/dist/commonjs/default_config.js +5 -0
- package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +27 -3
- package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/commonjs/manifest/classes/index.d.ts +2 -1
- package/dist/commonjs/manifest/classes/index.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts +1 -1
- package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
- package/dist/commonjs/manifest/classes/representation.js +2 -2
- package/dist/commonjs/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/commonjs/utils/base64.d.ts +15 -1
- package/dist/commonjs/utils/base64.d.ts.map +1 -1
- package/dist/commonjs/utils/base64.js +6 -0
- package/dist/commonjs/utils/ranges.d.ts +1 -1
- package/dist/commonjs/utils/ranges.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/es2017/compat/should_seek_at_zero.d.ts +12 -0
- package/dist/es2017/compat/should_seek_at_zero.d.ts.map +1 -0
- package/dist/es2017/compat/should_seek_at_zero.js +20 -0
- package/dist/es2017/core/entry/FreezeResolver.js +1 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
- package/dist/es2017/core/entry/content_time_boundaries_observer.js +8 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts +70 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.d.ts.map +1 -0
- package/dist/es2017/core/stream/representation/encryption_data_notifier.js +80 -0
- package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/representation_stream.js +10 -38
- package/dist/es2017/core/stream/representation/utils/get_needed_segments.js +1 -1
- package/dist/es2017/default_config.d.ts +5 -0
- package/dist/es2017/default_config.d.ts.map +1 -1
- package/dist/es2017/default_config.js +5 -0
- package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +27 -3
- package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/directfile_content_initializer.js +23 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -1
- package/dist/es2017/manifest/classes/index.d.ts +2 -1
- package/dist/es2017/manifest/classes/index.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts +1 -1
- package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
- package/dist/es2017/manifest/classes/representation.js +1 -1
- package/dist/es2017/manifest/classes/representation_index/types.d.ts +2 -2
- package/dist/es2017/utils/base64.d.ts +15 -1
- package/dist/es2017/utils/base64.d.ts.map +1 -1
- package/dist/es2017/utils/base64.js +6 -0
- package/dist/es2017/utils/ranges.d.ts +1 -1
- package/dist/es2017/utils/ranges.js +1 -1
- package/dist/mpd-parser.wasm +0 -0
- package/dist/worker.js +6 -6
- package/package.json +9 -3
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/should_seek_at_zero.ts +23 -0
- package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +790 -0
- package/src/core/adaptive/__tests__/network_analyzer.test.ts +514 -0
- package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +285 -0
- package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +247 -0
- package/src/core/entry/FreezeResolver.ts +1 -1
- package/src/core/entry/__tests__/FreezeResolver.test.ts +542 -0
- package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +868 -0
- package/src/core/entry/content_time_boundaries_observer.ts +10 -0
- package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +592 -0
- package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +646 -0
- package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +531 -0
- package/src/core/stream/representation/encryption_data_notifier.ts +122 -0
- package/src/core/stream/representation/representation_stream.ts +11 -49
- package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +321 -0
- package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +513 -0
- package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +132 -0
- package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +209 -0
- package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +343 -0
- package/src/core/stream/representation/utils/get_needed_segments.ts +1 -1
- package/src/default_config.ts +6 -0
- package/src/main_thread/api/public_api.ts +32 -3
- package/src/main_thread/init/directfile_content_initializer.ts +26 -1
- package/src/main_thread/init/utils/initial_seek_and_play.ts +5 -1
- package/src/manifest/classes/index.ts +2 -0
- package/src/manifest/classes/representation.ts +2 -1
- package/src/manifest/classes/representation_index/types.ts +2 -2
- package/src/utils/base64.ts +30 -1
- 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
|
+
}
|