Skip to content

Commit 721e1bf

Browse files
phloxicgesinger
andauthored
fix: cache aes keys for text tracks (#973) (#1228)
* fix: cache aes keys for text tracks (#973) * Move encryption key caching tests to common loader tests to cover VTT loader Co-authored-by: Garrett Singer <[email protected]>
1 parent 5223427 commit 721e1bf

File tree

3 files changed

+143
-138
lines changed

3 files changed

+143
-138
lines changed

src/vtt-segment-loader.js

+5
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ export default class VTTSegmentLoader extends SegmentLoader {
280280
// maintain functionality between segment loaders
281281
this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
282282

283+
// if this request included a segment key, save that data in the cache
284+
if (simpleSegment.key) {
285+
this.segmentKey(simpleSegment.key, true);
286+
}
287+
283288
this.state = 'APPENDING';
284289

285290
// used for tests

test/loader-common.js

+138-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import {
2424
muxed as muxedSegment,
2525
mp4Video as mp4VideoSegment,
2626
mp4VideoInit as mp4VideoInitSegment,
27-
videoOneSecond as tsVideoSegment
27+
videoOneSecond as tsVideoSegment,
28+
encrypted as encryptedSegment,
29+
encryptionKey
2830
} from 'create-test-data!segments';
2931

3032
/**
@@ -1693,5 +1695,140 @@ export const LoaderCommonFactory = ({
16931695

16941696
assert.notOk(loader.playlist_.syncInfo, 'did not set sync info on new playlist');
16951697
});
1698+
1699+
QUnit.test('segmentKey will cache new encrypted keys with cacheEncryptionKeys true', function(assert) {
1700+
loader.cacheEncryptionKeys_ = true;
1701+
1702+
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1703+
loader.playlist(playlistWithDuration(10, { isEncrypted: true }));
1704+
loader.load();
1705+
this.clock.tick(1);
1706+
1707+
const keyCache = loader.keyCache_;
1708+
const bytes = new Uint32Array([1, 2, 3, 4]);
1709+
1710+
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1711+
1712+
const result = loader.segmentKey({resolvedUri: 'key.php', bytes});
1713+
1714+
assert.deepEqual(result, {resolvedUri: 'key.php'}, 'gets by default');
1715+
loader.segmentKey({resolvedUri: 'key.php', bytes}, true);
1716+
assert.deepEqual(keyCache['key.php'].bytes, bytes, 'key has been cached');
1717+
});
1718+
});
1719+
1720+
QUnit.test('segmentKey will not cache encrypted keys with cacheEncryptionKeys false', function(assert) {
1721+
loader.cacheEncryptionKeys_ = false;
1722+
1723+
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1724+
loader.playlist(playlistWithDuration(10, { isEncrypted: true }));
1725+
loader.load();
1726+
this.clock.tick(1);
1727+
1728+
const keyCache = loader.keyCache_;
1729+
const bytes = new Uint32Array([1, 2, 3, 4]);
1730+
1731+
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1732+
loader.segmentKey({resolvedUri: 'key.php', bytes}, true);
1733+
1734+
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1735+
});
1736+
});
1737+
1738+
QUnit.test('new segment requests will use cached keys', function(assert) {
1739+
loader.cacheEncryptionKeys_ = true;
1740+
1741+
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1742+
return new Promise((resolve, reject) => {
1743+
loader.one('appended', resolve);
1744+
loader.one('error', reject);
1745+
loader.playlist(playlistWithDuration(20, { isEncrypted: true }));
1746+
1747+
// make the keys the same
1748+
loader.playlist_.segments[1].key =
1749+
videojs.mergeOptions({}, loader.playlist_.segments[0].key);
1750+
// give 2nd key an iv
1751+
loader.playlist_.segments[1].key.iv = new Uint32Array([0, 1, 2, 3]);
1752+
1753+
loader.load();
1754+
this.clock.tick(1);
1755+
1756+
assert.strictEqual(this.requests.length, 2, 'one request');
1757+
assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request');
1758+
assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request');
1759+
1760+
// key response
1761+
standardXHRResponse(this.requests.shift(), encryptionKey());
1762+
this.clock.tick(1);
1763+
1764+
// segment
1765+
standardXHRResponse(this.requests.shift(), encryptedSegment());
1766+
this.clock.tick(1);
1767+
1768+
// decryption tick for syncWorker
1769+
this.clock.tick(1);
1770+
1771+
// tick for web worker segment probe
1772+
this.clock.tick(1);
1773+
});
1774+
}).then(() => {
1775+
assert.deepEqual(loader.keyCache_['0-key.php'], {
1776+
resolvedUri: '0-key.php',
1777+
bytes: new Uint32Array([609867320, 2355137646, 2410040447, 480344904])
1778+
}, 'previous key was cached');
1779+
1780+
this.clock.tick(1);
1781+
assert.deepEqual(loader.pendingSegment_.segment.key, {
1782+
resolvedUri: '0-key.php',
1783+
uri: '0-key.php',
1784+
iv: new Uint32Array([0, 1, 2, 3])
1785+
}, 'used cached key for request and own initialization vector');
1786+
1787+
assert.strictEqual(this.requests.length, 1, 'one request');
1788+
assert.strictEqual(this.requests[0].uri, '1.ts', 'only segment request');
1789+
});
1790+
});
1791+
1792+
QUnit.test('new segment request keys every time', function(assert) {
1793+
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1794+
return new Promise((resolve, reject) => {
1795+
loader.one('appended', resolve);
1796+
loader.one('error', reject);
1797+
loader.playlist(playlistWithDuration(20, { isEncrypted: true }));
1798+
1799+
loader.load();
1800+
this.clock.tick(1);
1801+
1802+
assert.strictEqual(this.requests.length, 2, 'one request');
1803+
assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request');
1804+
assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request');
1805+
1806+
// key response
1807+
standardXHRResponse(this.requests.shift(), encryptionKey());
1808+
this.clock.tick(1);
1809+
1810+
// segment
1811+
standardXHRResponse(this.requests.shift(), encryptedSegment());
1812+
this.clock.tick(1);
1813+
1814+
// decryption tick for syncWorker
1815+
this.clock.tick(1);
1816+
1817+
});
1818+
}).then(() => {
1819+
this.clock.tick(1);
1820+
1821+
assert.notOk(loader.keyCache_['0-key.php'], 'not cached');
1822+
1823+
assert.deepEqual(loader.pendingSegment_.segment.key, {
1824+
resolvedUri: '1-key.php',
1825+
uri: '1-key.php'
1826+
}, 'used cached key for request and own initialization vector');
1827+
1828+
assert.strictEqual(this.requests.length, 2, 'two requests');
1829+
assert.strictEqual(this.requests[0].uri, '1-key.php', 'key request');
1830+
assert.strictEqual(this.requests[1].uri, '1.ts', 'segment request');
1831+
});
1832+
});
16961833
});
16971834
};

test/segment-loader.test.js

-137
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ import {
4545
mp4VideoInit as mp4VideoInitSegment,
4646
mp4Audio as mp4AudioSegment,
4747
mp4AudioInit as mp4AudioInitSegment,
48-
encrypted as encryptedSegment,
49-
encryptionKey,
5048
zeroLength as zeroLengthSegment
5149
} from 'create-test-data!segments';
5250
import sinon from 'sinon';
@@ -1433,141 +1431,6 @@ QUnit.module('SegmentLoader', function(hooks) {
14331431
});
14341432
});
14351433

1436-
QUnit.test('segmentKey will cache new encrypted keys with cacheEncryptionKeys true', function(assert) {
1437-
loader.cacheEncryptionKeys_ = true;
1438-
1439-
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1440-
loader.playlist(playlistWithDuration(10, { isEncrypted: true }));
1441-
loader.load();
1442-
this.clock.tick(1);
1443-
1444-
const keyCache = loader.keyCache_;
1445-
const bytes = new Uint32Array([1, 2, 3, 4]);
1446-
1447-
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1448-
1449-
const result = loader.segmentKey({resolvedUri: 'key.php', bytes});
1450-
1451-
assert.deepEqual(result, {resolvedUri: 'key.php'}, 'gets by default');
1452-
loader.segmentKey({resolvedUri: 'key.php', bytes}, true);
1453-
assert.deepEqual(keyCache['key.php'].bytes, bytes, 'key has been cached');
1454-
});
1455-
});
1456-
1457-
QUnit.test('segmentKey will not cache encrypted keys with cacheEncryptionKeys false', function(assert) {
1458-
loader.cacheEncryptionKeys_ = false;
1459-
1460-
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1461-
loader.playlist(playlistWithDuration(10, { isEncrypted: true }));
1462-
loader.load();
1463-
this.clock.tick(1);
1464-
1465-
const keyCache = loader.keyCache_;
1466-
const bytes = new Uint32Array([1, 2, 3, 4]);
1467-
1468-
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1469-
loader.segmentKey({resolvedUri: 'key.php', bytes}, true);
1470-
1471-
assert.strictEqual(Object.keys(keyCache).length, 0, 'no keys have been cached');
1472-
});
1473-
});
1474-
1475-
QUnit.test('new segment requests will use cached keys', function(assert) {
1476-
loader.cacheEncryptionKeys_ = true;
1477-
1478-
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1479-
return new Promise((resolve, reject) => {
1480-
loader.one('appended', resolve);
1481-
loader.one('error', reject);
1482-
loader.playlist(playlistWithDuration(20, { isEncrypted: true }));
1483-
1484-
// make the keys the same
1485-
loader.playlist_.segments[1].key =
1486-
videojs.mergeOptions({}, loader.playlist_.segments[0].key);
1487-
// give 2nd key an iv
1488-
loader.playlist_.segments[1].key.iv = new Uint32Array([0, 1, 2, 3]);
1489-
1490-
loader.load();
1491-
this.clock.tick(1);
1492-
1493-
assert.strictEqual(this.requests.length, 2, 'one request');
1494-
assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request');
1495-
assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request');
1496-
1497-
// key response
1498-
standardXHRResponse(this.requests.shift(), encryptionKey());
1499-
this.clock.tick(1);
1500-
1501-
// segment
1502-
standardXHRResponse(this.requests.shift(), encryptedSegment());
1503-
this.clock.tick(1);
1504-
1505-
// decryption tick for syncWorker
1506-
this.clock.tick(1);
1507-
1508-
// tick for web worker segment probe
1509-
this.clock.tick(1);
1510-
});
1511-
}).then(() => {
1512-
assert.deepEqual(loader.keyCache_['0-key.php'], {
1513-
resolvedUri: '0-key.php',
1514-
bytes: new Uint32Array([609867320, 2355137646, 2410040447, 480344904])
1515-
}, 'previous key was cached');
1516-
1517-
this.clock.tick(1);
1518-
assert.deepEqual(loader.pendingSegment_.segment.key, {
1519-
resolvedUri: '0-key.php',
1520-
uri: '0-key.php',
1521-
iv: new Uint32Array([0, 1, 2, 3])
1522-
}, 'used cached key for request and own initialization vector');
1523-
1524-
assert.strictEqual(this.requests.length, 1, 'one request');
1525-
assert.strictEqual(this.requests[0].uri, '1.ts', 'only segment request');
1526-
});
1527-
});
1528-
1529-
QUnit.test('new segment request keys every time', function(assert) {
1530-
return this.setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
1531-
return new Promise((resolve, reject) => {
1532-
loader.one('appended', resolve);
1533-
loader.one('error', reject);
1534-
loader.playlist(playlistWithDuration(20, { isEncrypted: true }));
1535-
1536-
loader.load();
1537-
this.clock.tick(1);
1538-
1539-
assert.strictEqual(this.requests.length, 2, 'one request');
1540-
assert.strictEqual(this.requests[0].uri, '0-key.php', 'key request');
1541-
assert.strictEqual(this.requests[1].uri, '0.ts', 'segment request');
1542-
1543-
// key response
1544-
standardXHRResponse(this.requests.shift(), encryptionKey());
1545-
this.clock.tick(1);
1546-
1547-
// segment
1548-
standardXHRResponse(this.requests.shift(), encryptedSegment());
1549-
this.clock.tick(1);
1550-
1551-
// decryption tick for syncWorker
1552-
this.clock.tick(1);
1553-
1554-
});
1555-
}).then(() => {
1556-
this.clock.tick(1);
1557-
1558-
assert.notOk(loader.keyCache_['0-key.php'], 'not cached');
1559-
1560-
assert.deepEqual(loader.pendingSegment_.segment.key, {
1561-
resolvedUri: '1-key.php',
1562-
uri: '1-key.php'
1563-
}, 'used cached key for request and own initialization vector');
1564-
1565-
assert.strictEqual(this.requests.length, 2, 'two requests');
1566-
assert.strictEqual(this.requests[0].uri, '1-key.php', 'key request');
1567-
assert.strictEqual(this.requests[1].uri, '1.ts', 'segment request');
1568-
});
1569-
});
1570-
15711434
QUnit.test('triggers syncinfoupdate before attempting a resync', function(assert) {
15721435
let syncInfoUpdates = 0;
15731436

0 commit comments

Comments
 (0)