mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
147 lines
5.7 KiB
JavaScript
147 lines
5.7 KiB
JavaScript
import QUnit from 'qunit';
|
|
import {detectContainerForBytes, isLikelyFmp4MediaSegment} from '../src/containers.js';
|
|
import {stringToBytes} from '../src/byte-helpers.js';
|
|
|
|
const fillerArray = (size) => Array.apply(null, Array(size)).map(() => 0x00);
|
|
const otherMp4Data = [0x00, 0x00, 0x00, 0x00].concat(stringToBytes('stypiso'));
|
|
const id3Data = []
|
|
// id3 header is 10 bytes without footer
|
|
// 10th byte is length 0x23 or 35 in decimal
|
|
// so a total length of 45
|
|
.concat(stringToBytes('ID3').concat([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23]))
|
|
// add in the id3 content
|
|
.concat(Array.apply(null, Array(35)).map(() => 0x00));
|
|
|
|
const id3DataWithFooter = []
|
|
// id3 header is 20 bytes with footer
|
|
// "we have a footer" is the sixth byte
|
|
// 10th byte is length of 0x23 or 35 in decimal
|
|
// so a total length of 55
|
|
.concat(stringToBytes('ID3').concat([0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x23]))
|
|
// add in the id3 content
|
|
.concat(Array.apply(null, Array(45)).map(() => 0x00));
|
|
|
|
const testData = {
|
|
'webm': [0x1A, 0x45, 0xDf, 0xA3],
|
|
'flac': stringToBytes('fLaC'),
|
|
'ogg': stringToBytes('OggS'),
|
|
'aac': [0xFF, 0xF1],
|
|
'mp3': [0xFF, 0xFB],
|
|
'3gp': [0x00, 0x00, 0x00, 0x00].concat(stringToBytes('ftyp3g')),
|
|
'mp4': [0x00, 0x00, 0x00, 0x00].concat(stringToBytes('ftypiso')),
|
|
'ts': [0x47]
|
|
};
|
|
|
|
QUnit.module('detectContainerForBytes');
|
|
|
|
QUnit.test('should identify known types', function(assert) {
|
|
Object.keys(testData).forEach(function(key) {
|
|
const data = new Uint8Array(testData[key]);
|
|
|
|
assert.equal(detectContainerForBytes(testData[key]), key, `found ${key} with Array`);
|
|
assert.equal(detectContainerForBytes(data.buffer), key, `found ${key} with ArrayBuffer`);
|
|
assert.equal(detectContainerForBytes(data), key, `found ${key} with Uint8Array`);
|
|
});
|
|
|
|
const mp4Bytes = new Uint8Array([0x00, 0x00, 0x00, 0x00].concat(stringToBytes('styp')));
|
|
|
|
assert.equal(detectContainerForBytes(mp4Bytes), 'mp4', 'styp mp4 detected as mp4');
|
|
|
|
// mp3 and aac audio can have id3 data before the
|
|
// signature for the file, so we need to handle that.
|
|
['mp3', 'aac'].forEach(function(type) {
|
|
const dataWithId3 = new Uint8Array([].concat(id3Data).concat(testData[type]));
|
|
const dataWithId3Footer = new Uint8Array([].concat(id3DataWithFooter).concat(testData[type]));
|
|
|
|
const recursiveDataWithId3 = new Uint8Array([]
|
|
.concat(id3Data)
|
|
.concat(id3Data)
|
|
.concat(id3Data)
|
|
.concat(testData[type]));
|
|
const recursiveDataWithId3Footer = new Uint8Array([]
|
|
.concat(id3DataWithFooter)
|
|
.concat(id3DataWithFooter)
|
|
.concat(id3DataWithFooter)
|
|
.concat(testData[type]));
|
|
|
|
const differentId3Sections = new Uint8Array([]
|
|
.concat(id3DataWithFooter)
|
|
.concat(id3Data)
|
|
.concat(id3DataWithFooter)
|
|
.concat(id3Data)
|
|
.concat(testData[type]));
|
|
|
|
assert.equal(detectContainerForBytes(dataWithId3), type, `id3 skipped and ${type} detected`);
|
|
assert.equal(detectContainerForBytes(dataWithId3Footer), type, `id3 + footer skipped and ${type} detected`);
|
|
assert.equal(detectContainerForBytes(recursiveDataWithId3), type, `id3 x3 skipped and ${type} detected`);
|
|
assert.equal(detectContainerForBytes(recursiveDataWithId3Footer), type, `id3 + footer x3 skipped and ${type} detected`);
|
|
assert.equal(detectContainerForBytes(differentId3Sections), type, `id3 with/without footer skipped and ${type} detected`);
|
|
});
|
|
|
|
const notTs = []
|
|
.concat(testData.ts)
|
|
.concat(fillerArray(188));
|
|
const longTs = []
|
|
.concat(testData.ts)
|
|
.concat(fillerArray(187))
|
|
.concat(testData.ts);
|
|
|
|
const unsyncTs = []
|
|
.concat(fillerArray(187))
|
|
.concat(testData.ts)
|
|
.concat(fillerArray(187))
|
|
.concat(testData.ts);
|
|
|
|
const badTs = []
|
|
.concat(fillerArray(188))
|
|
.concat(testData.ts)
|
|
.concat(fillerArray(187))
|
|
.concat(testData.ts);
|
|
|
|
assert.equal(detectContainerForBytes(longTs), 'ts', 'long ts data is detected');
|
|
assert.equal(detectContainerForBytes(unsyncTs), 'ts', 'unsynced ts is detected');
|
|
assert.equal(detectContainerForBytes(badTs), '', 'ts without a sync byte in 188 bytes is not detected');
|
|
assert.equal(detectContainerForBytes(notTs), '', 'ts missing 0x47 at 188 is not ts at all');
|
|
assert.equal(detectContainerForBytes(otherMp4Data), 'mp4', 'fmp4 detected as mp4');
|
|
assert.equal(detectContainerForBytes(new Uint8Array()), '', 'no type');
|
|
assert.equal(detectContainerForBytes(), '', 'no type');
|
|
});
|
|
|
|
const createBox = function(type) {
|
|
const size = 0x20;
|
|
|
|
// size bytes
|
|
return [0x00, 0x00, 0x00, size]
|
|
// box identfier styp
|
|
.concat(stringToBytes(type))
|
|
// filler data for size minus identfier and size bytes
|
|
.concat(fillerArray(size - 8));
|
|
};
|
|
|
|
QUnit.module('isLikelyFmp4MediaSegment');
|
|
QUnit.test('works as expected', function(assert) {
|
|
const fmp4Data = []
|
|
.concat(createBox('styp'))
|
|
.concat(createBox('sidx'))
|
|
.concat(createBox('moof'));
|
|
|
|
const mp4Data = []
|
|
.concat(createBox('ftyp'))
|
|
.concat(createBox('sidx'))
|
|
.concat(createBox('moov'));
|
|
|
|
const fmp4Fake = []
|
|
.concat(createBox('test'))
|
|
.concat(createBox('moof'))
|
|
.concat(createBox('fooo'))
|
|
.concat(createBox('bar'));
|
|
|
|
assert.ok(isLikelyFmp4MediaSegment(fmp4Data), 'fmp4 is recognized as fmp4');
|
|
assert.ok(isLikelyFmp4MediaSegment(fmp4Fake), 'fmp4 with moof and unknown boxes is still fmp4');
|
|
assert.ok(isLikelyFmp4MediaSegment(createBox('moof')), 'moof alone is recognized as fmp4');
|
|
assert.notOk(isLikelyFmp4MediaSegment(mp4Data), 'mp4 is not recognized');
|
|
assert.notOk(isLikelyFmp4MediaSegment([].concat(id3DataWithFooter).concat(testData.mp3)), 'bad data is not recognized');
|
|
assert.notOk(isLikelyFmp4MediaSegment(new Uint8Array()), 'no errors on empty data');
|
|
assert.notOk(isLikelyFmp4MediaSegment(), 'no errors on empty data');
|
|
});
|