codecs.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import window from 'global/window';
  2. import QUnit from 'qunit';
  3. import {
  4. mapLegacyAvcCodecs,
  5. translateLegacyCodecs,
  6. parseCodecs,
  7. codecsFromDefault,
  8. isVideoCodec,
  9. isAudioCodec,
  10. muxerSupportsCodec,
  11. browserSupportsCodec,
  12. getMimeForCodec
  13. } from '../src/codecs';
  14. const supportedMuxerCodecs = [
  15. 'mp4a',
  16. 'avc1'
  17. ];
  18. const unsupportedMuxerCodecs = [
  19. 'hvc1',
  20. 'ac-3',
  21. 'ec-3',
  22. 'mp3'
  23. ];
  24. QUnit.module('Legacy Codecs');
  25. QUnit.test('maps legacy AVC codecs', function(assert) {
  26. assert.equal(
  27. mapLegacyAvcCodecs('avc1.deadbeef'),
  28. 'avc1.deadbeef',
  29. 'does nothing for non legacy pattern'
  30. );
  31. assert.equal(
  32. mapLegacyAvcCodecs('avc1.dead.beef, mp4a.something'),
  33. 'avc1.dead.beef, mp4a.something',
  34. 'does nothing for non legacy pattern'
  35. );
  36. assert.equal(
  37. mapLegacyAvcCodecs('avc1.dead.beef,mp4a.something'),
  38. 'avc1.dead.beef,mp4a.something',
  39. 'does nothing for non legacy pattern'
  40. );
  41. assert.equal(
  42. mapLegacyAvcCodecs('mp4a.something,avc1.dead.beef'),
  43. 'mp4a.something,avc1.dead.beef',
  44. 'does nothing for non legacy pattern'
  45. );
  46. assert.equal(
  47. mapLegacyAvcCodecs('mp4a.something, avc1.dead.beef'),
  48. 'mp4a.something, avc1.dead.beef',
  49. 'does nothing for non legacy pattern'
  50. );
  51. assert.equal(
  52. mapLegacyAvcCodecs('avc1.42001e'),
  53. 'avc1.42001e',
  54. 'does nothing for non legacy pattern'
  55. );
  56. assert.equal(
  57. mapLegacyAvcCodecs('avc1.4d0020,mp4a.40.2'),
  58. 'avc1.4d0020,mp4a.40.2',
  59. 'does nothing for non legacy pattern'
  60. );
  61. assert.equal(
  62. mapLegacyAvcCodecs('mp4a.40.2,avc1.4d0020'),
  63. 'mp4a.40.2,avc1.4d0020',
  64. 'does nothing for non legacy pattern'
  65. );
  66. assert.equal(
  67. mapLegacyAvcCodecs('mp4a.40.40'),
  68. 'mp4a.40.40',
  69. 'does nothing for non video codecs'
  70. );
  71. assert.equal(
  72. mapLegacyAvcCodecs('avc1.66.30'),
  73. 'avc1.42001e',
  74. 'translates legacy video codec alone'
  75. );
  76. assert.equal(
  77. mapLegacyAvcCodecs('avc1.66.30, mp4a.40.2'),
  78. 'avc1.42001e, mp4a.40.2',
  79. 'translates legacy video codec when paired with audio'
  80. );
  81. assert.equal(
  82. mapLegacyAvcCodecs('mp4a.40.2, avc1.66.30'),
  83. 'mp4a.40.2, avc1.42001e',
  84. 'translates video codec when specified second'
  85. );
  86. });
  87. QUnit.test('translates legacy codecs', function(assert) {
  88. assert.deepEqual(
  89. translateLegacyCodecs(['avc1.66.30', 'avc1.66.30']),
  90. ['avc1.42001e', 'avc1.42001e'],
  91. 'translates legacy avc1.66.30 codec'
  92. );
  93. assert.deepEqual(
  94. translateLegacyCodecs(['avc1.42C01E', 'avc1.42C01E']),
  95. ['avc1.42C01E', 'avc1.42C01E'],
  96. 'does not translate modern codecs'
  97. );
  98. assert.deepEqual(
  99. translateLegacyCodecs(['avc1.42C01E', 'avc1.66.30']),
  100. ['avc1.42C01E', 'avc1.42001e'],
  101. 'only translates legacy codecs when mixed'
  102. );
  103. assert.deepEqual(
  104. translateLegacyCodecs(['avc1.4d0020', 'avc1.100.41', 'avc1.77.41',
  105. 'avc1.77.32', 'avc1.77.31', 'avc1.77.30',
  106. 'avc1.66.30', 'avc1.66.21', 'avc1.42C01e']),
  107. ['avc1.4d0020', 'avc1.640029', 'avc1.4d0029',
  108. 'avc1.4d0020', 'avc1.4d001f', 'avc1.4d001e',
  109. 'avc1.42001e', 'avc1.420015', 'avc1.42C01e'],
  110. 'translates a whole bunch'
  111. );
  112. });
  113. QUnit.module('parseCodecs');
  114. QUnit.test('parses text only codec string', function(assert) {
  115. assert.deepEqual(
  116. parseCodecs('stpp.ttml.im1t'),
  117. [{mediaType: 'text', type: 'stpp.ttml.im1t', details: ''}],
  118. 'parsed text only codec string'
  119. );
  120. });
  121. QUnit.test('parses video only codec string', function(assert) {
  122. assert.deepEqual(
  123. parseCodecs('avc1.42001e'),
  124. [{mediaType: 'video', type: 'avc1', details: '.42001e'}],
  125. 'parsed video only codec string'
  126. );
  127. });
  128. QUnit.test('parses audio only codec string', function(assert) {
  129. assert.deepEqual(
  130. parseCodecs('mp4a.40.2'),
  131. [{mediaType: 'audio', type: 'mp4a', details: '.40.2'}],
  132. 'parsed audio only codec string'
  133. );
  134. });
  135. QUnit.test('parses video, audio, and text codec string', function(assert) {
  136. assert.deepEqual(
  137. parseCodecs('avc1.42001e, mp4a.40.2, stpp.ttml.im1t'),
  138. [
  139. {mediaType: 'video', type: 'avc1', details: '.42001e'},
  140. {mediaType: 'audio', type: 'mp4a', details: '.40.2'},
  141. {mediaType: 'text', type: 'stpp.ttml.im1t', details: ''}
  142. ],
  143. 'parsed video, audio, and text codec string'
  144. );
  145. });
  146. QUnit.test('parses video, audio, and text codec with mixed case', function(assert) {
  147. assert.deepEqual(
  148. parseCodecs('AvC1.42001E, Mp4A.40.E, stpp.TTML.im1T'),
  149. [
  150. {mediaType: 'video', type: 'AvC1', details: '.42001E'},
  151. {mediaType: 'audio', type: 'Mp4A', details: '.40.E'},
  152. {mediaType: 'text', type: 'stpp.TTML.im1T', details: ''}
  153. ],
  154. 'parsed video, audio, and text codec string'
  155. );
  156. });
  157. QUnit.test('parses two unknown codec', function(assert) {
  158. assert.deepEqual(
  159. parseCodecs('fake.codec, other-fake'),
  160. [
  161. {mediaType: 'unknown', type: 'fake.codec', details: ''},
  162. {mediaType: 'unknown', type: 'other-fake', details: ''}
  163. ],
  164. 'parsed faked codecs as video/audio'
  165. );
  166. });
  167. QUnit.test('parses an unknown codec with a known audio', function(assert) {
  168. assert.deepEqual(
  169. parseCodecs('fake.codec, mp4a.40.2'),
  170. [
  171. {mediaType: 'unknown', type: 'fake.codec', details: ''},
  172. {mediaType: 'audio', type: 'mp4a', details: '.40.2'}
  173. ],
  174. 'parsed audio and unknwon'
  175. );
  176. });
  177. QUnit.test('parses an unknown codec with a known video', function(assert) {
  178. assert.deepEqual(
  179. parseCodecs('avc1.42001e, other-fake'),
  180. [
  181. {mediaType: 'video', type: 'avc1', details: '.42001e'},
  182. {mediaType: 'unknown', type: 'other-fake', details: ''}
  183. ],
  184. 'parsed video and unknown'
  185. );
  186. });
  187. QUnit.test('parses an unknown codec with a known text', function(assert) {
  188. assert.deepEqual(
  189. parseCodecs('stpp.ttml.im1t, other-fake'),
  190. [
  191. {mediaType: 'text', type: 'stpp.ttml.im1t', details: ''},
  192. {mediaType: 'unknown', type: 'other-fake', details: ''}
  193. ],
  194. 'parsed text and unknown'
  195. );
  196. });
  197. QUnit.test('parses an unknown codec with a known audio/video/text', function(assert) {
  198. assert.deepEqual(
  199. parseCodecs('fake.codec, avc1.42001e, mp4a.40.2, stpp.ttml.im1t'),
  200. [
  201. {mediaType: 'unknown', type: 'fake.codec', details: ''},
  202. {mediaType: 'video', type: 'avc1', details: '.42001e'},
  203. {mediaType: 'audio', type: 'mp4a', details: '.40.2'},
  204. {mediaType: 'text', type: 'stpp.ttml.im1t', details: ''}
  205. ],
  206. 'parsed video/audio/text and unknown codecs'
  207. );
  208. });
  209. QUnit.module('codecsFromDefault');
  210. QUnit.test('returns falsey when no audio group ID', function(assert) {
  211. assert.notOk(
  212. codecsFromDefault(
  213. { mediaGroups: { AUDIO: {} } },
  214. '',
  215. ),
  216. 'returns falsey when no audio group ID'
  217. );
  218. });
  219. QUnit.test('returns falsey when no matching audio group', function(assert) {
  220. assert.notOk(
  221. codecsFromDefault(
  222. {
  223. mediaGroups: {
  224. AUDIO: {
  225. au1: {
  226. en: {
  227. default: false,
  228. playlists: [{
  229. attributes: { CODECS: 'mp4a.40.2' }
  230. }]
  231. },
  232. es: {
  233. default: true,
  234. playlists: [{
  235. attributes: { CODECS: 'mp4a.40.5' }
  236. }]
  237. }
  238. }
  239. }
  240. }
  241. },
  242. 'au2'
  243. ),
  244. 'returned falsey when no matching audio group'
  245. );
  246. });
  247. QUnit.test('returns falsey when no default for audio group', function(assert) {
  248. assert.notOk(
  249. codecsFromDefault(
  250. {
  251. mediaGroups: {
  252. AUDIO: {
  253. au1: {
  254. en: {
  255. default: false,
  256. playlists: [{
  257. attributes: { CODECS: 'mp4a.40.2' }
  258. }]
  259. },
  260. es: {
  261. default: false,
  262. playlists: [{
  263. attributes: { CODECS: 'mp4a.40.5' }
  264. }]
  265. }
  266. }
  267. }
  268. }
  269. },
  270. 'au1'
  271. ),
  272. 'returned falsey when no default for audio group'
  273. );
  274. });
  275. QUnit.test('returns parsed audio codecs for default in audio group', function(assert) {
  276. assert.deepEqual(
  277. codecsFromDefault(
  278. {
  279. mediaGroups: {
  280. AUDIO: {
  281. au1: {
  282. en: {
  283. default: false,
  284. playlists: [{
  285. attributes: { CODECS: 'mp4a.40.2, mp4a.40.20' }
  286. }]
  287. },
  288. es: {
  289. default: true,
  290. playlists: [{
  291. attributes: { CODECS: 'mp4a.40.5, mp4a.40.7' }
  292. }]
  293. }
  294. }
  295. }
  296. }
  297. },
  298. 'au1'
  299. ),
  300. [
  301. {mediaType: 'audio', type: 'mp4a', details: '.40.5'},
  302. {mediaType: 'audio', type: 'mp4a', details: '.40.7'}
  303. ],
  304. 'returned parsed codec audio profile'
  305. );
  306. });
  307. QUnit.module('isVideoCodec');
  308. QUnit.test('works as expected', function(assert) {
  309. [
  310. 'av1',
  311. 'avc01',
  312. 'avc1',
  313. 'avc02',
  314. 'avc2',
  315. 'vp09',
  316. 'vp9',
  317. 'vp8',
  318. 'vp08',
  319. 'hvc1',
  320. 'hev1',
  321. 'theora',
  322. 'mp4v'
  323. ].forEach(function(codec) {
  324. assert.ok(isVideoCodec(codec), `"${codec}" is seen as a video codec`);
  325. assert.ok(isVideoCodec(` ${codec} `), `" ${codec} " is seen as video codec`);
  326. assert.ok(isVideoCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is seen as video codec`);
  327. });
  328. ['invalid', 'foo', 'mp4a', 'opus', 'vorbis'].forEach(function(codec) {
  329. assert.notOk(isVideoCodec(codec), `${codec} is not a video codec`);
  330. });
  331. });
  332. QUnit.module('isAudioCodec');
  333. QUnit.test('works as expected', function(assert) {
  334. [
  335. 'mp4a',
  336. 'flac',
  337. 'vorbis',
  338. 'opus',
  339. 'ac-3',
  340. 'ac-4',
  341. 'ec-3',
  342. 'alac',
  343. 'speex',
  344. 'aac',
  345. 'mp3'
  346. ].forEach(function(codec) {
  347. assert.ok(isAudioCodec(codec), `"${codec}" is seen as an audio codec`);
  348. assert.ok(isAudioCodec(` ${codec} `), `" ${codec} " is seen as an audio codec`);
  349. assert.ok(isAudioCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is seen as an audio codec`);
  350. });
  351. ['invalid', 'foo', 'bar', 'avc1', 'av1'].forEach(function(codec) {
  352. assert.notOk(isAudioCodec(codec), `${codec} is not an audio codec`);
  353. });
  354. });
  355. QUnit.module('muxerSupportsCodec');
  356. QUnit.test('works as expected', function(assert) {
  357. const validMuxerCodecs = [];
  358. const invalidMuxerCodecs = [];
  359. unsupportedMuxerCodecs.forEach(function(badCodec) {
  360. invalidMuxerCodecs.push(badCodec);
  361. supportedMuxerCodecs.forEach(function(goodCodec) {
  362. invalidMuxerCodecs.push(`${goodCodec}, ${badCodec}`);
  363. });
  364. });
  365. // generate all combinations of valid codecs
  366. supportedMuxerCodecs.forEach(function(codec, i) {
  367. validMuxerCodecs.push(codec);
  368. supportedMuxerCodecs.forEach(function(_codec, z) {
  369. if (z === i) {
  370. return;
  371. }
  372. validMuxerCodecs.push(`${codec}, ${_codec}`);
  373. validMuxerCodecs.push(`${codec},${_codec}`);
  374. });
  375. });
  376. validMuxerCodecs.forEach(function(codec) {
  377. assert.ok(muxerSupportsCodec(codec), `"${codec}" is supported`);
  378. assert.ok(muxerSupportsCodec(` ${codec} `), `" ${codec} " is supported`);
  379. assert.ok(muxerSupportsCodec(codec.toUpperCase()), `"${codec.toUpperCase()}" is supported`);
  380. });
  381. invalidMuxerCodecs.forEach(function(codec) {
  382. assert.notOk(muxerSupportsCodec(codec), `${codec} not supported`);
  383. });
  384. });
  385. QUnit.module('browserSupportsCodec', {
  386. beforeEach() {
  387. this.oldMediaSource = window.MediaSource;
  388. },
  389. afterEach() {
  390. window.MediaSource = this.oldMediaSource;
  391. }
  392. });
  393. QUnit.test('works as expected', function(assert) {
  394. window.MediaSource = {isTypeSupported: () => true};
  395. assert.ok(browserSupportsCodec('test'), 'isTypeSupported true, browser does support codec');
  396. window.MediaSource = {isTypeSupported: () => false};
  397. assert.notOk(browserSupportsCodec('test'), 'isTypeSupported false, browser does not support codec');
  398. window.MediaSource = null;
  399. assert.notOk(browserSupportsCodec('test'), 'no MediaSource, browser does not support codec');
  400. window.MediaSource = {isTypeSupported: null};
  401. assert.notOk(browserSupportsCodec('test'), 'no isTypeSupported, browser does not support codec');
  402. });
  403. QUnit.module('getMimeForCodec');
  404. QUnit.test('works as expected', function(assert) {
  405. // mp4
  406. assert.equal(getMimeForCodec('vp9,mp4a'), 'video/mp4;codecs="vp9,mp4a"', 'mp4 video/audio works');
  407. assert.equal(getMimeForCodec('vp9'), 'video/mp4;codecs="vp9"', 'mp4 video works');
  408. assert.equal(getMimeForCodec('mp4a'), 'audio/mp4;codecs="mp4a"', 'mp4 audio works');
  409. // webm
  410. assert.equal(getMimeForCodec('vp8,opus'), 'video/webm;codecs="vp8,opus"', 'webm video/audio works');
  411. assert.equal(getMimeForCodec('vp8'), 'video/webm;codecs="vp8"', 'webm video works');
  412. assert.equal(getMimeForCodec('vorbis'), 'audio/webm;codecs="vorbis"', 'webm audio works');
  413. // ogg
  414. assert.equal(getMimeForCodec('theora,vorbis'), 'video/ogg;codecs="theora,vorbis"', 'ogg video/audio works');
  415. assert.equal(getMimeForCodec('theora'), 'video/ogg;codecs="theora"', 'ogg video works');
  416. // ogg will never be selected for audio only
  417. // mixed
  418. assert.equal(getMimeForCodec('opus'), 'audio/mp4;codecs="opus"', 'mp4 takes priority over everything');
  419. assert.equal(getMimeForCodec('vorbis'), 'audio/webm;codecs="vorbis"', 'webm takes priority over ogg');
  420. assert.equal(getMimeForCodec('foo'), 'video/mp4;codecs="foo"', 'mp4 is the default');
  421. assert.notOk(getMimeForCodec(), 'invalid codec returns undefined');
  422. assert.equal(getMimeForCodec('Mp4A.40.2,AvC1.42001E'), 'video/mp4;codecs="Mp4A.40.2,AvC1.42001E"', 'case is preserved');
  423. assert.equal(getMimeForCodec('stpp.ttml.im1t'), 'application/mp4;codecs="stpp.ttml.im1t"', 'text is parsed');
  424. });