Bump @justinribeiro/lite-youtube from 0.9.0 to 0.9.1 in /build/javascript (#273)

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript (#265)

* Trying a new github workflow to install javascript packages

* Bump tailwindcss from 1.9.2 to 1.9.4 in /build/javascript (#266)

Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 1.9.2 to 1.9.4.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v1.9.2...v1.9.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript

Bumps [preact](https://github.com/preactjs/preact) from 10.5.4 to 10.5.5.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.5.4...10.5.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Owncast <owncast@owncast.online>

* Bump @justinribeiro/lite-youtube in /build/javascript

Bumps [@justinribeiro/lite-youtube](https://github.com/justinribeiro/lite-youtube) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/justinribeiro/lite-youtube/releases)
- [Commits](https://github.com/justinribeiro/lite-youtube/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
dependabot[bot]
2020-10-20 15:15:56 -07:00
committed by GitHub
parent fb4a822cd8
commit dab7914eab
6133 changed files with 546543 additions and 1108 deletions

View File

@@ -0,0 +1,138 @@
import QUnit from 'qunit';
import {
bytesToString,
stringToBytes,
toUint8,
concatTypedArrays
} from '../src/byte-helpers.js';
import window from 'global/window';
const arrayNames = [];
[
'Array',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array'
].forEach(function(name) {
if (window[name]) {
arrayNames.push(name);
}
});
QUnit.module('bytesToString');
const testString = 'hello竜';
const testBytes = [
// h
0x68,
// e
0x65,
// l
0x6c,
// l
0x6c,
// o
0x6f,
// 竜
0xe7, 0xab, 0x9c
];
const rawBytes = [0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xb0, 0x0d, 0x00, 0x01];
QUnit.test('should function as expected', function(assert) {
arrayNames.forEach(function(name) {
const testObj = name === 'Array' ? testBytes : new window[name](testBytes);
assert.equal(bytesToString(testObj), testString, `testString work as a string arg with ${name}`);
assert.equal(bytesToString(new window[name]()), '', `empty ${name} returns empty string`);
});
assert.equal(bytesToString(), '', 'undefined returns empty string');
assert.equal(bytesToString(null), '', 'null returns empty string');
assert.equal(bytesToString(stringToBytes(testString)), testString, 'stringToBytes -> bytesToString works');
});
QUnit.module('stringToBytes');
QUnit.test('should function as expected', function(assert) {
assert.deepEqual(stringToBytes(testString), testBytes, 'returns an array of bytes');
assert.deepEqual(stringToBytes(), [], 'empty array for undefined');
assert.deepEqual(stringToBytes(null), [], 'empty array for null');
assert.deepEqual(stringToBytes(''), [], 'empty array for empty string');
assert.deepEqual(stringToBytes(10), [0x31, 0x30], 'converts numbers to strings');
assert.deepEqual(stringToBytes(bytesToString(testBytes)), testBytes, 'bytesToString -> stringToBytes works');
assert.deepEqual(stringToBytes(bytesToString(rawBytes), true), rawBytes, 'equal to original with raw bytes mode');
assert.notDeepEqual(stringToBytes(bytesToString(rawBytes)), rawBytes, 'without raw byte mode works, not equal');
});
QUnit.module('toUint8');
QUnit.test('should function as expected', function(assert) {
const undef = toUint8();
assert.ok(undef instanceof Uint8Array && undef.length === 0, 'undef is a blank Uint8Array');
const nul = toUint8(null);
assert.ok(nul instanceof Uint8Array && nul.length === 0, 'undef is a blank Uint8Array');
arrayNames.forEach(function(name) {
const testObj = name === 'Array' ? testBytes : new window[name](testBytes);
const uint = toUint8(testObj);
assert.ok(uint instanceof Uint8Array && uint.length > 0, `converted ${name} to Uint8Array`);
});
});
QUnit.module('concatTypedArrays');
QUnit.test('should function as expected', function(assert) {
const tests = {
undef: {
data: concatTypedArrays(),
expected: toUint8([])
},
empty: {
data: concatTypedArrays(toUint8([])),
expected: toUint8([])
},
single: {
data: concatTypedArrays([0x01]),
expected: toUint8([0x01])
},
array: {
data: concatTypedArrays([0x01], [0x02]),
expected: toUint8([0x01, 0x02])
},
uint: {
data: concatTypedArrays(toUint8([0x01]), toUint8([0x02])),
expected: toUint8([0x01, 0x02])
},
buffer: {
data: concatTypedArrays(toUint8([0x01]).buffer, toUint8([0x02]).buffer),
expected: toUint8([0x01, 0x02])
},
manyarray: {
data: concatTypedArrays([0x01], [0x02], [0x03], [0x04]),
expected: toUint8([0x01, 0x02, 0x03, 0x04])
},
manyuint: {
data: concatTypedArrays(toUint8([0x01]), toUint8([0x02]), toUint8([0x03]), toUint8([0x04])),
expected: toUint8([0x01, 0x02, 0x03, 0x04])
}
};
Object.keys(tests).forEach(function(name) {
const {data, expected} = tests[name];
assert.ok(data instanceof Uint8Array, `obj is a Uint8Array for ${name}`);
assert.deepEqual(data, expected, `data is as expected for ${name}`);
});
});

View File

@@ -0,0 +1,398 @@
import window from 'global/window';
import QUnit from 'qunit';
import {
mapLegacyAvcCodecs,
translateLegacyCodecs,
parseCodecs,
codecsFromDefault,
isVideoCodec,
isAudioCodec,
muxerSupportsCodec,
browserSupportsCodec,
getMimeForCodec
} from '../src/codecs';
const supportedMuxerCodecs = [
'mp4a',
'avc1'
];
const unsupportedMuxerCodecs = [
'hvc1',
'ac-3',
'ec-3',
'mp3'
];
QUnit.module('Legacy Codecs');
QUnit.test('maps legacy AVC codecs', function(assert) {
assert.equal(
mapLegacyAvcCodecs('avc1.deadbeef'),
'avc1.deadbeef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef, mp4a.something'),
'avc1.dead.beef, mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef,mp4a.something'),
'avc1.dead.beef,mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something,avc1.dead.beef'),
'mp4a.something,avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something, avc1.dead.beef'),
'mp4a.something, avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.42001e'),
'avc1.42001e',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.4d0020,mp4a.40.2'),
'avc1.4d0020,mp4a.40.2',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2,avc1.4d0020'),
'mp4a.40.2,avc1.4d0020',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.40'),
'mp4a.40.40',
'does nothing for non video codecs'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30'),
'avc1.42001e',
'translates legacy video codec alone'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30, mp4a.40.2'),
'avc1.42001e, mp4a.40.2',
'translates legacy video codec when paired with audio'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2, avc1.66.30'),
'mp4a.40.2, avc1.42001e',
'translates video codec when specified second'
);
});
QUnit.test('translates legacy codecs', function(assert) {
assert.deepEqual(
translateLegacyCodecs(['avc1.66.30', 'avc1.66.30']),
['avc1.42001e', 'avc1.42001e'],
'translates legacy avc1.66.30 codec'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.42C01E']),
['avc1.42C01E', 'avc1.42C01E'],
'does not translate modern codecs'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.66.30']),
['avc1.42C01E', 'avc1.42001e'],
'only translates legacy codecs when mixed'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.4d0020', 'avc1.100.41', 'avc1.77.41',
'avc1.77.32', 'avc1.77.31', 'avc1.77.30',
'avc1.66.30', 'avc1.66.21', 'avc1.42C01e']),
['avc1.4d0020', 'avc1.640029', 'avc1.4d0029',
'avc1.4d0020', 'avc1.4d001f', 'avc1.4d001e',
'avc1.42001e', 'avc1.420015', 'avc1.42C01e'],
'translates a whole bunch'
);
});
QUnit.module('parseCodecs');
QUnit.test('parses video only codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e'),
{video: {type: 'avc1', details: '.42001e'}},
'parsed video only codec string'
);
});
QUnit.test('parses audio only codec string', function(assert) {
assert.deepEqual(
parseCodecs('mp4a.40.2'),
{audio: {type: 'mp4a', details: '.40.2'}},
'parsed audio only codec string'
);
});
QUnit.test('parses video and audio codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e, mp4a.40.2'),
{
video: {type: 'avc1', details: '.42001e'},
audio: {type: 'mp4a', details: '.40.2'}
},
'parsed video and audio codec string'
);
});
QUnit.test('parses video and audio codec with mixed case', function(assert) {
assert.deepEqual(
parseCodecs('AvC1.42001E, Mp4A.40.E'),
{
video: {type: 'AvC1', details: '.42001E'},
audio: {type: 'Mp4A', details: '.40.E'}
},
'parsed video and audio codec string'
);
});
QUnit.module('codecsFromDefault');
QUnit.test('returns falsey when no audio group ID', function(assert) {
assert.notOk(
codecsFromDefault(
{ mediaGroups: { AUDIO: {} } },
'',
),
'returns falsey when no audio group ID'
);
});
QUnit.test('returns falsey when no matching audio group', function(assert) {
assert.notOk(
codecsFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au2'
),
'returned falsey when no matching audio group'
);
});
QUnit.test('returns falsey when no default for audio group', function(assert) {
assert.notOk(
codecsFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
'returned falsey when no default for audio group'
);
});
QUnit.test('returns audio profile for default in audio group', function(assert) {
assert.deepEqual(
codecsFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
{audio: {type: 'mp4a', details: '.40.5'}},
'returned parsed codec audio profile'
);
});
QUnit.module('isVideoCodec');
QUnit.test('works as expected', function(assert) {
[
'av1',
'avc01',
'avc1',
'avc02',
'avc2',
'vp09',
'vp9',
'vp8',
'vp08',
'hvc1',
'hev1',
'theora',
'mp4v'
].forEach(function(codec) {
assert.ok(isVideoCodec(codec), `"${codec}" is seen as a video codec`);
assert.ok(isVideoCodec(` ${codec} `), `" ${codec} " is seen as video codec`);
assert.ok(isVideoCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is seen as video codec`);
});
['invalid', 'foo', 'mp4a', 'opus', 'vorbis'].forEach(function(codec) {
assert.notOk(isVideoCodec(codec), `${codec} is not a video codec`);
});
});
QUnit.module('isAudioCodec');
QUnit.test('works as expected', function(assert) {
[
'mp4a',
'flac',
'vorbis',
'opus',
'ac-3',
'ac-4',
'ec-3',
'alac'
].forEach(function(codec) {
assert.ok(isAudioCodec(codec), `"${codec}" is seen as an audio codec`);
assert.ok(isAudioCodec(` ${codec} `), `" ${codec} " is seen as an audio codec`);
assert.ok(isAudioCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is seen as an audio codec`);
});
['invalid', 'foo', 'bar', 'avc1', 'av1'].forEach(function(codec) {
assert.notOk(isAudioCodec(codec), `${codec} is not an audio codec`);
});
});
QUnit.module('muxerSupportsCodec');
QUnit.test('works as expected', function(assert) {
const validMuxerCodecs = [];
const invalidMuxerCodecs = [];
unsupportedMuxerCodecs.forEach(function(badCodec) {
invalidMuxerCodecs.push(badCodec);
supportedMuxerCodecs.forEach(function(goodCodec) {
invalidMuxerCodecs.push(`${goodCodec}, ${badCodec}`);
});
});
// generate all combinations of valid codecs
supportedMuxerCodecs.forEach(function(codec, i) {
validMuxerCodecs.push(codec);
supportedMuxerCodecs.forEach(function(_codec, z) {
if (z === i) {
return;
}
validMuxerCodecs.push(`${codec}, ${_codec}`);
validMuxerCodecs.push(`${codec},${_codec}`);
});
});
validMuxerCodecs.forEach(function(codec) {
assert.ok(muxerSupportsCodec(codec), `"${codec}" is supported`);
assert.ok(muxerSupportsCodec(` ${codec} `), `" ${codec} " is supported`);
assert.ok(muxerSupportsCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is supported`);
});
invalidMuxerCodecs.forEach(function(codec) {
assert.notOk(muxerSupportsCodec(codec), `${codec} not supported`);
});
});
QUnit.module('browserSupportsCodec', {
beforeEach() {
this.oldMediaSource = window.MediaSource;
},
afterEach() {
window.MediaSource = this.oldMediaSource;
}
});
QUnit.test('works as expected', function(assert) {
window.MediaSource = {isTypeSupported: () => true};
assert.ok(browserSupportsCodec('test'), 'isTypeSupported true, browser does support codec');
window.MediaSource = {isTypeSupported: () => false};
assert.notOk(browserSupportsCodec('test'), 'isTypeSupported false, browser does not support codec');
window.MediaSource = null;
assert.notOk(browserSupportsCodec('test'), 'no MediaSource, browser does not support codec');
window.MediaSource = {isTypeSupported: null};
assert.notOk(browserSupportsCodec('test'), 'no isTypeSupported, browser does not support codec');
});
QUnit.module('getMimeForCodec');
QUnit.test('works as expected', function(assert) {
// mp4
assert.equal(getMimeForCodec('vp9,mp4a'), 'video/mp4;codecs="vp9,mp4a"', 'mp4 video/audio works');
assert.equal(getMimeForCodec('vp9'), 'video/mp4;codecs="vp9"', 'mp4 video works');
assert.equal(getMimeForCodec('mp4a'), 'audio/mp4;codecs="mp4a"', 'mp4 audio works');
// webm
assert.equal(getMimeForCodec('vp8,opus'), 'video/webm;codecs="vp8,opus"', 'webm video/audio works');
assert.equal(getMimeForCodec('vp8'), 'video/webm;codecs="vp8"', 'webm video works');
assert.equal(getMimeForCodec('vorbis'), 'audio/webm;codecs="vorbis"', 'webm audio works');
// ogg
assert.equal(getMimeForCodec('theora,vorbis'), 'video/ogg;codecs="theora,vorbis"', 'ogg video/audio works');
assert.equal(getMimeForCodec('theora'), 'video/ogg;codecs="theora"', 'ogg video works');
// ogg will never be selected for audio only
// mixed
assert.equal(getMimeForCodec('opus'), 'audio/mp4;codecs="opus"', 'mp4 takes priority over everything');
assert.equal(getMimeForCodec('vorbis'), 'audio/webm;codecs="vorbis"', 'webm takes priority over ogg');
assert.equal(getMimeForCodec('foo'), 'video/mp4;codecs="foo"', 'mp4 is the default');
assert.notOk(getMimeForCodec(), 'invalid codec returns undefined');
assert.equal(getMimeForCodec('Mp4A.40.2,AvC1.42001E'), 'video/mp4;codecs="Mp4A.40.2,AvC1.42001E"', 'case is preserved');
});

View File

@@ -0,0 +1,146 @@
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');
});

View File

@@ -0,0 +1,13 @@
import QUnit from 'qunit';
import decodeB64ToUint8Array from '../src/decode-b64-to-uint8-array.js';
QUnit.module('decodeB64ToUint8Array');
// slightly modified version of m3u8 test
// 'parses Widevine #EXT-X-KEY attributes and attaches to manifest'
QUnit.test('can decode', function(assert) {
const b64 = 'AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnNoYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY';
const result = decodeB64ToUint8Array(b64);
assert.deepEqual(result.byteLength, 62, 'decoded');
});

View File

@@ -0,0 +1,171 @@
import QUnit from 'qunit';
import { forEachMediaGroup } from '../src/media-groups';
QUnit.module('forEachMediaGroup');
QUnit.test('runs callback for each audio media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated audio media groups'
);
});
QUnit.test('runs callback for each subtitle media group', function(assert) {
const master = {
mediaGroups: {
SUBTITLES: {
sub1: {
en: { en: 'en' },
es: { es: 'es' }
},
sub2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated subtitles media groups'
);
});
QUnit.test('runs callback for each audio and subtitles media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
SUBTITLES: {
sub1: {
enS: { enS: 'enS' },
esS: { esS: 'esS' }
},
sub2: {
deS: { deS: 'deS' },
frS: { frS: 'frS' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' },
{ enS: 'enS' },
{ esS: 'esS' },
{ deS: 'deS' },
{ frS: 'frS' }
],
'iterated audio and subtitles media groups'
);
});
QUnit.test('does not run callback for non specified media groups', function(assert) {
const master = {
mediaGroups: {
'VIDEO': { v1: { en: { en: 'en' } } },
'CLOSED-CAPTIONS': { cc1: { en: { en: 'en' } } }
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(iteratedMediaGroups, [], 'did not iterate non specified media groups');
});

View File

@@ -0,0 +1,41 @@
import QUnit from 'qunit';
import { simpleTypeFromSourceType } from '../src/media-types';
QUnit.module('simpleTypeFromSourceType');
QUnit.test('simpleTypeFromSourceType converts HLS mime types to hls', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/x-MPegUrl'),
'hls',
'supports application/x-mpegurl'
);
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
'hls',
'supports application/vnd.apple.mpegurl'
);
});
QUnit.test('simpleTypeFromSourceType converts DASH mime type to dash', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplication/dAsh+xMl'),
'dash',
'supports application/dash+xml'
);
});
QUnit.test(
'simpleTypeFromSourceType does not convert non HLS/DASH mime types',
function(assert) {
assert.notOk(simpleTypeFromSourceType('video/mp4'), 'does not support video/mp4');
assert.notOk(simpleTypeFromSourceType('video/x-flv'), 'does not support video/x-flv');
}
);
QUnit.test('simpleTypeFromSourceType converts VHS media type to vhs-json', function(assert) {
assert.equal(
simpleTypeFromSourceType('application/vnd.videojs.vhs+json'),
'vhs-json',
'supports application/vnd.videojs.vhs+json'
);
});

View File

@@ -0,0 +1,28 @@
import QUnit from 'qunit';
import window from 'global/window';
import resolveUrl from '../src/resolve-url';
// A modified subset of tests from https://github.com/tjenkinson/url-toolkit
QUnit.module('URL resolver');
QUnit.test('works with a selection of valid urls', function(assert) {
let currentLocation = '';
if (window.location && window.location.protocol) {
currentLocation = window.location.protocol + '//' + window.location.host;
}
assert.equal(
resolveUrl('http://a.com/b/cd/e.m3u8', 'https://example.com/z.ts'),
'https://example.com/z.ts'
);
assert.equal(resolveUrl('http://a.com/b/cd/e.m3u8', 'z.ts'), 'http://a.com/b/cd/z.ts');
assert.equal(resolveUrl('//a.com/b/cd/e.m3u8', 'z.ts'), '//a.com/b/cd/z.ts');
assert.equal(
resolveUrl('/a/b/cd/e.m3u8', 'https://example.com:8080/z.ts'),
'https://example.com:8080/z.ts'
);
assert.equal(resolveUrl('/a/b/cd/e.m3u8', 'z.ts'), currentLocation + '/a/b/cd/z.ts');
assert.equal(resolveUrl('/a/b/cd/e.m3u8', '../../../z.ts'), currentLocation + '/z.ts');
});

View File

@@ -0,0 +1,51 @@
import QUnit from 'qunit';
import Stream from '../src/stream';
QUnit.module('stream', {
beforeEach() {
this.stream = new Stream();
},
afterEach() {
this.stream.dispose();
}
});
QUnit.test('trigger calls listeners', function(assert) {
const args = [];
this.stream.on('test', function(data) {
args.push(data);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args, [1, 2]);
});
QUnit.test('callbacks can remove themselves', function(assert) {
const args1 = [];
const args2 = [];
const args3 = [];
const arg2Fn = (event) => {
args2.push(event);
this.stream.off('test', arg2Fn);
};
this.stream.on('test', (event) => {
args1.push(event);
});
this.stream.on('test', arg2Fn);
this.stream.on('test', (event) => {
args3.push(event);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args1, [1, 2], 'first callback ran all times');
assert.deepEqual(args2, [1], 'second callback removed after first run');
assert.deepEqual(args3, [1, 2], 'third callback ran all times');
});