e37356d8 by David LaPalomento

Merge pull request #251 from videojs/hlse-perf

Optimize decryption
2 parents 77591534 48f0f290
...@@ -38,7 +38,18 @@ ...@@ -38,7 +38,18 @@
38 (function(window, videojs, unpad) { 38 (function(window, videojs, unpad) {
39 'use strict'; 39 'use strict';
40 40
41 var AES, decrypt; 41 var AES, AsyncStream, Decrypter, decrypt, ntoh;
42
43 /**
44 * Convert network-order (big-endian) bytes into their little-endian
45 * representation.
46 */
47 ntoh = function(word) {
48 return (word << 24) |
49 ((word & 0xff00) << 8) |
50 ((word & 0xff0000) >> 8) |
51 (word >>> 24);
52 };
42 53
43 /** 54 /**
44 * Schedule out an AES key for both encryption and decryption. This 55 * Schedule out an AES key for both encryption and decryption. This
...@@ -151,49 +162,51 @@ AES.prototype = { ...@@ -151,49 +162,51 @@ AES.prototype = {
151 }, 162 },
152 163
153 /** 164 /**
154 * Decrypt an array of 4 big-endian words. 165 * Decrypt 16 bytes, specified as four 32-bit words.
155 * @param {Array} data The ciphertext. 166 * @param encrypted0 {number} the first word to decrypt
167 * @param encrypted1 {number} the second word to decrypt
168 * @param encrypted2 {number} the third word to decrypt
169 * @param encrypted3 {number} the fourth word to decrypt
170 * @param out {Int32Array} the array to write the decrypted words
171 * into
172 * @param offset {number} the offset into the output array to start
173 * writing results
156 * @return {Array} The plaintext. 174 * @return {Array} The plaintext.
157 */ 175 */
158 decrypt:function (input) { 176 decrypt:function (encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
159 if (input.length !== 4) {
160 throw new Error("Invalid aes block size");
161 }
162
163 var key = this._key[1], 177 var key = this._key[1],
164 // state variables a,b,c,d are loaded with pre-whitened data 178 // state variables a,b,c,d are loaded with pre-whitened data
165 a = input[0] ^ key[0], 179 a = encrypted0 ^ key[0],
166 b = input[3] ^ key[1], 180 b = encrypted3 ^ key[1],
167 c = input[2] ^ key[2], 181 c = encrypted2 ^ key[2],
168 d = input[1] ^ key[3], 182 d = encrypted1 ^ key[3],
169 a2, b2, c2, 183 a2, b2, c2,
170 184
171 nInnerRounds = key.length/4 - 2, 185 nInnerRounds = key.length / 4 - 2, // key.length === 2 ?
172 i, 186 i,
173 kIndex = 4, 187 kIndex = 4,
174 out = [0,0,0,0],
175 table = this._tables[1], 188 table = this._tables[1],
176 189
177 // load up the tables 190 // load up the tables
178 t0 = table[0], 191 table0 = table[0],
179 t1 = table[1], 192 table1 = table[1],
180 t2 = table[2], 193 table2 = table[2],
181 t3 = table[3], 194 table3 = table[3],
182 sbox = table[4]; 195 sbox = table[4];
183 196
184 // Inner rounds. Cribbed from OpenSSL. 197 // Inner rounds. Cribbed from OpenSSL.
185 for (i = 0; i < nInnerRounds; i++) { 198 for (i = 0; i < nInnerRounds; i++) {
186 a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex]; 199 a2 = table0[a>>>24] ^ table1[b>>16 & 255] ^ table2[c>>8 & 255] ^ table3[d & 255] ^ key[kIndex];
187 b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; 200 b2 = table0[b>>>24] ^ table1[c>>16 & 255] ^ table2[d>>8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
188 c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; 201 c2 = table0[c>>>24] ^ table1[d>>16 & 255] ^ table2[a>>8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
189 d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; 202 d = table0[d>>>24] ^ table1[a>>16 & 255] ^ table2[b>>8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
190 kIndex += 4; 203 kIndex += 4;
191 a=a2; b=b2; c=c2; 204 a=a2; b=b2; c=c2;
192 } 205 }
193 206
194 // Last round. 207 // Last round.
195 for (i = 0; i < 4; i++) { 208 for (i = 0; i < 4; i++) {
196 out[3 & -i] = 209 out[(3 & -i) + offset] =
197 sbox[a>>>24 ]<<24 ^ 210 sbox[a>>>24 ]<<24 ^
198 sbox[b>>16 & 255]<<16 ^ 211 sbox[b>>16 & 255]<<16 ^
199 sbox[c>>8 & 255]<<8 ^ 212 sbox[c>>8 & 255]<<8 ^
...@@ -201,49 +214,152 @@ AES.prototype = { ...@@ -201,49 +214,152 @@ AES.prototype = {
201 key[kIndex++]; 214 key[kIndex++];
202 a2=a; a=b; b=c; c=d; d=a2; 215 a2=a; a=b; b=c; c=d; d=a2;
203 } 216 }
204
205 return out;
206 } 217 }
207 }; 218 };
208 219
220 /**
221 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
222 * @param encrypted {Uint8Array} the encrypted bytes
223 * @param key {Uint32Array} the bytes of the decryption key
224 * @param initVector {Uint32Array} the initialization vector (IV) to
225 * use for the first round of CBC.
226 * @return {Uint8Array} the decrypted bytes
227 *
228 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
229 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
230 * @see https://tools.ietf.org/html/rfc2315
231 */
209 decrypt = function(encrypted, key, initVector) { 232 decrypt = function(encrypted, key, initVector) {
210 var 233 var
211 encryptedView = new DataView(encrypted.buffer), 234 // word-level access to the encrypted bytes
212 platformEndian = new Uint32Array(encrypted.byteLength / 4), 235 encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2),
236
213 decipher = new AES(Array.prototype.slice.call(key)), 237 decipher = new AES(Array.prototype.slice.call(key)),
238
239 // byte and word-level access for the decrypted output
214 decrypted = new Uint8Array(encrypted.byteLength), 240 decrypted = new Uint8Array(encrypted.byteLength),
215 decryptedView = new DataView(decrypted.buffer), 241 decrypted32 = new Int32Array(decrypted.buffer),
216 decryptedBlock, 242
217 word, 243 // temporary variables for working with the IV, encrypted, and
218 byte; 244 // decrypted data
245 init0, init1, init2, init3,
246 encrypted0, encrypted1, encrypted2, encrypted3,
247
248 // iteration variable
249 wordIx;
250
251 // pull out the words of the IV to ensure we don't modify the
252 // passed-in reference and easier access
253 init0 = initVector[0];
254 init1 = initVector[1];
255 init2 = initVector[2];
256 init3 = initVector[3];
219 257
220 // convert big-endian input to platform byte order for decryption
221 for (byte = 0; byte < encrypted.byteLength; byte += 4) {
222 platformEndian[byte >>> 2] = encryptedView.getUint32(byte);
223 }
224 // decrypt four word sequences, applying cipher-block chaining (CBC) 258 // decrypt four word sequences, applying cipher-block chaining (CBC)
225 // to each decrypted block 259 // to each decrypted block
226 for (word = 0; word < platformEndian.length; word += 4) { 260 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
261 // convert big-endian (network order) words into little-endian
262 // (javascript order)
263 encrypted0 = ntoh(encrypted32[wordIx]);
264 encrypted1 = ntoh(encrypted32[wordIx + 1]);
265 encrypted2 = ntoh(encrypted32[wordIx + 2]);
266 encrypted3 = ntoh(encrypted32[wordIx + 3]);
267
227 // decrypt the block 268 // decrypt the block
228 decryptedBlock = decipher.decrypt(platformEndian.subarray(word, word + 4)); 269 decipher.decrypt(encrypted0,
270 encrypted1,
271 encrypted2,
272 encrypted3,
273 decrypted32,
274 wordIx);
229 275
230 // XOR with the IV, and restore network byte-order to obtain the 276 // XOR with the IV, and restore network byte-order to obtain the
231 // plaintext 277 // plaintext
232 byte = word << 2; 278 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
233 decryptedView.setUint32(byte, decryptedBlock[0] ^ initVector[0]); 279 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
234 decryptedView.setUint32(byte + 4, decryptedBlock[1] ^ initVector[1]); 280 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
235 decryptedView.setUint32(byte + 8, decryptedBlock[2] ^ initVector[2]); 281 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
236 decryptedView.setUint32(byte + 12, decryptedBlock[3] ^ initVector[3]);
237 282
238 // setup the IV for the next round 283 // setup the IV for the next round
239 initVector = platformEndian.subarray(word, word + 4); 284 init0 = encrypted0;
285 init1 = encrypted1;
286 init2 = encrypted2;
287 init3 = encrypted3;
240 } 288 }
241 289
242 // remove any padding 290 return decrypted;
243 return unpad(decrypted); 291 };
292
293 AsyncStream = function() {
294 this.jobs = [];
295 this.delay = 1;
296 this.timeout_ = null;
297 };
298 AsyncStream.prototype = new videojs.Hls.Stream();
299 AsyncStream.prototype.processJob_ = function() {
300 this.jobs.shift()();
301 if (this.jobs.length) {
302 this.timeout_ = setTimeout(videojs.bind(this, this.processJob_),
303 this.delay);
304 } else {
305 this.timeout_ = null;
306 }
307 };
308 AsyncStream.prototype.push = function(job) {
309 this.jobs.push(job);
310 if (!this.timeout_) {
311 this.timeout_ = setTimeout(videojs.bind(this, this.processJob_),
312 this.delay);
313 }
314 };
315
316 Decrypter = function(encrypted, key, initVector, done) {
317 var
318 step = Decrypter.STEP,
319 encrypted32 = new Int32Array(encrypted.buffer),
320 decrypted = new Uint8Array(encrypted.byteLength),
321 i = 0;
322 this.asyncStream_ = new AsyncStream();
323
324 // split up the encryption job and do the individual chunks asynchronously
325 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
326 key,
327 initVector,
328 decrypted,
329 i));
330 for (i = step; i < encrypted32.length; i += step) {
331 initVector = new Uint32Array([
332 ntoh(encrypted32[i - 4]),
333 ntoh(encrypted32[i - 3]),
334 ntoh(encrypted32[i - 2]),
335 ntoh(encrypted32[i - 1])
336 ]);
337 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
338 key,
339 initVector,
340 decrypted));
341 }
342 // invoke the done() callback when everything is finished
343 this.asyncStream_.push(function() {
344 // remove pkcs#7 padding from the decrypted bytes
345 done(null, unpad(decrypted));
346 });
347 };
348 Decrypter.prototype = new videojs.Hls.Stream();
349 Decrypter.prototype.decryptChunk_ = function(encrypted, key, initVector, decrypted) {
350 return function() {
351 var bytes = decrypt(encrypted,
352 key,
353 initVector);
354 decrypted.set(bytes, encrypted.byteOffset);
355 };
244 }; 356 };
357 // the maximum number of bytes to process at one time
358 Decrypter.STEP = 4 * 8000;
245 359
246 // exports 360 // exports
247 videojs.Hls.decrypt = decrypt; 361 videojs.Hls.decrypt = decrypt;
362 videojs.Hls.Decrypter = Decrypter;
363 videojs.Hls.AsyncStream = AsyncStream;
248 364
249 })(window, window.videojs, window.pkcs7.unpad); 365 })(window, window.videojs, window.pkcs7.unpad);
......
...@@ -637,6 +637,8 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -637,6 +637,8 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
637 responseType: 'arraybuffer', 637 responseType: 'arraybuffer',
638 withCredentials: settings.withCredentials 638 withCredentials: settings.withCredentials
639 }, function(error, url) { 639 }, function(error, url) {
640 var segmentInfo;
641
640 // the segment request is no longer outstanding 642 // the segment request is no longer outstanding
641 tech.segmentXhr_ = null; 643 tech.segmentXhr_ = null;
642 644
...@@ -669,12 +671,26 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -669,12 +671,26 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
669 // if the segment is the start of a timestamp discontinuity, 671 // if the segment is the start of a timestamp discontinuity,
670 // we have to wait until the sourcebuffer is empty before 672 // we have to wait until the sourcebuffer is empty before
671 // aborting the source buffer processing 673 // aborting the source buffer processing
672 tech.segmentBuffer_.push({ 674 segmentInfo = {
675 // the segment's mediaIndex at the time it was received
673 mediaIndex: tech.mediaIndex, 676 mediaIndex: tech.mediaIndex,
677 // the segment's playlist
674 playlist: tech.playlists.media(), 678 playlist: tech.playlists.media(),
679 // optionally, a time offset to seek to within the segment
675 offset: offset, 680 offset: offset,
676 bytes: new Uint8Array(this.response) 681 // unencrypted bytes of the segment
677 }); 682 bytes: null,
683 // when a key is defined for this segment, the encrypted bytes
684 encryptedBytes: null,
685 // optionally, the decrypter that is unencrypting the segment
686 decrypter: null
687 };
688 if (segmentInfo.playlist.segments[segmentInfo.mediaIndex].key) {
689 segmentInfo.encryptedBytes = new Uint8Array(this.response);
690 } else {
691 segmentInfo.bytes = new Uint8Array(this.response);
692 }
693 tech.segmentBuffer_.push(segmentInfo);
678 player.trigger('progress'); 694 player.trigger('progress');
679 tech.drainBuffer(); 695 tech.drainBuffer();
680 696
...@@ -689,12 +705,15 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -689,12 +705,15 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
689 videojs.Hls.prototype.drainBuffer = function(event) { 705 videojs.Hls.prototype.drainBuffer = function(event) {
690 var 706 var
691 i = 0, 707 i = 0,
708 segmentInfo,
692 mediaIndex, 709 mediaIndex,
693 playlist, 710 playlist,
694 offset, 711 offset,
695 tags, 712 tags,
696 bytes, 713 bytes,
697 segment, 714 segment,
715 decrypter,
716 segIv,
698 717
699 ptsTime, 718 ptsTime,
700 segmentOffset, 719 segmentOffset,
...@@ -704,28 +723,45 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -704,28 +723,45 @@ videojs.Hls.prototype.drainBuffer = function(event) {
704 return; 723 return;
705 } 724 }
706 725
707 mediaIndex = segmentBuffer[0].mediaIndex; 726 segmentInfo = segmentBuffer[0];
708 playlist = segmentBuffer[0].playlist; 727
709 offset = segmentBuffer[0].offset; 728 mediaIndex = segmentInfo.mediaIndex;
710 bytes = segmentBuffer[0].bytes; 729 playlist = segmentInfo.playlist;
730 offset = segmentInfo.offset;
731 bytes = segmentInfo.bytes;
711 segment = playlist.segments[mediaIndex]; 732 segment = playlist.segments[mediaIndex];
712 733
713 if (segment.key) { 734 if (segment.key && !bytes) {
735
714 // this is an encrypted segment 736 // this is an encrypted segment
715 // if the key download failed, we want to skip this segment 737 // if the key download failed, we want to skip this segment
716 // but if the key hasn't downloaded yet, we want to try again later 738 // but if the key hasn't downloaded yet, we want to try again later
717 if (keyFailed(segment.key)) { 739 if (keyFailed(segment.key)) {
718 return segmentBuffer.shift(); 740 return segmentBuffer.shift();
719 } else if (!segment.key.bytes) { 741 } else if (!segment.key.bytes) {
742
720 // trigger a key request if one is not already in-flight 743 // trigger a key request if one is not already in-flight
721 return this.fetchKeys_(); 744 return this.fetchKeys_();
745
746 } else if (segmentInfo.decrypter) {
747
748 // decryption is in progress, try again later
749 return;
750
722 } else { 751 } else {
723 // if the media sequence is greater than 2^32, the IV will be incorrect 752 // if the media sequence is greater than 2^32, the IV will be incorrect
724 // assuming 10s segments, that would be about 1300 years 753 // assuming 10s segments, that would be about 1300 years
725 var segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]); 754 segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]);
726 bytes = videojs.Hls.decrypt(bytes, 755
756 // create a decrypter to incrementally decrypt the segment
757 decrypter = new videojs.Hls.Decrypter(segmentInfo.encryptedBytes,
727 segment.key.bytes, 758 segment.key.bytes,
728 segIv); 759 segIv,
760 function(err, bytes) {
761 segmentInfo.bytes = bytes;
762 });
763 segmentInfo.decrypter = decrypter;
764 return;
729 } 765 }
730 } 766 }
731 767
......
1 (function(window, videojs, undefined) { 1 (function(window, videojs, unpad, undefined) {
2 'use strict'; 2 'use strict';
3 /* 3 /*
4 ======== A Handy Little QUnit Reference ======== 4 ======== A Handy Little QUnit Reference ========
...@@ -46,7 +46,7 @@ test('decrypts a single AES-128 with PKCS7 block', function() { ...@@ -46,7 +46,7 @@ test('decrypts a single AES-128 with PKCS7 block', function() {
46 0x82, 0xa8, 0xf0, 0x67]); 46 0x82, 0xa8, 0xf0, 0x67]);
47 47
48 deepEqual('howdy folks', 48 deepEqual('howdy folks',
49 stringFromBytes(videojs.Hls.decrypt(encrypted, key, initVector)), 49 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))),
50 'decrypted with a byte array key'); 50 'decrypted with a byte array key');
51 }); 51 });
52 52
...@@ -68,8 +68,100 @@ test('decrypts multiple AES-128 blocks with CBC', function() { ...@@ -68,8 +68,100 @@ test('decrypts multiple AES-128 blocks with CBC', function() {
68 ]); 68 ]);
69 69
70 deepEqual('0123456789abcdef01234', 70 deepEqual('0123456789abcdef01234',
71 stringFromBytes(videojs.Hls.decrypt(encrypted, key, initVector)), 71 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))),
72 'decrypted multiple blocks'); 72 'decrypted multiple blocks');
73 }); 73 });
74 74
75 })(window, window.videojs); 75 var clock;
76
77 module('Incremental Processing', {
78 setup: function() {
79 clock = sinon.useFakeTimers();
80 },
81 teardown: function() {
82 clock.restore();
83 }
84 });
85
86 test('executes a callback after a timeout', function() {
87 var asyncStream = new videojs.Hls.AsyncStream(),
88 calls = '';
89 asyncStream.push(function() {
90 calls += 'a';
91 });
92
93 clock.tick(asyncStream.delay);
94 equal(calls, 'a', 'invoked the callback once');
95 clock.tick(asyncStream.delay);
96 equal(calls, 'a', 'only invoked the callback once');
97 });
98
99 test('executes callback in series', function() {
100 var asyncStream = new videojs.Hls.AsyncStream(),
101 calls = '';
102 asyncStream.push(function() {
103 calls += 'a';
104 });
105 asyncStream.push(function() {
106 calls += 'b';
107 });
108
109 clock.tick(asyncStream.delay);
110 equal(calls, 'a', 'invoked the first callback');
111 clock.tick(asyncStream.delay);
112 equal(calls, 'ab', 'invoked the second');
113 });
114
115 var decrypter;
116
117 module('Incremental Decryption', {
118 setup: function() {
119 clock = sinon.useFakeTimers();
120 },
121 teardown: function() {
122 clock.restore();
123 }
124 });
125
126 test('asynchronously decrypts a 4-word block', function() {
127 var
128 key = new Uint32Array([0, 0, 0, 0]),
129 initVector = key,
130 // the string "howdy folks" encrypted
131 encrypted = new Uint8Array([
132 0xce, 0x90, 0x97, 0xd0,
133 0x08, 0x46, 0x4d, 0x18,
134 0x4f, 0xae, 0x01, 0x1c,
135 0x82, 0xa8, 0xf0, 0x67]),
136 decrypted;
137
138 decrypter = new videojs.Hls.Decrypter(encrypted, key, initVector, function(error, result) {
139 decrypted = result;
140 });
141 ok(!decrypted, 'asynchronously decrypts');
142
143 clock.tick(decrypter.asyncStream_.delay * 2);
144
145 ok(decrypted, 'completed decryption');
146 deepEqual('howdy folks',
147 stringFromBytes(decrypted),
148 'decrypts and unpads the result');
149 });
150
151 test('breaks up input greater than the step value', function() {
152 var encrypted = new Int32Array(videojs.Hls.Decrypter.STEP + 4),
153 done = false,
154 decrypter = new videojs.Hls.Decrypter(encrypted,
155 new Uint32Array(4),
156 new Uint32Array(4),
157 function() {
158 done = true;
159 });
160 clock.tick(decrypter.asyncStream_.delay * 2);
161 ok(!done, 'not finished after two ticks');
162
163 clock.tick(decrypter.asyncStream_.delay);
164 ok(done, 'finished after the last chunk is decrypted');
165 });
166
167 })(window, window.videojs, window.pkcs7.unpad);
......
...@@ -152,10 +152,8 @@ module('HLS', { ...@@ -152,10 +152,8 @@ module('HLS', {
152 152
153 oldNativeHlsSupport = videojs.Hls.supportsNativeHls; 153 oldNativeHlsSupport = videojs.Hls.supportsNativeHls;
154 154
155 oldDecrypt = videojs.Hls.decrypt; 155 oldDecrypt = videojs.Hls.Decrypter;
156 videojs.Hls.decrypt = function() { 156 videojs.Hls.Decrypter = function() {};
157 return new Uint8Array([0]);
158 };
159 157
160 // fake XHRs 158 // fake XHRs
161 xhr = sinon.useFakeXMLHttpRequest(); 159 xhr = sinon.useFakeXMLHttpRequest();
...@@ -173,7 +171,7 @@ module('HLS', { ...@@ -173,7 +171,7 @@ module('HLS', {
173 videojs.MediaSource.open = oldMediaSourceOpen; 171 videojs.MediaSource.open = oldMediaSourceOpen;
174 videojs.Hls.SegmentParser = oldSegmentParser; 172 videojs.Hls.SegmentParser = oldSegmentParser;
175 videojs.Hls.supportsNativeHls = oldNativeHlsSupport; 173 videojs.Hls.supportsNativeHls = oldNativeHlsSupport;
176 videojs.Hls.decrypt = oldDecrypt; 174 videojs.Hls.Decrypter = oldDecrypt;
177 videojs.SourceBuffer = oldSourceBuffer; 175 videojs.SourceBuffer = oldSourceBuffer;
178 window.setTimeout = oldSetTimeout; 176 window.setTimeout = oldSetTimeout;
179 window.clearTimeout = oldClearTimeout; 177 window.clearTimeout = oldClearTimeout;
...@@ -1904,6 +1902,10 @@ test('a new key XHR is created when a the segment is received', function() { ...@@ -1904,6 +1902,10 @@ test('a new key XHR is created when a the segment is received', function() {
1904 '#EXT-X-ENDLIST\n'); 1902 '#EXT-X-ENDLIST\n');
1905 standardXHRResponse(requests.shift()); // segment 1 1903 standardXHRResponse(requests.shift()); // segment 1
1906 standardXHRResponse(requests.shift()); // key 1 1904 standardXHRResponse(requests.shift()); // key 1
1905 // "finish" decrypting segment 1
1906 player.hls.segmentBuffer_[0].bytes = new Uint8Array(16);
1907 player.hls.checkBuffer_();
1908
1907 standardXHRResponse(requests.shift()); // segment 2 1909 standardXHRResponse(requests.shift()); // segment 2
1908 1910
1909 strictEqual(requests.length, 1, 'a key XHR is created'); 1911 strictEqual(requests.length, 1, 'a key XHR is created');
...@@ -2009,6 +2011,9 @@ test('skip segments if key requests fail more than once', function() { ...@@ -2009,6 +2011,9 @@ test('skip segments if key requests fail more than once', function() {
2009 // key for second segment 2011 // key for second segment
2010 requests[0].response = new Uint32Array([0,0,0,0]).buffer; 2012 requests[0].response = new Uint32Array([0,0,0,0]).buffer;
2011 requests.shift().respond(200, null, ''); 2013 requests.shift().respond(200, null, '');
2014 // "finish" decryption
2015 player.hls.segmentBuffer_[0].bytes = new Uint8Array(16);
2016 player.hls.checkBuffer_();
2012 2017
2013 equal(bytes.length, 2, 'bytes from the second ts segment should be added'); 2018 equal(bytes.length, 2, 'bytes from the second ts segment should be added');
2014 equal(bytes[1], 1, 'the bytes from the second segment are added and not the first'); 2019 equal(bytes[1], 1, 'the bytes from the second segment are added and not the first');
...@@ -2033,9 +2038,8 @@ test('the key is supplied to the decrypter in the correct format', function() { ...@@ -2033,9 +2038,8 @@ test('the key is supplied to the decrypter in the correct format', function() {
2033 'http://media.example.com/fileSequence52-B.ts\n'); 2038 'http://media.example.com/fileSequence52-B.ts\n');
2034 2039
2035 2040
2036 videojs.Hls.decrypt = function(bytes, key) { 2041 videojs.Hls.Decrypter = function(encrypted, key) {
2037 keys.push(key); 2042 keys.push(key);
2038 return new Uint8Array([0]);
2039 }; 2043 };
2040 2044
2041 standardXHRResponse(requests.shift()); // segment 2045 standardXHRResponse(requests.shift()); // segment
...@@ -2043,7 +2047,7 @@ test('the key is supplied to the decrypter in the correct format', function() { ...@@ -2043,7 +2047,7 @@ test('the key is supplied to the decrypter in the correct format', function() {
2043 requests[0].respond(200, null, ''); 2047 requests[0].respond(200, null, '');
2044 requests.shift(); // key 2048 requests.shift(); // key
2045 2049
2046 equal(keys.length, 1, 'only one call to decrypt was made'); 2050 equal(keys.length, 1, 'only one Decrypter was constructed');
2047 deepEqual(keys[0], 2051 deepEqual(keys[0],
2048 new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]), 2052 new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]),
2049 'passed the specified segment key'); 2053 'passed the specified segment key');
...@@ -2068,9 +2072,8 @@ test('supplies the media sequence of current segment as the IV by default, if no ...@@ -2068,9 +2072,8 @@ test('supplies the media sequence of current segment as the IV by default, if no
2068 'http://media.example.com/fileSequence52-B.ts\n'); 2072 'http://media.example.com/fileSequence52-B.ts\n');
2069 2073
2070 2074
2071 videojs.Hls.decrypt = function(bytes, key, iv) { 2075 videojs.Hls.Decrypter = function(encrypted, key, iv) {
2072 ivs.push(iv); 2076 ivs.push(iv);
2073 return new Uint8Array([0]);
2074 }; 2077 };
2075 2078
2076 requests[0].response = new Uint32Array([0,0,0,0]).buffer; 2079 requests[0].response = new Uint32Array([0,0,0,0]).buffer;
...@@ -2078,7 +2081,7 @@ test('supplies the media sequence of current segment as the IV by default, if no ...@@ -2078,7 +2081,7 @@ test('supplies the media sequence of current segment as the IV by default, if no
2078 requests.shift(); 2081 requests.shift();
2079 standardXHRResponse(requests.pop()); 2082 standardXHRResponse(requests.pop());
2080 2083
2081 equal(ivs.length, 1, 'only one call to decrypt was made'); 2084 equal(ivs.length, 1, 'only one Decrypter was constructed');
2082 deepEqual(ivs[0], 2085 deepEqual(ivs[0],
2083 new Uint32Array([0, 0, 0, 5]), 2086 new Uint32Array([0, 0, 0, 5]),
2084 'the IV for the segment is the media sequence'); 2087 'the IV for the segment is the media sequence');
......