1 | import QUnit from 'qunit';
|
2 | import {mediaSegmentRequest, REQUEST_ERRORS} from '../src/media-segment-request';
|
3 | import xhrFactory from '../src/xhr';
|
4 | import {useFakeEnvironment} from './test-helpers';
|
5 | import Decrypter from '../src/decrypter-worker';
|
6 | import worker from 'webworkify';
|
7 |
|
8 | QUnit.module('Media Segment Request', {
|
9 | beforeEach(assert) {
|
10 | this.env = useFakeEnvironment(assert);
|
11 | this.clock = this.env.clock;
|
12 | this.requests = this.env.requests;
|
13 | this.xhr = xhrFactory();
|
14 | this.realDecrypter = worker(Decrypter);
|
15 | this.mockDecrypter = {
|
16 | listeners: [],
|
17 | postMessage(message) {
|
18 | const newMessage = Object.create(message);
|
19 |
|
20 | newMessage.decrypted = message.encrypted;
|
21 | this.listeners.forEach((fn)=>fn({
|
22 | data: newMessage
|
23 | }));
|
24 | },
|
25 | addEventListener(event, listener) {
|
26 | this.listeners.push(listener);
|
27 | },
|
28 | removeEventListener(event, listener) {
|
29 | this.listeners = this.listeners.filter((fn)=>fn !== listener);
|
30 | }
|
31 | };
|
32 | this.xhrOptions = {
|
33 | timeout: 1000
|
34 | };
|
35 | this.noop = () => {};
|
36 | },
|
37 | afterEach(assert) {
|
38 | this.realDecrypter.terminate();
|
39 | this.env.restore();
|
40 | }
|
41 | });
|
42 |
|
43 | QUnit.test('cancels outstanding segment request on abort', function(assert) {
|
44 | const done = assert.async();
|
45 |
|
46 | assert.expect(7);
|
47 |
|
48 | const abort = mediaSegmentRequest(
|
49 | this.xhr,
|
50 | this.xhrOptions,
|
51 | this.noop,
|
52 | { resolvedUri: '0-test.ts' },
|
53 | this.noop,
|
54 | (error, segmentData) => {
|
55 | assert.equal(this.requests.length, 1, 'there is only one request');
|
56 | assert.equal(this.requests[0].uri, '0-test.ts', 'the request is for a segment');
|
57 | assert.ok(this.requests[0].aborted, 'aborted the first request');
|
58 | assert.ok(error, 'an error object was generated');
|
59 | assert.equal(error.code, REQUEST_ERRORS.ABORTED, 'request was aborted');
|
60 |
|
61 | done();
|
62 | });
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | this.requests[0].response = new ArrayBuffer();
|
68 |
|
69 | abort();
|
70 | });
|
71 |
|
72 | QUnit.test('cancels outstanding key requests on abort', function(assert) {
|
73 | let keyReq;
|
74 | const done = assert.async();
|
75 |
|
76 | assert.expect(7);
|
77 |
|
78 | const abort = mediaSegmentRequest(
|
79 | this.xhr,
|
80 | this.xhrOptions,
|
81 | this.noop,
|
82 | {
|
83 | resolvedUri: '0-test.ts',
|
84 | key: {
|
85 | resolvedUri: '0-key.php'
|
86 | }
|
87 | },
|
88 | this.noop,
|
89 | (error, segmentData) => {
|
90 | assert.ok(keyReq.aborted, 'aborted the key request');
|
91 | assert.equal(error.code, REQUEST_ERRORS.ABORTED, 'key request was aborted');
|
92 |
|
93 | done();
|
94 | });
|
95 |
|
96 | assert.equal(this.requests.length, 2, 'there are two requests');
|
97 |
|
98 | keyReq = this.requests.shift();
|
99 | const segmentReq = this.requests.shift();
|
100 |
|
101 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
102 | assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
|
103 |
|
104 |
|
105 | segmentReq.response = new Uint8Array(10).buffer;
|
106 | segmentReq.respond(200, null, '');
|
107 |
|
108 | abort();
|
109 | });
|
110 |
|
111 | QUnit.test('cancels outstanding key requests on failure', function(assert) {
|
112 | let keyReq;
|
113 | const done = assert.async();
|
114 |
|
115 | assert.expect(7);
|
116 | mediaSegmentRequest(
|
117 | this.xhr,
|
118 | this.xhrOptions,
|
119 | this.noop,
|
120 | {
|
121 | resolvedUri: '0-test.ts',
|
122 | key: {
|
123 | resolvedUri: '0-key.php'
|
124 | }
|
125 | },
|
126 | this.noop,
|
127 | (error, segmentData) => {
|
128 | assert.ok(keyReq.aborted, 'aborted the key request');
|
129 | assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'segment request failed');
|
130 |
|
131 | done();
|
132 | });
|
133 |
|
134 | assert.equal(this.requests.length, 2, 'there are two requests');
|
135 |
|
136 | keyReq = this.requests.shift();
|
137 | const segmentReq = this.requests.shift();
|
138 |
|
139 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
140 | assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
|
141 |
|
142 |
|
143 | segmentReq.respond(500, null, '');
|
144 | });
|
145 |
|
146 | QUnit.test('cancels outstanding key requests on timeout', function(assert) {
|
147 | let keyReq;
|
148 | const done = assert.async();
|
149 |
|
150 | assert.expect(7);
|
151 | mediaSegmentRequest(
|
152 | this.xhr,
|
153 | this.xhrOptions,
|
154 | this.noop,
|
155 | {
|
156 | resolvedUri: '0-test.ts',
|
157 | key: {
|
158 | resolvedUri: '0-key.php'
|
159 | }
|
160 | },
|
161 | this.noop,
|
162 | (error, segmentData) => {
|
163 | assert.ok(keyReq.aborted, 'aborted the key request');
|
164 | assert.equal(error.code, REQUEST_ERRORS.TIMEOUT, 'key request failed');
|
165 |
|
166 | done();
|
167 | });
|
168 | assert.equal(this.requests.length, 2, 'there are two requests');
|
169 |
|
170 | keyReq = this.requests.shift();
|
171 | const segmentReq = this.requests.shift();
|
172 |
|
173 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
174 | assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
|
175 |
|
176 |
|
177 | this.clock.tick(2000);
|
178 | });
|
179 |
|
180 | QUnit.test('the key response is converted to the correct format', function(assert) {
|
181 | let keyReq;
|
182 | const done = assert.async();
|
183 | const postMessage = this.mockDecrypter.postMessage;
|
184 |
|
185 | assert.expect(9);
|
186 | this.mockDecrypter.postMessage = (message) => {
|
187 | const key = new Uint32Array(message.key.bytes,
|
188 | message.key.byteOffset,
|
189 | message.key.byteLength / 4);
|
190 |
|
191 | assert.deepEqual(key,
|
192 | new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]),
|
193 | 'passed the specified segment key');
|
194 | postMessage.call(this.mockDecrypter, message);
|
195 | };
|
196 |
|
197 | mediaSegmentRequest(
|
198 | this.xhr,
|
199 | this.xhrOptions,
|
200 | this.mockDecrypter,
|
201 | {
|
202 | resolvedUri: '0-test.ts',
|
203 | key: {
|
204 | resolvedUri: '0-key.php',
|
205 | IV: [0, 0, 0, 1]
|
206 | }
|
207 | },
|
208 | this.noop,
|
209 | (error, segmentData) => {
|
210 | assert.notOk(error, 'there are no errors');
|
211 | assert.equal(this.mockDecrypter.listeners.length,
|
212 | 0,
|
213 | 'all decryption webworker listeners are unbound');
|
214 |
|
215 | assert.equal(segmentData.stats.bytesReceived, 10, '10 bytes');
|
216 | done();
|
217 | });
|
218 |
|
219 | assert.equal(this.requests.length, 2, 'there are two requests');
|
220 |
|
221 | keyReq = this.requests.shift();
|
222 | const segmentReq = this.requests.shift();
|
223 |
|
224 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
225 | assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
|
226 |
|
227 | segmentReq.response = new Uint8Array(10).buffer;
|
228 | segmentReq.respond(200, null, '');
|
229 | keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
|
230 | keyReq.respond(200, null, '');
|
231 | });
|
232 |
|
233 | QUnit.test('segment with key has bytes decrypted', function(assert) {
|
234 | const done = assert.async();
|
235 |
|
236 | assert.expect(8);
|
237 | mediaSegmentRequest(
|
238 | this.xhr,
|
239 | this.xhrOptions,
|
240 | this.realDecrypter,
|
241 | {
|
242 | resolvedUri: '0-test.ts',
|
243 | key: {
|
244 | resolvedUri: '0-key.php',
|
245 | iv: {
|
246 | bytes: new Uint32Array([0, 0, 0, 1])
|
247 | }
|
248 | }
|
249 | },
|
250 | this.noop,
|
251 | (error, segmentData) => {
|
252 | assert.notOk(error, 'there are no errors');
|
253 | assert.ok(segmentData.bytes, 'decrypted bytes in segment');
|
254 |
|
255 |
|
256 | assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes');
|
257 | done();
|
258 | });
|
259 |
|
260 | assert.equal(this.requests.length, 2, 'there are two requests');
|
261 |
|
262 | const keyReq = this.requests.shift();
|
263 | const segmentReq = this.requests.shift();
|
264 |
|
265 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
266 | assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
|
267 |
|
268 | segmentReq.response = new Uint8Array(8).buffer;
|
269 | segmentReq.respond(200, null, '');
|
270 | keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
|
271 | keyReq.respond(200, null, '');
|
272 |
|
273 |
|
274 | this.clock.tick(100);
|
275 | });
|
276 |
|
277 | QUnit.test('waits for every request to finish before the callback is run',
|
278 | function(assert) {
|
279 | const done = assert.async();
|
280 |
|
281 | assert.expect(10);
|
282 | mediaSegmentRequest(
|
283 | this.xhr,
|
284 | this.xhrOptions,
|
285 | this.realDecrypter,
|
286 | {
|
287 | resolvedUri: '0-test.ts',
|
288 | key: {
|
289 | resolvedUri: '0-key.php',
|
290 | iv: {
|
291 | bytes: new Uint32Array([0, 0, 0, 1])
|
292 | }
|
293 | },
|
294 | map: {
|
295 | resolvedUri: '0-init.dat'
|
296 | }
|
297 | },
|
298 | this.noop,
|
299 | (error, segmentData) => {
|
300 | assert.notOk(error, 'there are no errors');
|
301 | assert.ok(segmentData.bytes, 'decrypted bytes in segment');
|
302 | assert.ok(segmentData.map.bytes, 'init segment bytes in map');
|
303 |
|
304 |
|
305 | assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes');
|
306 | done();
|
307 | });
|
308 |
|
309 | assert.equal(this.requests.length, 3, 'there are three requests');
|
310 |
|
311 | const keyReq = this.requests.shift();
|
312 | const initReq = this.requests.shift();
|
313 | const segmentReq = this.requests.shift();
|
314 |
|
315 | assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
|
316 | assert.equal(initReq.uri, '0-init.dat', 'the second request is for the init segment');
|
317 | assert.equal(segmentReq.uri, '0-test.ts', 'the third request is for a segment');
|
318 |
|
319 | segmentReq.response = new Uint8Array(8).buffer;
|
320 | segmentReq.respond(200, null, '');
|
321 | this.clock.tick(200);
|
322 |
|
323 | initReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
|
324 | initReq.respond(200, null, '');
|
325 | this.clock.tick(200);
|
326 |
|
327 | keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
|
328 | keyReq.respond(200, null, '');
|
329 |
|
330 |
|
331 | this.clock.tick(100);
|
332 | });
|