Encrypted HLS working on the example page
Used an adaptive, encrypted m3u8 on the example page and included hex dump utilities. Fixed formatting issues with the hex dump utility. Passed a 16-byte IV to the decrypter in drainBuffer(). Swapped byte order for the keys so they are not misintrepreted as little-endian. Added fields to the muxer test page to mux encrypted segments. Updated tests. Fixed docs on using openssl to test encryption and decryption.
Showing
8 changed files
with
159 additions
and
27 deletions
... | @@ -6,7 +6,7 @@ The [HLS spec](http://tools.ietf.org/html/draft-pantos-http-live-streaming-13#se | ... | @@ -6,7 +6,7 @@ The [HLS spec](http://tools.ietf.org/html/draft-pantos-http-live-streaming-13#se |
6 | # since this is for testing, skip the key salting so the output is stable | 6 | # since this is for testing, skip the key salting so the output is stable |
7 | # using -nosalt outside of testing is a terrible idea! | 7 | # using -nosalt outside of testing is a terrible idea! |
8 | echo -n "hello" | pkcs7 | \ | 8 | echo -n "hello" | pkcs7 | \ |
9 | openssl enc -aes-128-cbc -nopad -nosalt -k $KEY -iv $IV > hello.encrypted | 9 | openssl enc -aes-128-cbc -nopad -nosalt -K $KEY -iv $IV > hello.encrypted |
10 | 10 | ||
11 | # xxd is a handy way of translating binary into a format easily consumed by | 11 | # xxd is a handy way of translating binary into a format easily consumed by |
12 | # javascript | 12 | # javascript |
... | @@ -16,5 +16,5 @@ xxd -i hello.encrypted | ... | @@ -16,5 +16,5 @@ xxd -i hello.encrypted |
16 | Later, you can decrypt it: | 16 | Later, you can decrypt it: |
17 | 17 | ||
18 | ```sh | 18 | ```sh |
19 | openssl enc -d -nopad -aes-128-cbc -k $KEY -iv $IV | 19 | openssl enc -d -nopad -aes-128-cbc -K $KEY -iv $IV |
20 | ``` | 20 | ``` | ... | ... |
... | @@ -27,6 +27,11 @@ | ... | @@ -27,6 +27,11 @@ |
27 | <script src="src/stream.js"></script> | 27 | <script src="src/stream.js"></script> |
28 | <script src="src/m3u8/m3u8-parser.js"></script> | 28 | <script src="src/m3u8/m3u8-parser.js"></script> |
29 | <script src="src/playlist-loader.js"></script> | 29 | <script src="src/playlist-loader.js"></script> |
30 | |||
31 | <script src="node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | ||
32 | <script src="src/decrypter.js"></script> | ||
33 | |||
34 | <script src="src/bin-utils.js"></script> | ||
30 | 35 | ||
31 | <!-- example MPEG2-TS segments --> | 36 | <!-- example MPEG2-TS segments --> |
32 | <!-- bipbop --> | 37 | <!-- bipbop --> |
... | @@ -58,8 +63,11 @@ | ... | @@ -58,8 +63,11 @@ |
58 | width="600" | 63 | width="600" |
59 | controls> | 64 | controls> |
60 | <source | 65 | <source |
61 | src="http://solutions.brightcove.com/jwhisenant/hls/apple/bipbop/bipbopall.m3u8" | 66 | src="http://solutions.brightcove.com/jwhisenant/hls/encrypted/brightcove/oceans/oceans_aes-remote-key.m3u8" |
62 | type="application/x-mpegURL"> | 67 | type="application/x-mpegURL"> |
68 | <!--<source--> | ||
69 | <!--src="http://solutions.brightcove.com/jwhisenant/hls/apple/bipbop/bipbopall.m3u8"--> | ||
70 | <!--type="application/x-mpegURL">--> | ||
63 | </video> | 71 | </video> |
64 | <script> | 72 | <script> |
65 | videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; | 73 | videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; | ... | ... |
... | @@ -4,12 +4,12 @@ | ... | @@ -4,12 +4,12 @@ |
4 | var | 4 | var |
5 | bytes = Array.prototype.slice.call(data), | 5 | bytes = Array.prototype.slice.call(data), |
6 | step = 16, | 6 | step = 16, |
7 | formatHexString = function(e) { | 7 | formatHexString = function(e, i) { |
8 | var value = e.toString(16); | 8 | var value = e.toString(16); |
9 | return "00".substring(0, 2 - value.length) + value; | 9 | return "00".substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); |
10 | }, | 10 | }, |
11 | formatAsciiString = function(e) { | 11 | formatAsciiString = function(e) { |
12 | if (e > 32 && e < 125) { | 12 | if (e >= 0x20 && e < 0x7e) { |
13 | return String.fromCharCode(e); | 13 | return String.fromCharCode(e); |
14 | } | 14 | } |
15 | return '.'; | 15 | return '.'; |
... | @@ -18,9 +18,9 @@ | ... | @@ -18,9 +18,9 @@ |
18 | hex, | 18 | hex, |
19 | ascii; | 19 | ascii; |
20 | for (var j = 0; j < bytes.length / step; j++) { | 20 | for (var j = 0; j < bytes.length / step; j++) { |
21 | hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(' '); | 21 | hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); |
22 | ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); | 22 | ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); |
23 | result += hex + ' ' + ascii + '\n'; | 23 | result += hex + ' ' + ascii + '\n'; |
24 | } | 24 | } |
25 | return result; | 25 | return result; |
26 | }, | 26 | }, | ... | ... |
... | @@ -214,11 +214,12 @@ decrypt = function(encrypted, key, initVector) { | ... | @@ -214,11 +214,12 @@ decrypt = function(encrypted, key, initVector) { |
214 | var | 214 | var |
215 | encryptedView = new DataView(encrypted.buffer), | 215 | encryptedView = new DataView(encrypted.buffer), |
216 | platformEndian = new Uint32Array(encrypted.byteLength / 4), | 216 | platformEndian = new Uint32Array(encrypted.byteLength / 4), |
217 | decipher = new AES(key), | 217 | decipher = new AES(Array.prototype.slice.call(key)), |
218 | decrypted = new Uint8Array(encrypted.byteLength), | 218 | decrypted = new Uint8Array(encrypted.byteLength), |
219 | decryptedView = new DataView(decrypted.buffer), | 219 | decryptedView = new DataView(decrypted.buffer), |
220 | decryptedBlock, | 220 | decryptedBlock, |
221 | word, byte; | 221 | word, |
222 | byte; | ||
222 | 223 | ||
223 | // convert big-endian input to platform byte order for decryption | 224 | // convert big-endian input to platform byte order for decryption |
224 | for (byte = 0; byte < encrypted.byteLength; byte += 4) { | 225 | for (byte = 0; byte < encrypted.byteLength; byte += 4) { | ... | ... |
... | @@ -379,8 +379,6 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -379,8 +379,6 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
379 | responseType: 'arraybuffer', | 379 | responseType: 'arraybuffer', |
380 | withCredentials: settings.withCredentials | 380 | withCredentials: settings.withCredentials |
381 | }, function(error, url) { | 381 | }, function(error, url) { |
382 | var tags; | ||
383 | |||
384 | // the segment request is no longer outstanding | 382 | // the segment request is no longer outstanding |
385 | tech.segmentXhr_ = null; | 383 | tech.segmentXhr_ = null; |
386 | 384 | ||
... | @@ -420,7 +418,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -420,7 +418,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
420 | mediaIndex: tech.mediaIndex, | 418 | mediaIndex: tech.mediaIndex, |
421 | playlist: tech.playlists.media(), | 419 | playlist: tech.playlists.media(), |
422 | offset: offset, | 420 | offset: offset, |
423 | bytes: this.response | 421 | bytes: new Uint8Array(this.response) |
424 | }); | 422 | }); |
425 | tech.drainBuffer(); | 423 | tech.drainBuffer(); |
426 | 424 | ||
... | @@ -464,6 +462,14 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -464,6 +462,14 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
464 | return segmentBuffer.shift(); | 462 | return segmentBuffer.shift(); |
465 | } else if (!segment.key.bytes) { | 463 | } else if (!segment.key.bytes) { |
466 | return; | 464 | return; |
465 | } else { | ||
466 | // if the media sequence is greater than 2^32, the IV will be incorrect | ||
467 | // assuming 10s segments, that would be about 1300 years | ||
468 | bytes = videojs.Hls.decrypt(bytes, | ||
469 | segment.key.bytes, | ||
470 | new Uint32Array([ | ||
471 | 0, 0, 0, | ||
472 | mediaIndex + playlist.mediaSequence])); | ||
467 | } | 473 | } |
468 | } | 474 | } |
469 | 475 | ||
... | @@ -483,7 +489,7 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -483,7 +489,7 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
483 | } | 489 | } |
484 | 490 | ||
485 | // transmux the segment data from MP2T to FLV | 491 | // transmux the segment data from MP2T to FLV |
486 | this.segmentParser_.parseSegmentBinaryData(new Uint8Array(bytes)); | 492 | this.segmentParser_.parseSegmentBinaryData(bytes); |
487 | this.segmentParser_.flushTags(); | 493 | this.segmentParser_.flushTags(); |
488 | 494 | ||
489 | tags = []; | 495 | tags = []; |
... | @@ -528,7 +534,7 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -528,7 +534,7 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
528 | }; | 534 | }; |
529 | 535 | ||
530 | videojs.Hls.prototype.fetchKeys = function(playlist, index) { | 536 | videojs.Hls.prototype.fetchKeys = function(playlist, index) { |
531 | var i, key, tech, player, settings; | 537 | var i, key, tech, player, settings, view; |
532 | 538 | ||
533 | // if there is a pending XHR or no segments, don't do anything | 539 | // if there is a pending XHR or no segments, don't do anything |
534 | if (keyXhr || !playlist.segments) { | 540 | if (keyXhr || !playlist.segments) { |
... | @@ -559,7 +565,13 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { | ... | @@ -559,7 +565,13 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) { |
559 | return; | 565 | return; |
560 | } | 566 | } |
561 | 567 | ||
562 | key.bytes = this.response || new Uint8Array([1]); | 568 | view = new DataView(this.response); |
569 | key.bytes = new Uint32Array([ | ||
570 | view.getUint32(0), | ||
571 | view.getUint32(4), | ||
572 | view.getUint32(8), | ||
573 | view.getUint32(12) | ||
574 | ]); | ||
563 | tech.fetchKeys(playlist, i++, url); | 575 | tech.fetchKeys(playlist, i++, url); |
564 | }); | 576 | }); |
565 | break; | 577 | break; | ... | ... |
... | @@ -36,7 +36,7 @@ module('Decryption'); | ... | @@ -36,7 +36,7 @@ module('Decryption'); |
36 | 36 | ||
37 | test('decrypts a single AES-128 with PKCS7 block', function() { | 37 | test('decrypts a single AES-128 with PKCS7 block', function() { |
38 | var | 38 | var |
39 | key = [0, 0, 0, 0], | 39 | key = new Uint32Array([0, 0, 0, 0]), |
40 | initVector = key, | 40 | initVector = key, |
41 | // the string "howdy folks" encrypted | 41 | // the string "howdy folks" encrypted |
42 | encrypted = new Uint8Array([ | 42 | encrypted = new Uint8Array([ |
... | @@ -52,7 +52,7 @@ test('decrypts a single AES-128 with PKCS7 block', function() { | ... | @@ -52,7 +52,7 @@ test('decrypts a single AES-128 with PKCS7 block', function() { |
52 | 52 | ||
53 | test('decrypts multiple AES-128 blocks with CBC', function() { | 53 | test('decrypts multiple AES-128 blocks with CBC', function() { |
54 | var | 54 | var |
55 | key = [0, 0, 0, 0], | 55 | key = new Uint32Array([0, 0, 0, 0]), |
56 | initVector = key, | 56 | initVector = key, |
57 | // the string "0123456789abcdef01234" encrypted | 57 | // the string "0123456789abcdef01234" encrypted |
58 | encrypted = new Uint8Array([ | 58 | encrypted = new Uint8Array([ | ... | ... |
... | @@ -43,10 +43,20 @@ | ... | @@ -43,10 +43,20 @@ |
43 | <section> | 43 | <section> |
44 | <h2>Inputs</h2> | 44 | <h2>Inputs</h2> |
45 | <form id="inputs"> | 45 | <form id="inputs"> |
46 | <label> | 46 | <fieldset> |
47 | Your original MP2T segment: | 47 | <label> |
48 | <input type="file" id="original"> | 48 | Your original MP2T segment: |
49 | </label> | 49 | <input type="file" id="original"> |
50 | </label> | ||
51 | <label> | ||
52 | Key (optional): | ||
53 | <input type="text" id="key"> | ||
54 | </label> | ||
55 | <label> | ||
56 | IV (optional): | ||
57 | <input type="text" id="iv"> | ||
58 | </label> | ||
59 | </fieldset> | ||
50 | <label> | 60 | <label> |
51 | A working, FLV version of the underlying stream | 61 | A working, FLV version of the underlying stream |
52 | produced by another tool: | 62 | produced by another tool: |
... | @@ -105,11 +115,15 @@ | ... | @@ -105,11 +115,15 @@ |
105 | <script src="../../src/h264-stream.js"></script> | 115 | <script src="../../src/h264-stream.js"></script> |
106 | <script src="../../src/aac-stream.js"></script> | 116 | <script src="../../src/aac-stream.js"></script> |
107 | <script src="../../src/segment-parser.js"></script> | 117 | <script src="../../src/segment-parser.js"></script> |
118 | <script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | ||
119 | <script src="../../src/decrypter.js"></script> | ||
108 | 120 | ||
109 | <script src="../../src/bin-utils.js"></script> | 121 | <script src="../../src/bin-utils.js"></script> |
110 | <script> | 122 | <script> |
111 | var inputs = document.getElementById('inputs'), | 123 | var inputs = document.getElementById('inputs'), |
112 | original = document.getElementById('original'), | 124 | original = document.getElementById('original'), |
125 | key = document.getElementById('key'), | ||
126 | iv = document.getElementById('iv'), | ||
113 | working = document.getElementById('working'), | 127 | working = document.getElementById('working'), |
114 | 128 | ||
115 | vjsTags = document.querySelector('.vjs-tags'), | 129 | vjsTags = document.querySelector('.vjs-tags'), |
... | @@ -132,6 +146,7 @@ | ... | @@ -132,6 +146,7 @@ |
132 | var parser = new videojs.Hls.SegmentParser(), | 146 | var parser = new videojs.Hls.SegmentParser(), |
133 | tags = [parser.getFlvHeader()], | 147 | tags = [parser.getFlvHeader()], |
134 | tag, | 148 | tag, |
149 | bytes, | ||
135 | hex, | 150 | hex, |
136 | li, | 151 | li, |
137 | byteLength = 0, | 152 | byteLength = 0, |
... | @@ -142,7 +157,20 @@ | ... | @@ -142,7 +157,20 @@ |
142 | // clear old tag info | 157 | // clear old tag info |
143 | vjsTags.innerHTML = ''; | 158 | vjsTags.innerHTML = ''; |
144 | 159 | ||
145 | parser.parseSegmentBinaryData(new Uint8Array(reader.result)); | 160 | // optionally, decrypt the segment |
161 | if (key.value && iv.value) { | ||
162 | bytes = videojs.Hls.decrypt(new Uint8Array(reader.result), | ||
163 | key.value.match(/([0-9a-f]{8})/gi).map(function(e) { | ||
164 | return parseInt(e, 16); | ||
165 | }), | ||
166 | iv.value.match(/([0-9a-f]{8})/gi).map(function(e) { | ||
167 | return parseInt(e, 16); | ||
168 | })); | ||
169 | } else { | ||
170 | bytes = new Uint8Array(reader.result); | ||
171 | } | ||
172 | |||
173 | parser.parseSegmentBinaryData(bytes); | ||
146 | 174 | ||
147 | // collect all the tags | 175 | // collect all the tags |
148 | while (parser.tagsAvailable()) { | 176 | while (parser.tagsAvailable()) { | ... | ... |
... | @@ -29,6 +29,7 @@ var | ... | @@ -29,6 +29,7 @@ var |
29 | oldSourceBuffer, | 29 | oldSourceBuffer, |
30 | oldFlashSupported, | 30 | oldFlashSupported, |
31 | oldNativeHlsSupport, | 31 | oldNativeHlsSupport, |
32 | oldDecrypt, | ||
32 | requests, | 33 | requests, |
33 | xhr, | 34 | xhr, |
34 | 35 | ||
... | @@ -135,6 +136,11 @@ module('HLS', { | ... | @@ -135,6 +136,11 @@ module('HLS', { |
135 | 136 | ||
136 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; | 137 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; |
137 | 138 | ||
139 | oldDecrypt = videojs.Hls.decrypt; | ||
140 | videojs.Hls.decrypt = function() { | ||
141 | return new Uint8Array([0]); | ||
142 | }; | ||
143 | |||
138 | // fake XHRs | 144 | // fake XHRs |
139 | xhr = sinon.useFakeXMLHttpRequest(); | 145 | xhr = sinon.useFakeXMLHttpRequest(); |
140 | requests = []; | 146 | requests = []; |
... | @@ -152,6 +158,7 @@ module('HLS', { | ... | @@ -152,6 +158,7 @@ module('HLS', { |
152 | videojs.MediaSource.open = oldMediaSourceOpen; | 158 | videojs.MediaSource.open = oldMediaSourceOpen; |
153 | videojs.Hls.SegmentParser = oldSegmentParser; | 159 | videojs.Hls.SegmentParser = oldSegmentParser; |
154 | videojs.Hls.supportsNativeHls = oldNativeHlsSupport; | 160 | videojs.Hls.supportsNativeHls = oldNativeHlsSupport; |
161 | videojs.Hls.decrypt = oldDecrypt; | ||
155 | videojs.SourceBuffer = oldSourceBuffer; | 162 | videojs.SourceBuffer = oldSourceBuffer; |
156 | window.setTimeout = oldSetTimeout; | 163 | window.setTimeout = oldSetTimeout; |
157 | xhr.restore(); | 164 | xhr.restore(); |
... | @@ -1334,11 +1341,15 @@ test('a new keys XHR is created when a previous key XHR finishes', function() { | ... | @@ -1334,11 +1341,15 @@ test('a new keys XHR is created when a previous key XHR finishes', function() { |
1334 | }] | 1341 | }] |
1335 | }; | 1342 | }; |
1336 | }; | 1343 | }; |
1344 | // we're inject the media playlist, so drop the request | ||
1345 | requests.shift(); | ||
1337 | 1346 | ||
1338 | player.hls.playlists.trigger('loadedplaylist'); | 1347 | player.hls.playlists.trigger('loadedplaylist'); |
1339 | requests.pop().respond(200, new Uint8Array([1]).buffer); | 1348 | // key response |
1340 | strictEqual(requests.length, 2, 'a key XHR is created'); | 1349 | requests[0].response = new Uint32Array([0, 0, 0, 0]).buffer; |
1341 | strictEqual(requests[1].url, player.hls.playlists.media().segments[1].key.uri, 'a key XHR is created with the correct uri'); | 1350 | requests.shift().respond(200, null, ''); |
1351 | strictEqual(requests.length, 1, 'a key XHR is created'); | ||
1352 | strictEqual(requests[0].url, player.hls.playlists.media().segments[1].key.uri, 'a key XHR is created with the correct uri'); | ||
1342 | 1353 | ||
1343 | player.hls.playlists.media = oldMedia; | 1354 | player.hls.playlists.media = oldMedia; |
1344 | }); | 1355 | }); |
... | @@ -1527,7 +1538,9 @@ test('skip segments if key requests fail more than once', function() { | ... | @@ -1527,7 +1538,9 @@ test('skip segments if key requests fail more than once', function() { |
1527 | requests.pop().respond(404); | 1538 | requests.pop().respond(404); |
1528 | 1539 | ||
1529 | // key for second segment | 1540 | // key for second segment |
1530 | standardXHRResponse(requests.pop()); | 1541 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; |
1542 | requests[0].respond(200, null, ''); | ||
1543 | requests.shift(); | ||
1531 | 1544 | ||
1532 | equal(bytes.length, 1, 'bytes from the ts segments should not be added'); | 1545 | equal(bytes.length, 1, 'bytes from the ts segments should not be added'); |
1533 | 1546 | ||
... | @@ -1543,4 +1556,74 @@ test('skip segments if key requests fail more than once', function() { | ... | @@ -1543,4 +1556,74 @@ test('skip segments if key requests fail more than once', function() { |
1543 | equal(bytes[1], 1, 'the bytes from the second segment are added and not the first'); | 1556 | equal(bytes[1], 1, 'the bytes from the second segment are added and not the first'); |
1544 | }); | 1557 | }); |
1545 | 1558 | ||
1559 | test('the key is supplied to the decrypter in the correct format', function() { | ||
1560 | var keys = []; | ||
1561 | |||
1562 | player.src({ | ||
1563 | src: 'https://example.com/encrypted-media.m3u8', | ||
1564 | type: 'application/vnd.apple.mpegurl' | ||
1565 | }); | ||
1566 | openMediaSource(player); | ||
1567 | |||
1568 | requests.pop().respond(200, null, | ||
1569 | '#EXTM3U\n' + | ||
1570 | '#EXT-X-MEDIA-SEQUENCE:5\n' + | ||
1571 | '#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=52"\n' + | ||
1572 | '#EXTINF:2.833,\n' + | ||
1573 | 'http://media.example.com/fileSequence52-A.ts\n' + | ||
1574 | '#EXTINF:15.0,\n' + | ||
1575 | 'http://media.example.com/fileSequence52-B.ts\n'); | ||
1576 | |||
1577 | |||
1578 | videojs.Hls.decrypt = function(bytes, key) { | ||
1579 | keys.push(key); | ||
1580 | return new Uint8Array([0]); | ||
1581 | }; | ||
1582 | |||
1583 | requests[0].response = new Uint32Array([0,1,2,3]).buffer; | ||
1584 | requests[0].respond(200, null, ''); | ||
1585 | requests.shift(); | ||
1586 | standardXHRResponse(requests.pop()); | ||
1587 | |||
1588 | equal(keys.length, 1, 'only one call to decrypt was made'); | ||
1589 | deepEqual(keys[0], | ||
1590 | new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]), | ||
1591 | 'passed the specified segment key'); | ||
1592 | |||
1593 | }); | ||
1594 | test('supplies the media sequence of current segment as the IV by default, if no IV is specified', function() { | ||
1595 | var ivs = []; | ||
1596 | |||
1597 | player.src({ | ||
1598 | src: 'https://example.com/encrypted-media.m3u8', | ||
1599 | type: 'application/vnd.apple.mpegurl' | ||
1600 | }); | ||
1601 | openMediaSource(player); | ||
1602 | |||
1603 | requests.pop().respond(200, null, | ||
1604 | '#EXTM3U\n' + | ||
1605 | '#EXT-X-MEDIA-SEQUENCE:5\n' + | ||
1606 | '#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=52"\n' + | ||
1607 | '#EXTINF:2.833,\n' + | ||
1608 | 'http://media.example.com/fileSequence52-A.ts\n' + | ||
1609 | '#EXTINF:15.0,\n' + | ||
1610 | 'http://media.example.com/fileSequence52-B.ts\n'); | ||
1611 | |||
1612 | |||
1613 | videojs.Hls.decrypt = function(bytes, key, iv) { | ||
1614 | ivs.push(iv); | ||
1615 | return new Uint8Array([0]); | ||
1616 | }; | ||
1617 | |||
1618 | requests[0].response = new Uint32Array([0,0,0,0]).buffer; | ||
1619 | requests[0].respond(200, null, ''); | ||
1620 | requests.shift(); | ||
1621 | standardXHRResponse(requests.pop()); | ||
1622 | |||
1623 | equal(ivs.length, 1, 'only one call to decrypt was made'); | ||
1624 | deepEqual(ivs[0], | ||
1625 | new Uint32Array([0, 0, 0, 5]), | ||
1626 | 'the IV for the segment is the media sequence'); | ||
1627 | }); | ||
1628 | |||
1546 | })(window, window.videojs); | 1629 | })(window, window.videojs); | ... | ... |
-
Please register or sign in to post a comment