123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- import QUnit from 'qunit';
- import {mediaSegmentRequest, REQUEST_ERRORS} from '../src/media-segment-request';
- import xhrFactory from '../src/xhr';
- import {useFakeEnvironment} from './test-helpers';
- import Decrypter from '../src/decrypter-worker';
- import worker from 'webwackify';
- const resolveDecrypterWorker = () => {
- let result;
- try {
- result = require.resolve('../src/decrypter-worker');
- } catch (e) {
- // no result
- }
- return result;
- };
- QUnit.module('Media Segment Request', {
- beforeEach(assert) {
- this.env = useFakeEnvironment(assert);
- this.clock = this.env.clock;
- this.requests = this.env.requests;
- this.xhr = xhrFactory();
- this.realDecrypter = worker(Decrypter, resolveDecrypterWorker());
- this.mockDecrypter = {
- listeners: [],
- postMessage(message) {
- const newMessage = Object.create(message);
- newMessage.decrypted = message.encrypted;
- this.listeners.forEach((fn)=>fn({
- data: newMessage
- }));
- },
- addEventListener(event, listener) {
- this.listeners.push(listener);
- },
- removeEventListener(event, listener) {
- this.listeners = this.listeners.filter((fn)=>fn !== listener);
- }
- };
- this.xhrOptions = {
- timeout: 1000
- };
- this.noop = () => {};
- },
- afterEach(assert) {
- this.realDecrypter.terminate();
- this.env.restore();
- }
- });
- QUnit.test('cancels outstanding segment request on abort', function(assert) {
- const done = assert.async();
- assert.expect(7);
- const abort = mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.noop,
- { resolvedUri: '0-test.ts' },
- this.noop,
- (error, segmentData) => {
- assert.equal(this.requests.length, 1, 'there is only one request');
- assert.equal(this.requests[0].uri, '0-test.ts', 'the request is for a segment');
- assert.ok(this.requests[0].aborted, 'aborted the first request');
- assert.ok(error, 'an error object was generated');
- assert.equal(error.code, REQUEST_ERRORS.ABORTED, 'request was aborted');
- done();
- });
- // Simulate Firefox's handling of aborted segments -
- // Firefox sets the response to an empty array buffer if the xhr type is 'arraybuffer'
- // and no data was received
- this.requests[0].response = new ArrayBuffer();
- abort();
- });
- QUnit.test('cancels outstanding key requests on abort', function(assert) {
- let keyReq;
- const done = assert.async();
- assert.expect(7);
- const abort = mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.noop,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php'
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.ok(keyReq.aborted, 'aborted the key request');
- assert.equal(error.code, REQUEST_ERRORS.ABORTED, 'key request was aborted');
- done();
- });
- assert.equal(this.requests.length, 2, 'there are two requests');
- keyReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
- // Fulfill the segment request
- segmentReq.response = new Uint8Array(10).buffer;
- segmentReq.respond(200, null, '');
- abort();
- });
- QUnit.test('cancels outstanding key requests on failure', function(assert) {
- let keyReq;
- const done = assert.async();
- assert.expect(7);
- mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.noop,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php'
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.ok(keyReq.aborted, 'aborted the key request');
- assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'segment request failed');
- done();
- });
- assert.equal(this.requests.length, 2, 'there are two requests');
- keyReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
- // Fulfill the segment request
- segmentReq.respond(500, null, '');
- });
- QUnit.test('cancels outstanding key requests on timeout', function(assert) {
- let keyReq;
- const done = assert.async();
- assert.expect(7);
- mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.noop,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php'
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.ok(keyReq.aborted, 'aborted the key request');
- assert.equal(error.code, REQUEST_ERRORS.TIMEOUT, 'key request failed');
- done();
- });
- assert.equal(this.requests.length, 2, 'there are two requests');
- keyReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
- // Timeout request
- this.clock.tick(2000);
- });
- QUnit.test('the key response is converted to the correct format', function(assert) {
- let keyReq;
- const done = assert.async();
- const postMessage = this.mockDecrypter.postMessage;
- assert.expect(9);
- this.mockDecrypter.postMessage = (message) => {
- const key = new Uint32Array(message.key.bytes,
- message.key.byteOffset,
- message.key.byteLength / 4);
- assert.deepEqual(key,
- new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]),
- 'passed the specified segment key');
- postMessage.call(this.mockDecrypter, message);
- };
- mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.mockDecrypter,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php',
- IV: [0, 0, 0, 1]
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.notOk(error, 'there are no errors');
- assert.equal(this.mockDecrypter.listeners.length,
- 0,
- 'all decryption webworker listeners are unbound');
- // verify stats
- assert.equal(segmentData.stats.bytesReceived, 10, '10 bytes');
- done();
- });
- assert.equal(this.requests.length, 2, 'there are two requests');
- keyReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
- segmentReq.response = new Uint8Array(10).buffer;
- segmentReq.respond(200, null, '');
- keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
- keyReq.respond(200, null, '');
- });
- QUnit.test('segment with key has bytes decrypted', function(assert) {
- const done = assert.async();
- assert.expect(8);
- mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.realDecrypter,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php',
- iv: {
- bytes: new Uint32Array([0, 0, 0, 1])
- }
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.notOk(error, 'there are no errors');
- assert.ok(segmentData.bytes, 'decrypted bytes in segment');
- // verify stats
- assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes');
- done();
- });
- assert.equal(this.requests.length, 2, 'there are two requests');
- const keyReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(segmentReq.uri, '0-test.ts', 'the second request is for a segment');
- segmentReq.response = new Uint8Array(8).buffer;
- segmentReq.respond(200, null, '');
- keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
- keyReq.respond(200, null, '');
- // Allow the decrypter to decrypt
- this.clock.tick(100);
- });
- QUnit.test('waits for every request to finish before the callback is run',
- function(assert) {
- const done = assert.async();
- assert.expect(10);
- mediaSegmentRequest(
- this.xhr,
- this.xhrOptions,
- this.realDecrypter,
- {
- resolvedUri: '0-test.ts',
- key: {
- resolvedUri: '0-key.php',
- iv: {
- bytes: new Uint32Array([0, 0, 0, 1])
- }
- },
- map: {
- resolvedUri: '0-init.dat'
- }
- },
- this.noop,
- (error, segmentData) => {
- assert.notOk(error, 'there are no errors');
- assert.ok(segmentData.bytes, 'decrypted bytes in segment');
- assert.ok(segmentData.map.bytes, 'init segment bytes in map');
- // verify stats
- assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes');
- done();
- });
- assert.equal(this.requests.length, 3, 'there are three requests');
- const keyReq = this.requests.shift();
- const initReq = this.requests.shift();
- const segmentReq = this.requests.shift();
- assert.equal(keyReq.uri, '0-key.php', 'the first request is for a key');
- assert.equal(initReq.uri, '0-init.dat', 'the second request is for the init segment');
- assert.equal(segmentReq.uri, '0-test.ts', 'the third request is for a segment');
- segmentReq.response = new Uint8Array(8).buffer;
- segmentReq.respond(200, null, '');
- this.clock.tick(200);
- initReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
- initReq.respond(200, null, '');
- this.clock.tick(200);
- keyReq.response = new Uint32Array([0, 1, 2, 3]).buffer;
- keyReq.respond(200, null, '');
- // Allow the decrypter to decrypt
- this.clock.tick(100);
- });
|