metadata-stream.test.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. 'use strict';
  2. /*
  3. ======== A Handy Little QUnit Reference ========
  4. http://api.qunitjs.com/
  5. Test methods:
  6. module(name, {[setup][ ,teardown]})
  7. test(name, callback)
  8. expect(numberOfAssertions)
  9. stop(increment)
  10. start(decrement)
  11. Test assertions:
  12. ok(value, [message])
  13. equal(actual, expected, [message])
  14. notEqual(actual, expected, [message])
  15. deepEqual(actual, expected, [message])
  16. notDeepEqual(actual, expected, [message])
  17. strictEqual(actual, expected, [message])
  18. notStrictEqual(actual, expected, [message])
  19. throws(block, [expected], [message])
  20. */
  21. var metadataStream, stringToInts, stringToCString, id3Tag, id3Frame, id3Generator, mp2t, QUnit,
  22. webworkify, MetadataStreamTestWorker;
  23. mp2t = require('../lib/m2ts');
  24. QUnit = require('qunit');
  25. id3Generator = require('./utils/id3-generator');
  26. MetadataStreamTestWorker = require('worker!./metadata-stream-test-worker.js');
  27. stringToInts = id3Generator.stringToInts;
  28. stringToCString = id3Generator.stringToCString;
  29. id3Tag = id3Generator.id3Tag;
  30. id3Frame = id3Generator.id3Frame;
  31. QUnit.module('MetadataStream', {
  32. beforeEach: function() {
  33. metadataStream = new mp2t.MetadataStream();
  34. }
  35. });
  36. QUnit.test('can construct a MetadataStream', function(assert) {
  37. assert.ok(metadataStream, 'does not return null');
  38. });
  39. QUnit.test('triggers log for non-id3/invalid data', function(assert) {
  40. var logs = [];
  41. metadataStream.on('log', function(log) {
  42. logs.push(log);
  43. });
  44. // id3 not long enough
  45. metadataStream.push({type: 'timed-metadata', data: new Uint8Array()});
  46. // invalid data
  47. metadataStream.push({type: 'timed-metadata', data: new Uint8Array([
  48. 0x01, 0x01, 0x01, 0x01, 0x01,
  49. 0x01, 0x01, 0x01, 0x01, 0x01
  50. ])});
  51. const zeroFrames = new Uint8Array(stringToInts('ID3').concat([
  52. 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
  53. 0x40, // flags. include an extended header
  54. 0x00, 0x00, 0x00, 0x00, // size. set later
  55. // extended header
  56. 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
  57. 0x00, 0x00, // extended flags
  58. 0x00, 0x00, 0x00, 0x02 // size of padding
  59. ]));
  60. metadataStream.push({type: 'timed-metadata', data: zeroFrames});
  61. assert.deepEqual(logs, [
  62. {level: 'warn', message: 'Skipping unrecognized metadata packet'},
  63. {level: 'warn', message: 'Skipping unrecognized metadata packet'},
  64. {level: 'warn', message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'}
  65. ], 'logs as expected.');
  66. });
  67. QUnit.test('parses simple ID3 metadata out of PES packets', function(assert) {
  68. var
  69. events = [],
  70. wxxxPayload = [
  71. 0x00 // text encoding. ISO-8859-1
  72. ].concat(stringToCString('ad tag URL'), // description
  73. stringToInts('http://example.com/ad?v=1234&q=7')), // value
  74. id3Bytes,
  75. size;
  76. metadataStream.on('data', function(event) {
  77. events.push(event);
  78. });
  79. id3Bytes = new Uint8Array(stringToInts('ID3').concat([
  80. 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
  81. 0x40, // flags. include an extended header
  82. 0x00, 0x00, 0x00, 0x00, // size. set later
  83. // extended header
  84. 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
  85. 0x00, 0x00, // extended flags
  86. 0x00, 0x00, 0x00, 0x02 // size of padding
  87. // frame 0
  88. // http://id3.org/id3v2.3.0#User_defined_text_information_frame
  89. ], id3Frame('WXXX',
  90. wxxxPayload), // value
  91. // frame 1
  92. // custom tag
  93. id3Frame('XINF',
  94. [
  95. 0x04, 0x03, 0x02, 0x01 // arbitrary data
  96. ]), [
  97. 0x00, 0x00 // padding
  98. ]));
  99. // set header size field
  100. size = id3Bytes.byteLength - 10;
  101. id3Bytes[6] = (size >>> 21) & 0x7f;
  102. id3Bytes[7] = (size >>> 14) & 0x7f;
  103. id3Bytes[8] = (size >>> 7) & 0x7f;
  104. id3Bytes[9] = (size) & 0x7f;
  105. metadataStream.push({
  106. type: 'timed-metadata',
  107. trackId: 7,
  108. pts: 1000,
  109. dts: 1000,
  110. // header
  111. data: id3Bytes
  112. });
  113. assert.equal(events.length, 1, 'parsed one tag');
  114. assert.equal(events[0].frames.length, 2, 'parsed two frames');
  115. assert.equal(events[0].frames[0].key, 'WXXX', 'parsed a WXXX frame');
  116. assert.deepEqual(new Uint8Array(events[0].frames[0].data),
  117. new Uint8Array(wxxxPayload),
  118. 'attached the frame payload');
  119. assert.equal(events[0].frames[1].key, 'XINF', 'parsed a user-defined frame');
  120. assert.deepEqual(new Uint8Array(events[0].frames[1].data),
  121. new Uint8Array([0x04, 0x03, 0x02, 0x01]),
  122. 'attached the frame payload');
  123. assert.equal(events[0].pts, 1000, 'did not modify the PTS');
  124. assert.equal(events[0].dts, 1000, 'did not modify the PTS');
  125. });
  126. QUnit.test('skips non-ID3 metadata events', function(assert) {
  127. var events = [];
  128. metadataStream.on('data', function(event) {
  129. events.push(event);
  130. });
  131. metadataStream.push({
  132. type: 'timed-metadata',
  133. trackId: 7,
  134. pts: 1000,
  135. dts: 1000,
  136. // header
  137. data: new Uint8Array([0])
  138. });
  139. assert.equal(events.length, 0, 'did not emit an event');
  140. });
  141. // missing cases:
  142. // unsynchronization
  143. // CRC
  144. // no extended header
  145. // compressed frames
  146. // encrypted frames
  147. // frame groups
  148. // too large/small tag size values
  149. // too large/small frame size values
  150. QUnit.test('parses TXXX frames without null terminators', function(assert) {
  151. var events = [];
  152. metadataStream.on('data', function(event) {
  153. events.push(event);
  154. });
  155. metadataStream.push({
  156. type: 'timed-metadata',
  157. trackId: 7,
  158. pts: 1000,
  159. dts: 900,
  160. // header
  161. data: new Uint8Array(id3Tag(id3Frame('TXXX',
  162. 0x03, // utf-8
  163. stringToCString('get done'),
  164. stringToInts('{ "key": "value" }')),
  165. [0x00, 0x00]))
  166. });
  167. assert.equal(events.length, 1, 'parsed one tag');
  168. assert.equal(events[0].frames.length, 1, 'parsed one frame');
  169. assert.equal(events[0].frames[0].key, 'TXXX', 'parsed the frame key');
  170. assert.equal(events[0].frames[0].description, 'get done', 'parsed the description');
  171. assert.deepEqual(JSON.parse(events[0].frames[0].data), { key: 'value' }, 'parsed the data');
  172. });
  173. QUnit.test('parses TXXX frames with null terminators', function(assert) {
  174. var events = [];
  175. metadataStream.on('data', function(event) {
  176. events.push(event);
  177. });
  178. metadataStream.push({
  179. type: 'timed-metadata',
  180. trackId: 7,
  181. pts: 1000,
  182. dts: 900,
  183. // header
  184. data: new Uint8Array(id3Tag(id3Frame('TXXX',
  185. 0x03, // utf-8
  186. stringToCString('get done'),
  187. stringToCString('{ "key": "value" }')),
  188. [0x00, 0x00]))
  189. });
  190. assert.equal(events.length, 1, 'parsed one tag');
  191. assert.equal(events[0].frames.length, 1, 'parsed one frame');
  192. assert.equal(events[0].frames[0].key, 'TXXX', 'parsed the frame key');
  193. assert.equal(events[0].frames[0].description, 'get done', 'parsed the description');
  194. assert.deepEqual(JSON.parse(events[0].frames[0].data), { key: 'value' }, 'parsed the data');
  195. });
  196. QUnit.test('parses WXXX frames', function(assert) {
  197. var events = [], url = 'http://example.com/path/file?abc=7&d=4#ty';
  198. metadataStream.on('data', function(event) {
  199. events.push(event);
  200. });
  201. metadataStream.push({
  202. type: 'timed-metadata',
  203. trackId: 7,
  204. pts: 1000,
  205. dts: 900,
  206. // header
  207. data: new Uint8Array(id3Tag(id3Frame('WXXX',
  208. 0x03, // utf-8
  209. stringToCString(''),
  210. stringToInts(url)),
  211. [0x00, 0x00]))
  212. });
  213. assert.equal(events.length, 1, 'parsed one tag');
  214. assert.equal(events[0].frames.length, 1, 'parsed one frame');
  215. assert.equal(events[0].frames[0].key, 'WXXX', 'parsed the frame key');
  216. assert.equal(events[0].frames[0].description, '', 'parsed the description');
  217. assert.equal(events[0].frames[0].url, url, 'parsed the value');
  218. });
  219. QUnit.test('parses TXXX frames with characters that have a single-digit hexadecimal representation', function(assert) {
  220. var events = [], value = String.fromCharCode(7);
  221. metadataStream.on('data', function(event) {
  222. events.push(event);
  223. });
  224. metadataStream.push({
  225. type: 'timed-metadata',
  226. trackId: 7,
  227. pts: 1000,
  228. dts: 900,
  229. // header
  230. data: new Uint8Array(id3Tag(id3Frame('TXXX',
  231. 0x03, // utf-8
  232. stringToCString(''),
  233. stringToCString(value)),
  234. [0x00, 0x00]))
  235. });
  236. assert.equal(events[0].frames[0].data,
  237. value,
  238. 'parsed the single-digit character');
  239. });
  240. QUnit.test('parses PRIV frames', function(assert) {
  241. var
  242. events = [],
  243. payload = stringToInts('arbitrary data may be included in the payload ' +
  244. 'of a PRIV frame');
  245. metadataStream.on('data', function(event) {
  246. events.push(event);
  247. });
  248. metadataStream.push({
  249. type: 'timed-metadata',
  250. trackId: 7,
  251. pts: 1000,
  252. dts: 900,
  253. // header
  254. data: new Uint8Array(id3Tag(id3Frame('PRIV',
  255. stringToCString('priv-owner@example.com'),
  256. payload)))
  257. });
  258. assert.equal(events.length, 1, 'parsed a tag');
  259. assert.equal(events[0].frames.length, 1, 'parsed a frame');
  260. assert.equal(events[0].frames[0].key, 'PRIV', 'frame key is PRIV');
  261. assert.equal(events[0].frames[0].owner, 'priv-owner@example.com', 'parsed the owner');
  262. assert.deepEqual(new Uint8Array(events[0].frames[0].data),
  263. new Uint8Array(payload),
  264. 'parsed the frame private data');
  265. });
  266. QUnit.test('parses tags split across pushes', function(assert) {
  267. var
  268. events = [],
  269. owner = stringToCString('owner@example.com'),
  270. payload = stringToInts('A TS packet is 188 bytes in length so that it can' +
  271. ' be easily transmitted over ATM networks, an ' +
  272. 'important medium at one time. We want to be sure' +
  273. ' that ID3 frames larger than a TS packet are ' +
  274. 'properly re-assembled.'),
  275. tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))),
  276. front = tag.subarray(0, 100),
  277. back = tag.subarray(100);
  278. metadataStream.on('data', function(event) {
  279. events.push(event);
  280. });
  281. metadataStream.push({
  282. type: 'timed-metadata',
  283. trackId: 7,
  284. pts: 1000,
  285. dts: 900,
  286. data: front,
  287. dataAlignmentIndicator: true
  288. });
  289. assert.equal(events.length, 0, 'parsed zero tags');
  290. metadataStream.push({
  291. type: 'timed-metadata',
  292. trackId: 7,
  293. pts: 1000,
  294. dts: 900,
  295. data: back,
  296. dataAlignmentIndicator: false
  297. });
  298. assert.equal(events.length, 1, 'parsed a tag');
  299. assert.equal(events[0].frames.length, 1, 'parsed a frame');
  300. assert.equal(events[0].frames[0].data.byteLength,
  301. payload.length,
  302. 'collected data across pushes');
  303. // parses subsequent fragmented tags
  304. tag = new Uint8Array(id3Tag(id3Frame('PRIV',
  305. owner, payload, payload)));
  306. front = tag.subarray(0, 188);
  307. back = tag.subarray(188);
  308. events = [];
  309. metadataStream.push({
  310. type: 'timed-metadata',
  311. trackId: 7,
  312. pts: 2000,
  313. dts: 2000,
  314. data: front,
  315. dataAlignmentIndicator: true
  316. });
  317. metadataStream.push({
  318. type: 'timed-metadata',
  319. trackId: 7,
  320. pts: 2000,
  321. dts: 2000,
  322. data: back,
  323. dataAlignmentIndicator: false
  324. });
  325. assert.equal(events.length, 1, 'parsed a tag');
  326. assert.equal(events[0].frames.length, 1, 'parsed a frame');
  327. assert.equal(events[0].frames[0].data.byteLength,
  328. 2 * payload.length,
  329. 'collected data across pushes');
  330. });
  331. QUnit.test('id3 frame is malformed first time but gets corrected in the next frame', function(assert) {
  332. var
  333. events = [],
  334. owner = stringToCString('owner@example.com'),
  335. payload = stringToInts('A TS packet is 188 bytes in length so that it can' +
  336. ' be easily transmitted over ATM networks, an ' +
  337. 'important medium at one time. We want to be sure' +
  338. ' that ID3 frames larger than a TS packet are ' +
  339. 'properly re-assembled.'),
  340. tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))),
  341. front = tag.subarray(0, 100);
  342. metadataStream.on('data', function(event) {
  343. events.push(event);
  344. });
  345. // receives incomplete id3
  346. metadataStream.push({
  347. type: 'timed-metadata',
  348. trackId: 7,
  349. pts: 1000,
  350. dts: 900,
  351. data: front,
  352. dataAlignmentIndicator: true
  353. });
  354. assert.equal(events.length, 0, 'parsed zero tags');
  355. // receives complete id3
  356. metadataStream.push({
  357. type: 'timed-metadata',
  358. trackId: 7,
  359. pts: 1000,
  360. dts: 900,
  361. data: tag,
  362. dataAlignmentIndicator: true
  363. });
  364. assert.equal(events.length, 1, 'parsed a tag');
  365. assert.equal(events[0].frames.length, 1, 'parsed a frame');
  366. assert.equal(events[0].frames[0].data.byteLength,
  367. payload.length,
  368. 'collected data across pushes');
  369. });
  370. QUnit.test('id3 frame reports more data than its tagsize ', function(assert) {
  371. var
  372. events = [],
  373. owner = stringToCString('owner@example.com'),
  374. payload = stringToInts('A TS packet is 188 bytes in length so that it can' +
  375. ' be easily transmitted over ATM networks, an ' +
  376. 'important medium at one time. We want to be sure' +
  377. ' that ID3 frames larger than a TS packet are ' +
  378. 'properly re-assembled.'),
  379. tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))),
  380. d = new Uint8Array([0x04, 0x05, 0x06]),
  381. data = new Uint8Array(tag.byteLength + d.byteLength);
  382. data.set(tag);
  383. data.set(d, tag.length);
  384. metadataStream.on('data', function(event) {
  385. events.push(event);
  386. });
  387. metadataStream.push({
  388. type: 'timed-metadata',
  389. trackId: 7,
  390. pts: 1000,
  391. dts: 900,
  392. data: data,
  393. dataAlignmentIndicator: true
  394. });
  395. assert.equal(events.length, 1, 'parsed a tag');
  396. assert.equal(events[0].frames.length, 1, 'parsed a frame');
  397. assert.equal(events[0].frames[0].data.byteLength,
  398. payload.length,
  399. 'collected data across pushes');
  400. });
  401. QUnit.test('ignores tags when the header is fragmented', function(assert) {
  402. var
  403. events = [],
  404. tag = new Uint8Array(id3Tag(id3Frame('PRIV',
  405. stringToCString('owner@example.com'),
  406. stringToInts('payload')))),
  407. // split the 10-byte ID3 tag header in half
  408. front = tag.subarray(0, 5),
  409. back = tag.subarray(5);
  410. metadataStream.on('data', function(event) {
  411. events.push(event);
  412. });
  413. metadataStream.push({
  414. type: 'timed-metadata',
  415. trackId: 7,
  416. pts: 1000,
  417. dts: 900,
  418. data: front
  419. });
  420. metadataStream.push({
  421. type: 'timed-metadata',
  422. trackId: 7,
  423. pts: 1000,
  424. dts: 900,
  425. data: back
  426. });
  427. assert.equal(events.length, 0, 'parsed zero tags');
  428. metadataStream.push({
  429. type: 'timed-metadata',
  430. trackId: 7,
  431. pts: 1500,
  432. dts: 1500,
  433. data: new Uint8Array(id3Tag(id3Frame('PRIV',
  434. stringToCString('owner2'),
  435. stringToInts('payload2'))))
  436. });
  437. assert.equal(events.length, 1, 'parsed one tag');
  438. assert.equal(events[0].frames[0].owner, 'owner2', 'dropped the first tag');
  439. });
  440. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  441. QUnit.test('constructs the dispatch type', function(assert) {
  442. metadataStream = new mp2t.MetadataStream({
  443. descriptor: new Uint8Array([0x03, 0x02, 0x01, 0x00])
  444. });
  445. assert.equal(metadataStream.dispatchType, '1503020100', 'built the dispatch type');
  446. });
  447. QUnit.test('should skip tag frame parsing on malformed frame, preserving previous frames', function(assert) {
  448. var events = [],
  449. validFrame = id3Frame('TIT2',
  450. 0x03, // utf-8
  451. stringToCString('sample title')),
  452. malformedFrame = id3Frame('WOAF'), // frame with size of 0B
  453. tag = id3Tag(validFrame, malformedFrame);
  454. metadataStream.on('data', function(event) {
  455. events.push(event);
  456. });
  457. metadataStream.push({
  458. type: 'timed-metadata',
  459. data: new Uint8Array(tag)
  460. })
  461. assert.equal(events.length, 1, 'parsed 1 tag')
  462. assert.equal(events[0].frames.length, 1, 'parsed 1 frame');
  463. assert.equal(events[0].frames[0].key, 'TIT2');
  464. });
  465. QUnit.test('can parse APIC frame in web worker', function(assert) {
  466. var worker = new MetadataStreamTestWorker(),
  467. done = assert.async();
  468. worker.addEventListener('message', function(e) {
  469. assert.equal(e.data.frames[0].key, 'APIC', 'frame key is APIC');
  470. assert.equal(e.data.frames[0].mimeType, 'image/jpeg', 'parsed MIME type is "image/jpeg"');
  471. assert.equal(e.data.frames[0].pictureType, 0x03, 'parsed picture type is 0x03');
  472. assert.equal(e.data.frames[0].description, 'sample description', 'parsed description');
  473. assert.deepEqual(e.data.frames[0].pictureData, new Uint8Array(stringToInts("picture binary data")), 'parsed picture data');
  474. assert.equal(e.data.frames[1].key, 'APIC', 'frame key is APIC');
  475. assert.equal(e.data.frames[1].mimeType, '-->', 'parsed MIME type is "-->"');
  476. assert.equal(e.data.frames[1].pictureType, 0x04, 'parsed picture type is 0x04');
  477. assert.equal(e.data.frames[1].description, 'sample description 2', 'parsed description');
  478. assert.equal(e.data.frames[1].url, 'http://example.org/cover-back.jpg', 'parsed picture data');
  479. worker.terminate();
  480. done();
  481. });
  482. worker.postMessage({
  483. type: 'timed-metadata',
  484. data: new Uint8Array(id3Tag(id3Frame('APIC',
  485. 0x03, // Text encoding: UTF-8
  486. stringToCString('image/jpeg'), // MIME type + \0
  487. 0x03, // Picture type: Cover (front) [ID3v2.4.0 section 4.14]
  488. stringToCString('sample description'), // Decription + \0
  489. stringToInts('picture binary data')
  490. ),
  491. id3Frame('APIC',
  492. 0x03, // Text encoding: UTF-8
  493. stringToCString('-->'), // MIME type: link to the image [ID3v2.4.0 section 4.14] + \0
  494. 0x04, // Picture type: Cover (back) [ID3v2.4.0 section 4.14]
  495. stringToCString('sample description 2'), // Decription + \0
  496. stringToInts('http://example.org/cover-back.jpg')
  497. )))
  498. });
  499. });
  500. QUnit.test('can parse PRIV frames in web worker', function(assert) {
  501. var payload = stringToInts('arbitrary'),
  502. worker = new MetadataStreamTestWorker(),
  503. done = assert.async();
  504. worker.addEventListener('message', function(e) {
  505. assert.equal(e.data.frames[0].key, 'PRIV', 'frame key is PRIV');
  506. assert.deepEqual(new Uint8Array(e.data.frames[0].data), new Uint8Array(payload),
  507. 'parsed the frame private data');
  508. worker.terminate();
  509. done();
  510. });
  511. worker.postMessage({
  512. type: 'timed-metadata',
  513. trackId: 7,
  514. pts: 1000,
  515. dts: 900,
  516. // header
  517. data: new Uint8Array(id3Tag(id3Frame('PRIV',
  518. stringToCString('priv-owner@example.com'),
  519. payload)))
  520. });
  521. });
  522. QUnit.test('can parse TXXX frames in web worker', function(assert) {
  523. var worker = new MetadataStreamTestWorker(),
  524. done = assert.async();
  525. worker.addEventListener('message', function(e) {
  526. assert.equal(e.data.frames[0].key, 'TXXX', 'frame key is TXXX');
  527. assert.equal(e.data.frames[0].description, 'get done', 'parsed the description');
  528. assert.deepEqual(JSON.parse(e.data.frames[0].data), { key: 'value' }, 'parsed the data');
  529. worker.terminate();
  530. done();
  531. });
  532. worker.postMessage({
  533. type: 'timed-metadata',
  534. trackId: 7,
  535. pts: 1000,
  536. dts: 900,
  537. // header
  538. data: new Uint8Array(id3Tag(id3Frame('TXXX',
  539. 0x03, // utf-8
  540. stringToCString('get done'),
  541. stringToCString('{ "key": "value" }')),
  542. [0x00, 0x00]))
  543. });
  544. });
  545. QUnit.test('should parse text frames in web worker', function(assert) {
  546. var worker = new MetadataStreamTestWorker(),
  547. done = assert.async();
  548. worker.addEventListener('message', function(e) {
  549. assert.equal(e.data.frames.length, 2, 'got 2 frames');
  550. assert.equal(e.data.frames[0].key, 'TIT2', 'frame key is TIT2');
  551. assert.equal(e.data.frames[0].value, 'sample song title', 'parsed value')
  552. assert.equal(e.data.frames[0].values.length, 1, 'parsed value is an array of size 1')
  553. assert.equal(e.data.frames[0].values[0], 'sample song title', 'parsed a non multiple strings value')
  554. assert.equal(e.data.frames[1].key, 'TIT3', 'frame key is TIT3');
  555. assert.equal(e.data.frames[1].value, 'sample title 1\0sample title 2', 'parsed value')
  556. assert.equal(e.data.frames[1].values.length, 2, 'parsed value is an array of size 2')
  557. assert.equal(e.data.frames[1].values[0], 'sample title 1', 'parsed 1st multiple strings value')
  558. assert.equal(e.data.frames[1].values[1], 'sample title 2', 'parsed 2nd multiple strings value')
  559. worker.terminate();
  560. done();
  561. });
  562. worker.postMessage({
  563. type: 'timed-metadata',
  564. data: new Uint8Array(id3Tag(id3Frame('TIT2',
  565. 0x03, // utf-8
  566. // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
  567. stringToCString('sample song title')),
  568. id3Frame('TIT3',
  569. 0x03, // utf-8
  570. // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
  571. // text information frames supports multiple strings, stored as a terminator separated list [ID3v2.4.0 section 4.2.]
  572. stringToCString('sample title 1'), stringToCString('sample title 2'))))
  573. });
  574. });
  575. QUnit.test('should parse URL link frames in web worker', function(assert) {
  576. var worker = new MetadataStreamTestWorker(),
  577. done = assert.async(),
  578. payloadBytes;
  579. // if the payload is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
  580. payloadBytes = stringToInts('http://example.org\0 ignored \0 part')
  581. worker.addEventListener('message', function(e) {
  582. assert.equal(e.data.frames[0].key, 'WOAF', 'frame key is WOAF');
  583. assert.equal(e.data.frames[0].url, 'http://example.org', 'parsed URL')
  584. worker.terminate();
  585. done();
  586. });
  587. worker.postMessage({
  588. type: 'timed-metadata',
  589. data: new Uint8Array(id3Tag(id3Frame('WOAF', payloadBytes)))
  590. });
  591. });
  592. QUnit.test('triggers special event after parsing a timestamp ID3 tag', function(assert) {
  593. var
  594. array = new Uint8Array(73),
  595. streamTimestamp = 'com.apple.streaming.transportStreamTimestamp',
  596. priv = 'PRIV',
  597. count = 0,
  598. frame,
  599. tag,
  600. metadataStream,
  601. chunk,
  602. i;
  603. metadataStream = new mp2t.MetadataStream();
  604. metadataStream.on('timestamp', function(f) {
  605. frame = f;
  606. count += 1;
  607. });
  608. metadataStream.on('data', function(t) {
  609. tag = t;
  610. });
  611. array[0] = 73;
  612. array[1] = 68;
  613. array[2] = 51;
  614. array[3] = 4;
  615. array[9] = 63;
  616. array[17] = 53;
  617. array[70] = 13;
  618. array[71] = 187;
  619. array[72] = 160;
  620. for (i = 0; i < priv.length; i++) {
  621. array[i + 10] = priv.charCodeAt(i);
  622. }
  623. for (i = 0; i < streamTimestamp.length; i++) {
  624. array[i + 20] = streamTimestamp.charCodeAt(i);
  625. }
  626. chunk = {
  627. type: 'timed-metadata',
  628. data: array
  629. };
  630. metadataStream.push(chunk);
  631. assert.equal(count, 1, 'timestamp event triggered once');
  632. assert.equal(frame.timeStamp, 900000, 'Initial timestamp fired and calculated correctly');
  633. assert.equal(tag.pts, 10 * 90e3, 'set tag PTS');
  634. assert.equal(tag.dts, 10 * 90e3, 'set tag DTS');
  635. });