media-segment-request.test.js 9.4 KB

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