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.
741 lines
16 KiB
741 lines
16 KiB
import QUnit from 'qunit';
|
|
import TransmuxWorker from 'worker!../src/transmuxer-worker.worker.js';
|
|
import {
|
|
mp4Captions as mp4CaptionsSegment,
|
|
muxed as muxedSegment,
|
|
oneSecond as oneSecondSegment,
|
|
caption as captionSegment
|
|
} from 'create-test-data!segments';
|
|
// needed for plugin registration
|
|
import '../src/videojs-http-streaming';
|
|
|
|
const createTransmuxer = (isPartial) => {
|
|
const transmuxer = new TransmuxWorker();
|
|
|
|
transmuxer.postMessage({
|
|
action: 'init',
|
|
options: {
|
|
remux: false,
|
|
keepOriginalTimestamps: true,
|
|
handlePartialData: isPartial
|
|
}
|
|
});
|
|
|
|
return transmuxer;
|
|
};
|
|
|
|
// The final done message from the Transmux worker
|
|
// will have type `transmuxed`
|
|
const isFinalDone = (event) => {
|
|
return event.data.action === 'done' &&
|
|
event.data.type === 'transmuxed';
|
|
};
|
|
|
|
QUnit.module('Transmuxer Worker: Full Transmuxer', {
|
|
beforeEach(assert) {
|
|
assert.timeout(5000);
|
|
},
|
|
afterEach(assert) {
|
|
if (this.transmuxer) {
|
|
this.transmuxer.terminate();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Missing tests as these are not accessible to unit testing
|
|
// - setTimestampOffset
|
|
// - setAudioAppendStart
|
|
// - alignGopsWith
|
|
|
|
QUnit.test('push should result in a trackinfo event', function(assert) {
|
|
const done = assert.async();
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
assert.equal(
|
|
e.data.action,
|
|
'trackinfo',
|
|
'pushing data should get trackinfo as the first event'
|
|
);
|
|
assert.deepEqual(
|
|
e.data.trackInfo,
|
|
{
|
|
hasVideo: true,
|
|
hasAudio: true
|
|
},
|
|
'should have video and audio'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
});
|
|
|
|
QUnit.test('flush should return data from transmuxer', function(assert) {
|
|
const testDone = assert.async();
|
|
const messages = [];
|
|
const handleMessages = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages.map((x) => x.action),
|
|
[
|
|
'trackinfo',
|
|
'gopInfo',
|
|
'videoSegmentTimingInfo',
|
|
'videoTimingInfo',
|
|
'data',
|
|
'audioTimingInfo',
|
|
'data',
|
|
'done',
|
|
'done'
|
|
],
|
|
'the events are received in the expected order'
|
|
);
|
|
assert.ok(
|
|
messages.shift().trackInfo,
|
|
'returns trackInfo with trackinfo event'
|
|
);
|
|
assert.ok(
|
|
messages.shift().gopInfo,
|
|
'returns gopInfo with gopInfo event'
|
|
);
|
|
assert.ok(
|
|
messages.shift().videoSegmentTimingInfo,
|
|
'returns timing information with videoSegmentTimingInfo event'
|
|
);
|
|
assert.ok(
|
|
messages.shift().videoTimingInfo,
|
|
'returns timing information with videoTimingInfo event'
|
|
);
|
|
|
|
const data1 = messages.shift();
|
|
|
|
assert.ok(
|
|
data1.segment.data.byteLength > 0,
|
|
'returns data with the 1st data event'
|
|
);
|
|
assert.ok(
|
|
data1.segment.type,
|
|
'video',
|
|
'returns video data with the 1st data event'
|
|
);
|
|
assert.ok(
|
|
messages.shift().audioTimingInfo,
|
|
'returns timing information with audioTimingInfo event'
|
|
);
|
|
|
|
const data2 = messages.shift();
|
|
|
|
assert.ok(
|
|
data2.segment.data.byteLength > 0,
|
|
'returns data with the 2nd data event'
|
|
);
|
|
assert.ok(
|
|
data2.segment.type,
|
|
'returns audio bytes with the 2nd data event'
|
|
);
|
|
|
|
testDone();
|
|
};
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = handleMessages;
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'flush'
|
|
});
|
|
});
|
|
|
|
QUnit.test('reset will clear transmuxer', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages.map((x) => x.action),
|
|
[
|
|
'trackinfo',
|
|
'done',
|
|
'done'
|
|
],
|
|
'flush after a reset does not return data events'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'reset'
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'flush'
|
|
});
|
|
});
|
|
|
|
QUnit.test('endTimeline will return unflushed data', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (e.data.action !== 'endedtimeline') {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
e.data,
|
|
{
|
|
action: 'endedtimeline',
|
|
type: 'transmuxed'
|
|
},
|
|
'endedtimeline event is received from worker'
|
|
);
|
|
assert.ok(
|
|
messages.filter((x) => x.action === 'data'),
|
|
'data event was returned on endedtimeline'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'endTimeline'
|
|
});
|
|
});
|
|
|
|
QUnit.test('caption events are returned', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages
|
|
.map((x) => x.action)
|
|
.filter((y) => y === 'trackinfo')
|
|
.length,
|
|
25,
|
|
'expected amount of trackinfo events returned'
|
|
);
|
|
|
|
assert.deepEqual(
|
|
messages
|
|
.map((x) => x.action)
|
|
.filter((y) => y !== 'trackinfo'),
|
|
[
|
|
'gopInfo',
|
|
'videoSegmentTimingInfo',
|
|
'videoTimingInfo',
|
|
'data',
|
|
'caption',
|
|
'done',
|
|
'done'
|
|
],
|
|
'events are returned in expected order'
|
|
);
|
|
|
|
assert.deepEqual(
|
|
messages.shift().trackInfo,
|
|
{
|
|
hasVideo: true,
|
|
hasAudio: false
|
|
},
|
|
'trackinfo should have video only'
|
|
);
|
|
assert.ok(
|
|
messages[24].gopInfo,
|
|
'gopInfo event has gopInfo'
|
|
);
|
|
|
|
assert.ok(
|
|
messages[25].videoSegmentTimingInfo,
|
|
'videoSegmentTimingInfo event has timing info'
|
|
);
|
|
assert.ok(
|
|
messages[26].videoTimingInfo,
|
|
'videoTimingInfo event has timing info'
|
|
);
|
|
assert.ok(
|
|
messages[27].segment.data.byteLength > 0,
|
|
'data event returns data'
|
|
);
|
|
assert.deepEqual(
|
|
messages[28].caption,
|
|
{
|
|
text: 'Bip!',
|
|
stream: 'CC1',
|
|
startPts: 157500,
|
|
endPts: 175500,
|
|
startTime: 1.75,
|
|
endTime: 1.95
|
|
},
|
|
'caption event returns expected caption'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: captionSegment()
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'flush'
|
|
});
|
|
});
|
|
|
|
QUnit.test('can parse mp4 captions', function(assert) {
|
|
const done = assert.async();
|
|
const data = mp4CaptionsSegment();
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
const message = e.data;
|
|
|
|
assert.equal(message.action, 'mp4Captions', 'returned mp4Captions event');
|
|
assert.deepEqual(message.captions.length, 2, 'two captions');
|
|
assert.deepEqual(
|
|
new Uint8Array(message.data),
|
|
data,
|
|
'data returned to main thread'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'pushMp4Captions',
|
|
data,
|
|
timescales: 30000,
|
|
trackIds: [1],
|
|
byteLength: data.byteLength,
|
|
byteOffset: 0
|
|
});
|
|
});
|
|
|
|
QUnit.test('returns empty array without mp4 captions', function(assert) {
|
|
const done = assert.async();
|
|
const data = muxedSegment();
|
|
|
|
this.transmuxer = createTransmuxer(false);
|
|
this.transmuxer.onmessage = (e) => {
|
|
const message = e.data;
|
|
|
|
assert.equal(message.action, 'mp4Captions', 'returned mp4Captions event');
|
|
assert.deepEqual(message.captions, [], 'no captions');
|
|
assert.deepEqual(
|
|
new Uint8Array(message.data),
|
|
data,
|
|
'data returned to main thread'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'pushMp4Captions',
|
|
data,
|
|
timescales: 30000,
|
|
trackIds: [1],
|
|
byteLength: data.byteLength,
|
|
byteOffset: 0
|
|
});
|
|
});
|
|
|
|
QUnit.module('Transmuxer Worker: Partial Transmuxer', {
|
|
beforeEach(assert) {
|
|
assert.timeout(5000);
|
|
},
|
|
afterEach(assert) {
|
|
if (this.transmuxer) {
|
|
this.transmuxer.terminate();
|
|
delete this.transmuxer;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Missing tests as these are not accessible to unit testing
|
|
// - setTimestampOffset
|
|
// - setAudioAppendStart
|
|
// - alignGopsWith
|
|
|
|
QUnit.test('push should result in a trackinfo event', function(assert) {
|
|
const done = assert.async();
|
|
|
|
this.transmuxer = createTransmuxer(true);
|
|
this.transmuxer.onmessage = (e) => {
|
|
assert.equal(
|
|
e.data.action,
|
|
'trackinfo',
|
|
'pushing data should get trackinfo as the first event'
|
|
);
|
|
assert.deepEqual(
|
|
e.data.trackInfo,
|
|
{
|
|
hasVideo: true,
|
|
hasAudio: true
|
|
},
|
|
'should have video and audio'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
});
|
|
|
|
QUnit.test('flush should return data from transmuxer', function(assert) {
|
|
const testDone = assert.async();
|
|
const messages = [];
|
|
|
|
const handleMessages = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages.map((x) => x.action),
|
|
[
|
|
'trackinfo',
|
|
'videoTimingInfo',
|
|
'data',
|
|
'data',
|
|
'done',
|
|
'audioTimingInfo',
|
|
'data',
|
|
'audioTimingInfo',
|
|
'done',
|
|
'done'
|
|
],
|
|
'the events are received in the expected order'
|
|
);
|
|
|
|
const trackInfoEvent = messages.shift();
|
|
const videoTimingInfoEvent = messages.shift();
|
|
const data1 = messages.shift();
|
|
const data2 = messages.shift();
|
|
const done1 = messages.shift();
|
|
const audioTimingInfoEvent = messages.shift();
|
|
const data3 = messages.shift();
|
|
const audioTimingInfoEvent2 = messages.shift();
|
|
const done2 = messages.shift();
|
|
|
|
assert.ok(
|
|
trackInfoEvent.trackInfo,
|
|
'returns trackInfo with trackinfo event'
|
|
);
|
|
assert.ok(
|
|
videoTimingInfoEvent.videoTimingInfo,
|
|
'returns timing information with videoTimingInfo event'
|
|
);
|
|
|
|
assert.ok(
|
|
data1.segment.boxes.byteLength > 0,
|
|
'returns data with the 1st data event'
|
|
);
|
|
assert.deepEqual(
|
|
data1.segment.type,
|
|
'video',
|
|
'returns video data with the 1st data event'
|
|
);
|
|
assert.ok(
|
|
data2.segment.boxes.byteLength > 0,
|
|
'returns data with the 2nd data event'
|
|
);
|
|
assert.deepEqual(
|
|
data2.segment.type,
|
|
'video',
|
|
'returns video bytes with the 2nd data event'
|
|
);
|
|
assert.deepEqual(
|
|
done1,
|
|
{
|
|
action: 'done',
|
|
type: 'video'
|
|
},
|
|
'got done event for video data only'
|
|
);
|
|
|
|
assert.ok(
|
|
audioTimingInfoEvent.audioTimingInfo,
|
|
'returns timing information with audioTimingInfo event'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(audioTimingInfoEvent.audioTimingInfo),
|
|
['start'],
|
|
'1st audioTimingInfo only has startTime'
|
|
);
|
|
assert.ok(
|
|
data3.segment.boxes.byteLength > 0,
|
|
'returns data with audio data event'
|
|
);
|
|
assert.deepEqual(
|
|
data3.segment.type,
|
|
'audio',
|
|
'returns audio bytes with the audio data event'
|
|
);
|
|
assert.ok(
|
|
audioTimingInfoEvent2.audioTimingInfo,
|
|
'returns timing information with 2nd audioTimingInfo event'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(audioTimingInfoEvent2.audioTimingInfo),
|
|
['start', 'end'],
|
|
'2nd audioTimingInfo has startTime and endTime'
|
|
);
|
|
assert.deepEqual(
|
|
done2,
|
|
{
|
|
action: 'done',
|
|
type: 'audio'
|
|
},
|
|
'got done event for audio data only'
|
|
);
|
|
|
|
testDone();
|
|
};
|
|
|
|
this.transmuxer = createTransmuxer(true);
|
|
this.transmuxer.onmessage = handleMessages;
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'flush'
|
|
});
|
|
});
|
|
|
|
QUnit.test('reset will clear transmuxer', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
|
|
this.transmuxer = createTransmuxer(true);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages.map((x) => x.action),
|
|
[
|
|
'trackinfo',
|
|
'done',
|
|
// Note: the partial transmuxer differs in behavior
|
|
// with the full transmuxer and will trigger this
|
|
// event even without audio data
|
|
'audioTimingInfo',
|
|
'done',
|
|
'done'
|
|
],
|
|
'flush after a reset does not return data events'
|
|
);
|
|
assert.deepEqual(
|
|
messages.filter((x) => x.action === 'audioTimingInfo')[0],
|
|
{
|
|
action: 'audioTimingInfo',
|
|
audioTimingInfo: {
|
|
start: null,
|
|
end: null
|
|
}
|
|
},
|
|
'gets invalid/reset data for audioTimingInfo after reset'
|
|
);
|
|
assert.deepEqual(
|
|
messages.filter((x) => x.action === 'done'),
|
|
[
|
|
{
|
|
action: 'done',
|
|
type: 'video'
|
|
},
|
|
{
|
|
action: 'done',
|
|
type: 'audio'
|
|
},
|
|
{
|
|
action: 'done',
|
|
type: 'transmuxed'
|
|
}
|
|
],
|
|
'gets audio, video and transmuxed done events separately'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'reset'
|
|
});
|
|
this.transmuxer.postMessage({
|
|
action: 'flush'
|
|
});
|
|
});
|
|
|
|
QUnit.test('endTimeline will return unflushed data', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
|
|
this.transmuxer = createTransmuxer(true);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (e.data.action !== 'endedtimeline') {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
e.data,
|
|
{
|
|
action: 'endedtimeline',
|
|
type: 'transmuxed'
|
|
},
|
|
'endedtimeline event is received from worker'
|
|
);
|
|
assert.ok(
|
|
messages.filter((x) => x.action === 'data'),
|
|
'data event was returned on endedtimeline'
|
|
);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: muxedSegment()
|
|
});
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'endTimeline'
|
|
});
|
|
});
|
|
|
|
QUnit.test('partialFlush', function(assert) {
|
|
const done = assert.async();
|
|
const messages = [];
|
|
const isFinalPartialDone = (e) => {
|
|
return e.data.action === 'partialdone' &&
|
|
e.data.type === 'transmuxed';
|
|
};
|
|
|
|
this.transmuxer = createTransmuxer(true);
|
|
this.transmuxer.onmessage = (e) => {
|
|
messages.push(e.data);
|
|
|
|
if (!isFinalPartialDone(e)) {
|
|
return;
|
|
}
|
|
|
|
assert.deepEqual(
|
|
messages.map((x) => x.action),
|
|
[
|
|
'trackinfo', 'trackinfo', 'trackinfo',
|
|
'trackinfo', 'trackinfo', 'trackinfo',
|
|
'videoTimingInfo',
|
|
'data', 'data', 'data', 'data', 'data', 'data',
|
|
'data', 'data', 'data', 'data', 'data', 'data',
|
|
'data', 'data', 'data', 'data', 'data', 'data',
|
|
'data', 'data', 'data', 'data', 'data', 'data',
|
|
'data', 'data', 'data', 'data',
|
|
'partialdone',
|
|
'audioTimingInfo',
|
|
'data',
|
|
'partialdone',
|
|
'partialdone'
|
|
],
|
|
'the events are received in the expected order'
|
|
);
|
|
|
|
const partialdones = [];
|
|
|
|
messages.forEach(function(m) {
|
|
const expected = {action: m.action};
|
|
|
|
if (m.action === 'trackinfo') {
|
|
expected.trackInfo = {hasAudio: true, hasVideo: true};
|
|
} else if (m.action === 'videoTimingInfo') {
|
|
expected.videoTimingInfo = {end: 2.300911111111111, start: 1.4};
|
|
} else if (m.action === 'audioTimingInfo') {
|
|
expected.audioTimingInfo = {start: 1.4};
|
|
} else if (m.action === 'data') {
|
|
assert.ok(m.segment.boxes.byteLength > 0, 'box has bytes');
|
|
assert.ok(m.segment.initSegment.byteLength > 0, 'init segment has bytes');
|
|
|
|
if (m.segment.type === 'video') {
|
|
assert.ok(m.segment.videoFrameDtsTime > 0, 'has video frame dts time');
|
|
}
|
|
return;
|
|
} else if (m.action === 'partialdone') {
|
|
partialdones.push(m);
|
|
return;
|
|
}
|
|
assert.deepEqual(m, expected, `${m.action} as expected`);
|
|
});
|
|
|
|
assert.deepEqual(partialdones, [
|
|
{action: 'partialdone', type: 'video'},
|
|
{action: 'partialdone', type: 'audio'},
|
|
{action: 'partialdone', type: 'transmuxed'}
|
|
]);
|
|
|
|
done();
|
|
};
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'push',
|
|
data: oneSecondSegment()
|
|
});
|
|
|
|
this.transmuxer.postMessage({
|
|
action: 'partialFlush'
|
|
});
|
|
});
|