Implement basic ID3 parsing
Pull out individual frames of the ID3 tag. Tests are now passing.
Showing
4 changed files
with
111 additions
and
17 deletions
... | @@ -24,12 +24,13 @@ module.exports = function(grunt) { | ... | @@ -24,12 +24,13 @@ module.exports = function(grunt) { |
24 | dist: { | 24 | dist: { |
25 | nonull: true, | 25 | nonull: true, |
26 | src: ['src/videojs-hls.js', | 26 | src: ['src/videojs-hls.js', |
27 | 'src/stream.js', | ||
27 | 'src/flv-tag.js', | 28 | 'src/flv-tag.js', |
28 | 'src/exp-golomb.js', | 29 | 'src/exp-golomb.js', |
29 | 'src/h264-stream.js', | 30 | 'src/h264-stream.js', |
30 | 'src/aac-stream.js', | 31 | 'src/aac-stream.js', |
32 | 'src/metadata-stream.js', | ||
31 | 'src/segment-parser.js', | 33 | 'src/segment-parser.js', |
32 | 'src/stream.js', | ||
33 | 'src/m3u8/m3u8-parser.js', | 34 | 'src/m3u8/m3u8-parser.js', |
34 | 'src/xhr.js', | 35 | 'src/xhr.js', |
35 | 'src/playlist-loader.js', | 36 | 'src/playlist-loader.js', | ... | ... |
src/metadata-stream.js
0 → 100644
1 | /** | ||
2 | * Accepts program elementary stream (PES) data events and parses out | ||
3 | * ID3 metadata from them, if present. | ||
4 | * @see http://id3.org/id3v2.3.0 | ||
5 | */ | ||
6 | (function(window, videojs, undefined) { | ||
7 | 'use strict'; | ||
8 | var defaults = { | ||
9 | debug: false | ||
10 | }, MetadataStream; | ||
11 | |||
12 | MetadataStream = function(options) { | ||
13 | var settings = videojs.util.mergeOptions(defaults, options); | ||
14 | MetadataStream.prototype.init.call(this); | ||
15 | |||
16 | this.push = function(chunk) { | ||
17 | var tagSize, frameStart, frameSize; | ||
18 | |||
19 | // ignore events that don't look like ID3 data | ||
20 | if (chunk.data.length < 10 || | ||
21 | chunk.data[0] !== 'I'.charCodeAt(0) || | ||
22 | chunk.data[1] !== 'D'.charCodeAt(0) || | ||
23 | chunk.data[2] !== '3'.charCodeAt(0)) { | ||
24 | if (settings.debug) { | ||
25 | videojs.log('Skipping unrecognized metadata stream'); | ||
26 | } | ||
27 | return; | ||
28 | } | ||
29 | |||
30 | // find the start of the first frame and the end of the tag | ||
31 | tagSize = chunk.data.byteLength; | ||
32 | frameStart = 10; | ||
33 | if (chunk.data[5] & 0x40) { | ||
34 | // advance the frame start past the extended header | ||
35 | frameStart += 4; // header size field | ||
36 | frameStart += (chunk.data[10] << 24) | | ||
37 | (chunk.data[11] << 16) | | ||
38 | (chunk.data[12] << 8) | | ||
39 | (chunk.data[13]); | ||
40 | |||
41 | // clip any padding off the end | ||
42 | tagSize -= (chunk.data[16] << 24) | | ||
43 | (chunk.data[17] << 16) | | ||
44 | (chunk.data[18] << 8) | | ||
45 | (chunk.data[19]); | ||
46 | } | ||
47 | |||
48 | // parse one or more ID3 frames | ||
49 | // http://id3.org/id3v2.3.0#ID3v2_frame_overview | ||
50 | chunk.frames = []; | ||
51 | do { | ||
52 | chunk.frames.push({ | ||
53 | id: String.fromCharCode(chunk.data[frameStart]) + | ||
54 | String.fromCharCode(chunk.data[frameStart + 1]) + | ||
55 | String.fromCharCode(chunk.data[frameStart + 2]) + | ||
56 | String.fromCharCode(chunk.data[frameStart + 3]) | ||
57 | }); | ||
58 | |||
59 | frameSize = (chunk.data[frameStart + 4] << 24) | | ||
60 | (chunk.data[frameStart + 5] << 16) | | ||
61 | (chunk.data[frameStart + 6] << 8) | | ||
62 | (chunk.data[frameStart + 7]); | ||
63 | if (frameSize < 1) { | ||
64 | return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); | ||
65 | } | ||
66 | frameStart += 10; // advance past the frame header | ||
67 | frameStart += frameSize; // advance past the frame body | ||
68 | } while (frameStart < tagSize) | ||
69 | this.trigger('data', chunk); | ||
70 | }; | ||
71 | }; | ||
72 | MetadataStream.prototype = new videojs.Hls.Stream(); | ||
73 | |||
74 | videojs.Hls.MetadataStream = MetadataStream; | ||
75 | })(window, videojs); |
... | @@ -25,7 +25,7 @@ | ... | @@ -25,7 +25,7 @@ |
25 | 25 | ||
26 | module('MetadataStream', { | 26 | module('MetadataStream', { |
27 | setup: function() { | 27 | setup: function() { |
28 | // metadataStream = new videojs.Hls.MetadataStream(); | 28 | metadataStream = new videojs.Hls.MetadataStream(); |
29 | } | 29 | } |
30 | }); | 30 | }); |
31 | 31 | ||
... | @@ -53,23 +53,23 @@ | ... | @@ -53,23 +53,23 @@ |
53 | size = result.length - 10; | 53 | size = result.length - 10; |
54 | 54 | ||
55 | // append the fields of the ID3 frame | 55 | // append the fields of the ID3 frame |
56 | result.concat(Array.prototype.slice.call(arguments, 1)); | 56 | result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1)); |
57 | 57 | ||
58 | // set the size | 58 | // set the size |
59 | size = result.length - 10; | 59 | size = result.length - 10; |
60 | result[0] = (size >>> 24); | 60 | result[4] = (size >>> 24); |
61 | result[1] = (size >>> 16) & 0xff; | 61 | result[5] = (size >>> 16) & 0xff; |
62 | result[2] = (size >>> 8) & 0xff; | 62 | result[6] = (size >>> 8) & 0xff; |
63 | result[3] = (size) & 0xff; | 63 | result[7] = (size) & 0xff; |
64 | 64 | ||
65 | return result; | 65 | return result; |
66 | }; | 66 | }; |
67 | 67 | ||
68 | test('parses simple ID3 metadata out of PES packets', function() { | 68 | test('parses simple ID3 metadata out of PES packets', function() { |
69 | var events = [], id3Bytes, size; | 69 | var events = [], id3Bytes, size; |
70 | // metadataStream.on('data', function(event) { | 70 | metadataStream.on('data', function(event) { |
71 | // events.push(event); | 71 | events.push(event); |
72 | // }); | 72 | }); |
73 | 73 | ||
74 | id3Bytes = new Uint8Array(stringToInts('ID3').concat([ | 74 | id3Bytes = new Uint8Array(stringToInts('ID3').concat([ |
75 | 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0) | 75 | 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0) |
... | @@ -77,10 +77,9 @@ | ... | @@ -77,10 +77,9 @@ |
77 | 0x00, 0x00, 0x00, 0x00, // size. set later | 77 | 0x00, 0x00, 0x00, 0x00, // size. set later |
78 | 78 | ||
79 | // extended header | 79 | // extended header |
80 | 0x00, 0x00, 0x00, 0x01, // extended header size | 80 | 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC |
81 | 0x00, 0x00, // extended flags | 81 | 0x00, 0x00, // extended flags |
82 | 0x00, 0x00, 0x00, 0x02, // size of padding | 82 | 0x00, 0x00, 0x00, 0x02, // size of padding |
83 | 0xff // extended header payload | ||
84 | 83 | ||
85 | // frame 0 | 84 | // frame 0 |
86 | // http://id3.org/id3v2.3.0#User_defined_text_information_frame | 85 | // http://id3.org/id3v2.3.0#User_defined_text_information_frame |
... | @@ -99,10 +98,10 @@ | ... | @@ -99,10 +98,10 @@ |
99 | 98 | ||
100 | // set header size field | 99 | // set header size field |
101 | size = id3Bytes.byteLength - 10; | 100 | size = id3Bytes.byteLength - 10; |
102 | id3Bytes[6] = (size >>> 21) & 0xef; | 101 | id3Bytes[6] = (size >>> 21) & 0x7f; |
103 | id3Bytes[7] = (size >>> 14) & 0xef; | 102 | id3Bytes[7] = (size >>> 14) & 0x7f; |
104 | id3Bytes[8] = (size >>> 7) & 0xef; | 103 | id3Bytes[8] = (size >>> 7) & 0x7f; |
105 | id3Bytes[9] = (size) & 0xef; | 104 | id3Bytes[9] = (size) & 0x7f; |
106 | 105 | ||
107 | metadataStream.push({ | 106 | metadataStream.push({ |
108 | trackId: 7, | 107 | trackId: 7, |
... | @@ -119,6 +118,24 @@ | ... | @@ -119,6 +118,24 @@ |
119 | equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame'); | 118 | equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame'); |
120 | }); | 119 | }); |
121 | 120 | ||
121 | test('skips non-ID3 metadata events', function() { | ||
122 | var events = [], id3Bytes, size; | ||
123 | metadataStream.on('data', function(event) { | ||
124 | events.push(event); | ||
125 | }); | ||
126 | |||
127 | metadataStream.push({ | ||
128 | trackId: 7, | ||
129 | pts: 1000, | ||
130 | dts: 1000, | ||
131 | |||
132 | // header | ||
133 | data: new Uint8Array([0]) | ||
134 | }); | ||
135 | |||
136 | equal(events.length, 0, 'did not emit an event'); | ||
137 | }); | ||
138 | |||
122 | // missing cases: | 139 | // missing cases: |
123 | // unsynchronization | 140 | // unsynchronization |
124 | // CRC | 141 | // CRC | ... | ... |
... | @@ -21,14 +21,15 @@ | ... | @@ -21,14 +21,15 @@ |
21 | <!-- HLS plugin --> | 21 | <!-- HLS plugin --> |
22 | <script src="../src/videojs-hls.js"></script> | 22 | <script src="../src/videojs-hls.js"></script> |
23 | <script src="../src/xhr.js"></script> | 23 | <script src="../src/xhr.js"></script> |
24 | <script src="../src/stream.js"></script> | ||
24 | <script src="../src/flv-tag.js"></script> | 25 | <script src="../src/flv-tag.js"></script> |
25 | <script src="../src/exp-golomb.js"></script> | 26 | <script src="../src/exp-golomb.js"></script> |
26 | <script src="../src/h264-stream.js"></script> | 27 | <script src="../src/h264-stream.js"></script> |
27 | <script src="../src/aac-stream.js"></script> | 28 | <script src="../src/aac-stream.js"></script> |
29 | <script src="../src/metadata-stream.js"></script> | ||
28 | <script src="../src/segment-parser.js"></script> | 30 | <script src="../src/segment-parser.js"></script> |
29 | 31 | ||
30 | <!-- M3U8 --> | 32 | <!-- M3U8 --> |
31 | <script src="../src/stream.js"></script> | ||
32 | <script src="../src/m3u8/m3u8-parser.js"></script> | 33 | <script src="../src/m3u8/m3u8-parser.js"></script> |
33 | <script src="../src/playlist-loader.js"></script> | 34 | <script src="../src/playlist-loader.js"></script> |
34 | <script src="../node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | 35 | <script src="../node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | ... | ... |
-
Please register or sign in to post a comment