622d5984 by David LaPalomento

Implement basic ID3 parsing

Pull out individual frames of the ID3 tag. Tests are now passing.
1 parent 58bf3727
...@@ -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',
......
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>
......