You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

422 lines
10 KiB

import QUnit from 'qunit';
import sinon from 'sinon';
import TransmuxWorker from 'worker!../src/transmuxer-worker.worker.js';
import {
muxed as muxedSegment,
caption as captionSegment,
oneSecond as oneSecondSegment
} from 'create-test-data!segments';
import {
transmux,
reset,
endTimeline,
enqueueAction,
dequeue,
processAction,
processTransmux,
handleGopInfo_,
handleDone_,
handleData_,
dispose
} from '../src/segment-transmuxer';
// needed for plugin registration
import '../src/videojs-http-streaming';
const noop = () => {};
const createTransmuxer = (isPartial) => {
const transmuxer = new TransmuxWorker();
transmuxer.postMessage({
action: 'init',
options: {
remux: false,
keepOriginalTimestamps: true,
handlePartialData: isPartial
}
});
return transmuxer;
};
const mockTransmuxer = (isPartial) => {
const transmuxer = {
postMessage(event) {},
terminate() {}
};
return transmuxer;
};
QUnit.module('Segment Transmuxer', {
beforeEach(assert) {
this.transmuxer = null;
assert.timeout(5000);
},
afterEach(assert) {
dispose();
if (this.transmuxer) {
this.transmuxer.terminate();
}
}
});
QUnit.test('transmux returns data for full appends', function(assert) {
const done = assert.async();
const dataFn = sinon.spy();
const trackInfoFn = sinon.spy();
const audioTimingFn = sinon.spy();
const videoTimingFn = sinon.spy();
const videoSegmentTimingInfoFn = sinon.spy();
this.transmuxer = createTransmuxer(false);
transmux({
transmuxer: this.transmuxer,
bytes: muxedSegment(),
audioAppendStart: null,
gopsToAlignWith: null,
isPartial: false,
onData: dataFn,
onTrackInfo: trackInfoFn,
onAudioTimingInfo: audioTimingFn,
onVideoTimingInfo: videoTimingFn,
onVideoSegmentTimingInfo: videoSegmentTimingInfoFn,
onId3: noop,
onCaptions: noop,
onDone: () => {
assert.ok(dataFn.callCount, 'got data events');
assert.ok(trackInfoFn.callCount, 'got trackInfo events');
assert.ok(audioTimingFn.callCount, 'got audioTimingInfo events');
assert.ok(videoTimingFn.callCount, 'got videoTimingInfo events');
assert.ok(videoSegmentTimingInfoFn.callCount, 'got videoSegmentTimingInfo events');
done();
}
});
});
QUnit.test('transmux returns captions for full appends', function(assert) {
const done = assert.async();
const dataFn = sinon.spy();
const captionsFn = sinon.spy();
this.transmuxer = createTransmuxer(false);
transmux({
transmuxer: this.transmuxer,
bytes: captionSegment(),
audioAppendStart: null,
gopsToAlignWith: null,
isPartial: false,
onData: dataFn,
onTrackInfo: noop,
onAudioTimingInfo: noop,
onVideoTimingInfo: noop,
onVideoSegmentTimingInfo: noop,
onId3: noop,
onCaptions: captionsFn,
onDone: () => {
assert.ok(dataFn.callCount, 'got data events');
assert.ok(captionsFn.callCount, 'got captions');
done();
}
});
});
QUnit.test('transmux returns data for partial appends', function(assert) {
const done = assert.async();
const dataFn = sinon.spy();
const trackInfoFn = sinon.spy();
const audioTimingFn = sinon.spy();
const videoTimingFn = sinon.spy();
const videoSegmentTimingInfoFn = sinon.spy();
this.transmuxer = createTransmuxer(true);
transmux({
transmuxer: this.transmuxer,
bytes: oneSecondSegment(),
audioAppendStart: null,
gopsToAlignWith: null,
isPartial: true,
onData: () => {
dataFn();
// TODO: parial appends don't current fire this
// assert.ok(videoSegmentTimingInfoFn.callCount, 'got videoSegmentTimingInfoFn event');
assert.ok(trackInfoFn.callCount, 'got trackInfo event');
assert.ok(videoTimingFn.callCount, 'got videoTimingInfo event');
if (audioTimingFn.callCount) {
assert.ok(audioTimingFn.callCount, 'got audioTimingInfo event');
done();
}
},
onTrackInfo: trackInfoFn,
onAudioTimingInfo: audioTimingFn,
onVideoTimingInfo: videoTimingFn,
onVideoSegmentTimingInfo: videoSegmentTimingInfoFn,
onId3: noop,
onCaptions: noop,
// This will be called on partialdone events,
// so don't check here
onDone: noop
});
});
QUnit.test('resets transmuxer on reset()', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.postMessage = sinon.spy();
reset(this.transmuxer);
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{ action: 'reset' },
'called reset on transmuxer'
);
});
QUnit.test('passes endTimeline to transmuxer on endTimeline()', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.postMessage = sinon.spy();
endTimeline(this.transmuxer);
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{ action: 'endTimeline' },
'called endTimeline on transmuxer'
);
});
QUnit.test('passes action to transmuxer on enqueueAction()', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.postMessage = sinon.spy();
enqueueAction('push', this.transmuxer);
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{ action: 'push' },
'called push on transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.callCount,
1,
'only posted one message'
);
});
QUnit.test('dequeues and processes action on dequeue()', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.postMessage = sinon.spy();
assert.deepEqual(this.transmuxer.postMessage.callCount, 0, 'no actions yet');
transmux({
transmuxer: this.transmuxer,
bytes: new Uint8Array(),
audioAppendStart: null,
gopsToAlignWith: null,
isPartial: false,
onData: noop,
onTrackInfo: noop,
onAudioTimingInfo: noop,
onVideoTimingInfo: noop,
onId3: noop,
onCaptions: noop,
onDone: noop
});
enqueueAction('reset', this.transmuxer);
// reset is in the queue instead of being processed
assert.deepEqual(this.transmuxer.postMessage.callCount, 1, 'only one action is processed');
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{ action: 'flush' },
'the transmux() posted `flush` to the transmuxer'
);
dequeue();
assert.deepEqual(this.transmuxer.postMessage.callCount, 2, 'two actions processed');
assert.deepEqual(
this.transmuxer.postMessage.args[1][0],
{ action: 'reset' },
'the reset was posted to the transmuxer'
);
});
QUnit.test('processAction posts a message to the transmuxer', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.postMessage = sinon.spy();
processAction(this.transmuxer, 'fakeaction');
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{ action: 'fakeaction' },
'the action was posted to the transmuxer'
);
});
QUnit.test('processTransmux posts all actions', function(assert) {
this.transmuxer = mockTransmuxer(false);
this.transmuxer.onmessage = sinon.spy();
this.transmuxer.postMessage = sinon.spy();
processTransmux({
transmuxer: this.transmuxer,
bytes: muxedSegment(),
audioAppendStart: [0],
gopsToAlignWith: [0],
isPartial: false,
onData: noop,
onTrackInfo: noop,
onAudioTimingInfo: noop,
onVideoTimingInfo: noop,
onId3: noop,
onCaptions: noop,
onDone: noop
});
assert.deepEqual(
this.transmuxer.postMessage.args[0][0],
{
action: 'setAudioAppendStart',
appendStart: [0]
},
'sends audio append start data to transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.args[1][0],
{
action: 'alignGopsWith',
gopsToAlignWith: [0]
},
'sends gops to align with to transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.args[2][0].action,
'push',
'pushed data to transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.args[2][0].byteOffset,
0,
'pushed byteOffset to transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.args[2][0].byteLength,
muxedSegment().length,
'pushed byteLength to transmuxer'
);
assert.deepEqual(
this.transmuxer.postMessage.args[3][0],
{ action: 'flush' },
'calls flush on the transmuxer'
);
});
QUnit.test('handleGopInfo_ attaches gopInfo from an event to the transmuxedData', function(assert) {
const transmuxedData = {};
handleGopInfo_(
{
data: {
gopInfo: [{ a: 1 }]
}
},
transmuxedData
);
assert.deepEqual(
transmuxedData,
{
gopInfo: [{ a: 1 }]
},
'gopInfo is attached to transmuxed data'
);
});
QUnit.test('handleDone_ modifies transmuxedData and passes it to the callback', function(assert) {
const callback = sinon.spy();
handleDone_({
transmuxedData: { a: 1 },
callback
});
assert.deepEqual(callback.callCount, 1, 'called the callback');
assert.deepEqual(
callback.args[0][0],
{
a: 1,
buffer: []
},
'passes transmuxedData to callback'
);
});
QUnit.test(
'handleData_ passes initSegment and segment data to callback',
function(assert) {
const callback = sinon.spy();
const event = {
data: {
segment: {
type: 'video',
initSegment: {
data: [],
byteOffset: 0,
byteLength: 0
},
boxes: {
data: [],
byteOffset: 0,
byteLength: 0
},
captions: [{
text: 'a',
startTime: 1,
endTime: 2
}],
captionStreams: {
CC1: true
},
metadata: [{
cueTime: 1,
frames: [{
data: 'example'
}]
}],
videoFrameDtsTime: 1
}
}
};
const transmuxedData = {
isPartial: false,
buffer: []
};
handleData_(event, transmuxedData, callback);
assert.deepEqual(
transmuxedData,
{
buffer: [{
captions: event.data.segment.captions,
captionStreams: event.data.segment.captionStreams,
metadata: event.data.segment.metadata
}],
isPartial: false
},
'captions and metadata are added to transmuxedData buffer'
);
assert.deepEqual(callback.callCount, 1, 'callback ran');
assert.deepEqual(
callback.args[0][0],
{
type: 'video',
// cast ArrayBuffer to TypedArray
data: new Uint8Array(new ArrayBuffer(0), 0, 0),
initSegment: new Uint8Array(new ArrayBuffer(0), 0, 0),
videoFrameDtsTime: 1
},
'callback passed the bytes for the segment and initSegment'
);
}
);