Chunk up decryption
Split up large segments that need AES-128 decryption and hand control back to the browser between segments. Clean up the decryption block loop a bit since it's no longer critical to finish before the next frame is rendered.
Showing
4 changed files
with
255 additions
and
70 deletions
... | @@ -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 |
... | @@ -221,7 +232,7 @@ AES.prototype = { | ... | @@ -221,7 +232,7 @@ AES.prototype = { |
221 | decrypt = function(encrypted, key, initVector) { | 232 | decrypt = function(encrypted, key, initVector) { |
222 | var | 233 | var |
223 | // word-level access to the encrypted bytes | 234 | // word-level access to the encrypted bytes |
224 | encrypted32 = new Int32Array(encrypted.buffer), | 235 | encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2), |
225 | 236 | ||
226 | decipher = new AES(Array.prototype.slice.call(key)), | 237 | decipher = new AES(Array.prototype.slice.call(key)), |
227 | 238 | ||
... | @@ -233,7 +244,6 @@ decrypt = function(encrypted, key, initVector) { | ... | @@ -233,7 +244,6 @@ decrypt = function(encrypted, key, initVector) { |
233 | // decrypted data | 244 | // decrypted data |
234 | init0, init1, init2, init3, | 245 | init0, init1, init2, init3, |
235 | encrypted0, encrypted1, encrypted2, encrypted3, | 246 | encrypted0, encrypted1, encrypted2, encrypted3, |
236 | decrypted0, decrypted1, decrypted2, decrypted3, | ||
237 | 247 | ||
238 | // iteration variable | 248 | // iteration variable |
239 | wordIx; | 249 | wordIx; |
... | @@ -250,49 +260,25 @@ decrypt = function(encrypted, key, initVector) { | ... | @@ -250,49 +260,25 @@ decrypt = function(encrypted, key, initVector) { |
250 | for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { | 260 | for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { |
251 | // convert big-endian (network order) words into little-endian | 261 | // convert big-endian (network order) words into little-endian |
252 | // (javascript order) | 262 | // (javascript order) |
253 | encrypted0 = (encrypted32[wordIx] << 24) | | 263 | encrypted0 = ntoh(encrypted32[wordIx]); |
254 | ((encrypted32[wordIx] & 0xff00) << 8) | | 264 | encrypted1 = ntoh(encrypted32[wordIx + 1]); |
255 | ((encrypted32[wordIx] & 0xff0000) >> 8) | | 265 | encrypted2 = ntoh(encrypted32[wordIx + 2]); |
256 | (encrypted32[wordIx] >>> 24); | 266 | encrypted3 = ntoh(encrypted32[wordIx + 3]); |
257 | encrypted1 = (encrypted32[wordIx + 1] << 24) | | ||
258 | ((encrypted32[wordIx + 1] & 0xff00) << 8) | | ||
259 | ((encrypted32[wordIx + 1] & 0xff0000) >> 8) | | ||
260 | (encrypted32[wordIx + 1] >>> 24); | ||
261 | encrypted2 = (encrypted32[wordIx + 2] << 24) | | ||
262 | ((encrypted32[wordIx + 2] & 0xff00) << 8) | | ||
263 | ((encrypted32[wordIx + 2] & 0xff0000) >> 8) | | ||
264 | (encrypted32[wordIx + 2] >>> 24); | ||
265 | encrypted3 = (encrypted32[wordIx + 3] << 24) | | ||
266 | ((encrypted32[wordIx + 3] & 0xff00) << 8) | | ||
267 | ((encrypted32[wordIx + 3] & 0xff0000) >> 8) | | ||
268 | (encrypted32[wordIx + 3] >>> 24); | ||
269 | 267 | ||
270 | // decrypt the block | 268 | // decrypt the block |
271 | decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); | 269 | decipher.decrypt(encrypted0, |
270 | encrypted1, | ||
271 | encrypted2, | ||
272 | encrypted3, | ||
273 | decrypted32, | ||
274 | wordIx); | ||
272 | 275 | ||
273 | // 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 |
274 | // plaintext | 277 | // plaintext |
275 | decrypted0 = decrypted32[wordIx] ^ init0; | 278 | decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); |
276 | decrypted1 = decrypted32[wordIx + 1] ^ init1; | 279 | decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); |
277 | decrypted2 = decrypted32[wordIx + 2] ^ init2; | 280 | decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); |
278 | decrypted3 = decrypted32[wordIx + 3] ^ init3; | 281 | decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); |
279 | |||
280 | decrypted32[wordIx] = decrypted0 << 24 | | ||
281 | ((decrypted0 & 0xff00) << 8) | | ||
282 | ((decrypted0 & 0xff0000) >> 8) | | ||
283 | (decrypted0 >>> 24); | ||
284 | decrypted32[wordIx + 1] = decrypted1 << 24 | | ||
285 | ((decrypted1 & 0xff00) << 8) | | ||
286 | ((decrypted1 & 0xff0000) >> 8) | | ||
287 | (decrypted1 >>> 24); | ||
288 | decrypted32[wordIx + 2] = decrypted2 << 24 | | ||
289 | ((decrypted2 & 0xff00) << 8) | | ||
290 | ((decrypted2 & 0xff0000) >> 8) | | ||
291 | (decrypted2 >>> 24); | ||
292 | decrypted32[wordIx + 3] = decrypted3 << 24 | | ||
293 | ((decrypted3 & 0xff00) << 8) | | ||
294 | ((decrypted3 & 0xff0000) >> 8) | | ||
295 | (decrypted3 >>> 24); | ||
296 | 282 | ||
297 | // setup the IV for the next round | 283 | // setup the IV for the next round |
298 | init0 = encrypted0; | 284 | init0 = encrypted0; |
... | @@ -301,11 +287,79 @@ decrypt = function(encrypted, key, initVector) { | ... | @@ -301,11 +287,79 @@ decrypt = function(encrypted, key, initVector) { |
301 | init3 = encrypted3; | 287 | init3 = encrypted3; |
302 | } | 288 | } |
303 | 289 | ||
304 | // remove any padding | 290 | return decrypted; |
305 | 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 | }; | ||
306 | }; | 356 | }; |
357 | // the maximum number of bytes to process at one time | ||
358 | Decrypter.STEP = 4 * 8000; | ||
307 | 359 | ||
308 | // exports | 360 | // exports |
309 | videojs.Hls.decrypt = decrypt; | 361 | videojs.Hls.decrypt = decrypt; |
362 | videojs.Hls.Decrypter = Decrypter; | ||
363 | videojs.Hls.AsyncStream = AsyncStream; | ||
310 | 364 | ||
311 | })(window, window.videojs, window.pkcs7.unpad); | 365 | })(window, window.videojs, window.pkcs7.unpad); | ... | ... |
... | @@ -633,6 +633,8 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -633,6 +633,8 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
633 | responseType: 'arraybuffer', | 633 | responseType: 'arraybuffer', |
634 | withCredentials: settings.withCredentials | 634 | withCredentials: settings.withCredentials |
635 | }, function(error, url) { | 635 | }, function(error, url) { |
636 | var segmentInfo; | ||
637 | |||
636 | // the segment request is no longer outstanding | 638 | // the segment request is no longer outstanding |
637 | tech.segmentXhr_ = null; | 639 | tech.segmentXhr_ = null; |
638 | 640 | ||
... | @@ -665,12 +667,26 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -665,12 +667,26 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
665 | // if the segment is the start of a timestamp discontinuity, | 667 | // if the segment is the start of a timestamp discontinuity, |
666 | // we have to wait until the sourcebuffer is empty before | 668 | // we have to wait until the sourcebuffer is empty before |
667 | // aborting the source buffer processing | 669 | // aborting the source buffer processing |
668 | tech.segmentBuffer_.push({ | 670 | segmentInfo = { |
671 | // the segment's mediaIndex at the time it was received | ||
669 | mediaIndex: tech.mediaIndex, | 672 | mediaIndex: tech.mediaIndex, |
673 | // the segment's playlist | ||
670 | playlist: tech.playlists.media(), | 674 | playlist: tech.playlists.media(), |
675 | // optionally, a time offset to seek to within the segment | ||
671 | offset: offset, | 676 | offset: offset, |
672 | bytes: new Uint8Array(this.response) | 677 | // unencrypted bytes of the segment |
673 | }); | 678 | bytes: null, |
679 | // when a key is defined for this segment, the encrypted bytes | ||
680 | encryptedBytes: null, | ||
681 | // optionally, the decrypter that is unencrypting the segment | ||
682 | decrypter: null | ||
683 | }; | ||
684 | if (segmentInfo.playlist.segments[segmentInfo.mediaIndex].key) { | ||
685 | segmentInfo.encryptedBytes = new Uint8Array(this.response); | ||
686 | } else { | ||
687 | segmentInfo.bytes = new Uint8Array(this.response); | ||
688 | } | ||
689 | tech.segmentBuffer_.push(segmentInfo); | ||
674 | player.trigger('progress'); | 690 | player.trigger('progress'); |
675 | tech.drainBuffer(); | 691 | tech.drainBuffer(); |
676 | 692 | ||
... | @@ -685,12 +701,15 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -685,12 +701,15 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
685 | videojs.Hls.prototype.drainBuffer = function(event) { | 701 | videojs.Hls.prototype.drainBuffer = function(event) { |
686 | var | 702 | var |
687 | i = 0, | 703 | i = 0, |
704 | segmentInfo, | ||
688 | mediaIndex, | 705 | mediaIndex, |
689 | playlist, | 706 | playlist, |
690 | offset, | 707 | offset, |
691 | tags, | 708 | tags, |
692 | bytes, | 709 | bytes, |
693 | segment, | 710 | segment, |
711 | decrypter, | ||
712 | segIv, | ||
694 | 713 | ||
695 | ptsTime, | 714 | ptsTime, |
696 | segmentOffset, | 715 | segmentOffset, |
... | @@ -700,28 +719,45 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -700,28 +719,45 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
700 | return; | 719 | return; |
701 | } | 720 | } |
702 | 721 | ||
703 | mediaIndex = segmentBuffer[0].mediaIndex; | 722 | segmentInfo = segmentBuffer[0]; |
704 | playlist = segmentBuffer[0].playlist; | 723 | |
705 | offset = segmentBuffer[0].offset; | 724 | mediaIndex = segmentInfo.mediaIndex; |
706 | bytes = segmentBuffer[0].bytes; | 725 | playlist = segmentInfo.playlist; |
726 | offset = segmentInfo.offset; | ||
727 | bytes = segmentInfo.bytes; | ||
707 | segment = playlist.segments[mediaIndex]; | 728 | segment = playlist.segments[mediaIndex]; |
708 | 729 | ||
709 | if (segment.key) { | 730 | if (segment.key && !bytes) { |
731 | |||
710 | // this is an encrypted segment | 732 | // this is an encrypted segment |
711 | // if the key download failed, we want to skip this segment | 733 | // if the key download failed, we want to skip this segment |
712 | // but if the key hasn't downloaded yet, we want to try again later | 734 | // but if the key hasn't downloaded yet, we want to try again later |
713 | if (keyFailed(segment.key)) { | 735 | if (keyFailed(segment.key)) { |
714 | return segmentBuffer.shift(); | 736 | return segmentBuffer.shift(); |
715 | } else if (!segment.key.bytes) { | 737 | } else if (!segment.key.bytes) { |
738 | |||
716 | // trigger a key request if one is not already in-flight | 739 | // trigger a key request if one is not already in-flight |
717 | return this.fetchKeys_(); | 740 | return this.fetchKeys_(); |
741 | |||
742 | } else if (segmentInfo.decrypter) { | ||
743 | |||
744 | // decryption is in progress, try again later | ||
745 | return; | ||
746 | |||
718 | } else { | 747 | } else { |
719 | // if the media sequence is greater than 2^32, the IV will be incorrect | 748 | // if the media sequence is greater than 2^32, the IV will be incorrect |
720 | // assuming 10s segments, that would be about 1300 years | 749 | // assuming 10s segments, that would be about 1300 years |
721 | var segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]); | 750 | segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]); |
722 | bytes = videojs.Hls.decrypt(bytes, | 751 | |
723 | segment.key.bytes, | 752 | // create a decrypter to incrementally decrypt the segment |
724 | segIv); | 753 | decrypter = new videojs.Hls.Decrypter(segmentInfo.encryptedBytes, |
754 | segment.key.bytes, | ||
755 | segIv, | ||
756 | function(err, bytes) { | ||
757 | segmentInfo.bytes = bytes; | ||
758 | }); | ||
759 | segmentInfo.decrypter = decrypter; | ||
760 | return; | ||
725 | } | 761 | } |
726 | } | 762 | } |
727 | 763 | ... | ... |
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(error, result) { | ||
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; |
... | @@ -1878,6 +1876,10 @@ test('a new key XHR is created when a the segment is received', function() { | ... | @@ -1878,6 +1876,10 @@ test('a new key XHR is created when a the segment is received', function() { |
1878 | '#EXT-X-ENDLIST\n'); | 1876 | '#EXT-X-ENDLIST\n'); |
1879 | standardXHRResponse(requests.shift()); // segment 1 | 1877 | standardXHRResponse(requests.shift()); // segment 1 |
1880 | standardXHRResponse(requests.shift()); // key 1 | 1878 | standardXHRResponse(requests.shift()); // key 1 |
1879 | // "finish" decrypting segment 1 | ||
1880 | player.hls.segmentBuffer_[0].bytes = new Uint8Array(16); | ||
1881 | player.hls.checkBuffer_(); | ||
1882 | |||
1881 | standardXHRResponse(requests.shift()); // segment 2 | 1883 | standardXHRResponse(requests.shift()); // segment 2 |
1882 | 1884 | ||
1883 | strictEqual(requests.length, 1, 'a key XHR is created'); | 1885 | strictEqual(requests.length, 1, 'a key XHR is created'); |
... | @@ -1983,6 +1985,9 @@ test('skip segments if key requests fail more than once', function() { | ... | @@ -1983,6 +1985,9 @@ test('skip segments if key requests fail more than once', function() { |
1983 | // key for second segment | 1985 | // key for second segment |
1984 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; | 1986 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; |
1985 | requests.shift().respond(200, null, ''); | 1987 | requests.shift().respond(200, null, ''); |
1988 | // "finish" decryption | ||
1989 | player.hls.segmentBuffer_[0].bytes = new Uint8Array(16); | ||
1990 | player.hls.checkBuffer_(); | ||
1986 | 1991 | ||
1987 | equal(bytes.length, 2, 'bytes from the second ts segment should be added'); | 1992 | equal(bytes.length, 2, 'bytes from the second ts segment should be added'); |
1988 | equal(bytes[1], 1, 'the bytes from the second segment are added and not the first'); | 1993 | equal(bytes[1], 1, 'the bytes from the second segment are added and not the first'); |
... | @@ -2007,9 +2012,8 @@ test('the key is supplied to the decrypter in the correct format', function() { | ... | @@ -2007,9 +2012,8 @@ test('the key is supplied to the decrypter in the correct format', function() { |
2007 | 'http://media.example.com/fileSequence52-B.ts\n'); | 2012 | 'http://media.example.com/fileSequence52-B.ts\n'); |
2008 | 2013 | ||
2009 | 2014 | ||
2010 | videojs.Hls.decrypt = function(bytes, key) { | 2015 | videojs.Hls.Decrypter = function(encrypted, key) { |
2011 | keys.push(key); | 2016 | keys.push(key); |
2012 | return new Uint8Array([0]); | ||
2013 | }; | 2017 | }; |
2014 | 2018 | ||
2015 | standardXHRResponse(requests.shift()); // segment | 2019 | standardXHRResponse(requests.shift()); // segment |
... | @@ -2017,7 +2021,7 @@ test('the key is supplied to the decrypter in the correct format', function() { | ... | @@ -2017,7 +2021,7 @@ test('the key is supplied to the decrypter in the correct format', function() { |
2017 | requests[0].respond(200, null, ''); | 2021 | requests[0].respond(200, null, ''); |
2018 | requests.shift(); // key | 2022 | requests.shift(); // key |
2019 | 2023 | ||
2020 | equal(keys.length, 1, 'only one call to decrypt was made'); | 2024 | equal(keys.length, 1, 'only one Decrypter was constructed'); |
2021 | deepEqual(keys[0], | 2025 | deepEqual(keys[0], |
2022 | new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]), | 2026 | new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]), |
2023 | 'passed the specified segment key'); | 2027 | 'passed the specified segment key'); |
... | @@ -2042,9 +2046,8 @@ test('supplies the media sequence of current segment as the IV by default, if no | ... | @@ -2042,9 +2046,8 @@ test('supplies the media sequence of current segment as the IV by default, if no |
2042 | 'http://media.example.com/fileSequence52-B.ts\n'); | 2046 | 'http://media.example.com/fileSequence52-B.ts\n'); |
2043 | 2047 | ||
2044 | 2048 | ||
2045 | videojs.Hls.decrypt = function(bytes, key, iv) { | 2049 | videojs.Hls.Decrypter = function(encrypted, key, iv) { |
2046 | ivs.push(iv); | 2050 | ivs.push(iv); |
2047 | return new Uint8Array([0]); | ||
2048 | }; | 2051 | }; |
2049 | 2052 | ||
2050 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; | 2053 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; |
... | @@ -2052,7 +2055,7 @@ test('supplies the media sequence of current segment as the IV by default, if no | ... | @@ -2052,7 +2055,7 @@ test('supplies the media sequence of current segment as the IV by default, if no |
2052 | requests.shift(); | 2055 | requests.shift(); |
2053 | standardXHRResponse(requests.pop()); | 2056 | standardXHRResponse(requests.pop()); |
2054 | 2057 | ||
2055 | equal(ivs.length, 1, 'only one call to decrypt was made'); | 2058 | equal(ivs.length, 1, 'only one Decrypter was constructed'); |
2056 | deepEqual(ivs[0], | 2059 | deepEqual(ivs[0], |
2057 | new Uint32Array([0, 0, 0, 5]), | 2060 | new Uint32Array([0, 0, 0, 5]), |
2058 | 'the IV for the segment is the media sequence'); | 2061 | 'the IV for the segment is the media sequence'); | ... | ... |
-
Please register or sign in to post a comment