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) {
dist: {
nonull: true,
src: ['src/videojs-hls.js',
'src/stream.js',
'src/flv-tag.js',
'src/exp-golomb.js',
'src/h264-stream.js',
'src/aac-stream.js',
'src/metadata-stream.js',
'src/segment-parser.js',
'src/stream.js',
'src/m3u8/m3u8-parser.js',
'src/xhr.js',
'src/playlist-loader.js',
......
/**
* Accepts program elementary stream (PES) data events and parses out
* ID3 metadata from them, if present.
* @see http://id3.org/id3v2.3.0
*/
(function(window, videojs, undefined) {
'use strict';
var defaults = {
debug: false
}, MetadataStream;
MetadataStream = function(options) {
var settings = videojs.util.mergeOptions(defaults, options);
MetadataStream.prototype.init.call(this);
this.push = function(chunk) {
var tagSize, frameStart, frameSize;
// ignore events that don't look like ID3 data
if (chunk.data.length < 10 ||
chunk.data[0] !== 'I'.charCodeAt(0) ||
chunk.data[1] !== 'D'.charCodeAt(0) ||
chunk.data[2] !== '3'.charCodeAt(0)) {
if (settings.debug) {
videojs.log('Skipping unrecognized metadata stream');
}
return;
}
// find the start of the first frame and the end of the tag
tagSize = chunk.data.byteLength;
frameStart = 10;
if (chunk.data[5] & 0x40) {
// advance the frame start past the extended header
frameStart += 4; // header size field
frameStart += (chunk.data[10] << 24) |
(chunk.data[11] << 16) |
(chunk.data[12] << 8) |
(chunk.data[13]);
// clip any padding off the end
tagSize -= (chunk.data[16] << 24) |
(chunk.data[17] << 16) |
(chunk.data[18] << 8) |
(chunk.data[19]);
}
// parse one or more ID3 frames
// http://id3.org/id3v2.3.0#ID3v2_frame_overview
chunk.frames = [];
do {
chunk.frames.push({
id: String.fromCharCode(chunk.data[frameStart]) +
String.fromCharCode(chunk.data[frameStart + 1]) +
String.fromCharCode(chunk.data[frameStart + 2]) +
String.fromCharCode(chunk.data[frameStart + 3])
});
frameSize = (chunk.data[frameStart + 4] << 24) |
(chunk.data[frameStart + 5] << 16) |
(chunk.data[frameStart + 6] << 8) |
(chunk.data[frameStart + 7]);
if (frameSize < 1) {
return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
}
frameStart += 10; // advance past the frame header
frameStart += frameSize; // advance past the frame body
} while (frameStart < tagSize)
this.trigger('data', chunk);
};
};
MetadataStream.prototype = new videojs.Hls.Stream();
videojs.Hls.MetadataStream = MetadataStream;
})(window, videojs);
......@@ -25,7 +25,7 @@
module('MetadataStream', {
setup: function() {
// metadataStream = new videojs.Hls.MetadataStream();
metadataStream = new videojs.Hls.MetadataStream();
}
});
......@@ -53,23 +53,23 @@
size = result.length - 10;
// append the fields of the ID3 frame
result.concat(Array.prototype.slice.call(arguments, 1));
result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1));
// set the size
size = result.length - 10;
result[0] = (size >>> 24);
result[1] = (size >>> 16) & 0xff;
result[2] = (size >>> 8) & 0xff;
result[3] = (size) & 0xff;
result[4] = (size >>> 24);
result[5] = (size >>> 16) & 0xff;
result[6] = (size >>> 8) & 0xff;
result[7] = (size) & 0xff;
return result;
};
test('parses simple ID3 metadata out of PES packets', function() {
var events = [], id3Bytes, size;
// metadataStream.on('data', function(event) {
// events.push(event);
// });
metadataStream.on('data', function(event) {
events.push(event);
});
id3Bytes = new Uint8Array(stringToInts('ID3').concat([
0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
......@@ -77,10 +77,9 @@
0x00, 0x00, 0x00, 0x00, // size. set later
// extended header
0x00, 0x00, 0x00, 0x01, // extended header size
0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
0x00, 0x00, // extended flags
0x00, 0x00, 0x00, 0x02, // size of padding
0xff // extended header payload
// frame 0
// http://id3.org/id3v2.3.0#User_defined_text_information_frame
......@@ -99,10 +98,10 @@
// set header size field
size = id3Bytes.byteLength - 10;
id3Bytes[6] = (size >>> 21) & 0xef;
id3Bytes[7] = (size >>> 14) & 0xef;
id3Bytes[8] = (size >>> 7) & 0xef;
id3Bytes[9] = (size) & 0xef;
id3Bytes[6] = (size >>> 21) & 0x7f;
id3Bytes[7] = (size >>> 14) & 0x7f;
id3Bytes[8] = (size >>> 7) & 0x7f;
id3Bytes[9] = (size) & 0x7f;
metadataStream.push({
trackId: 7,
......@@ -119,6 +118,24 @@
equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame');
});
test('skips non-ID3 metadata events', function() {
var events = [], id3Bytes, size;
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 1000,
// header
data: new Uint8Array([0])
});
equal(events.length, 0, 'did not emit an event');
});
// missing cases:
// unsynchronization
// CRC
......
......@@ -21,14 +21,15 @@
<!-- HLS plugin -->
<script src="../src/videojs-hls.js"></script>
<script src="../src/xhr.js"></script>
<script src="../src/stream.js"></script>
<script src="../src/flv-tag.js"></script>
<script src="../src/exp-golomb.js"></script>
<script src="../src/h264-stream.js"></script>
<script src="../src/aac-stream.js"></script>
<script src="../src/metadata-stream.js"></script>
<script src="../src/segment-parser.js"></script>
<!-- M3U8 -->
<script src="../src/stream.js"></script>
<script src="../src/m3u8/m3u8-parser.js"></script>
<script src="../src/playlist-loader.js"></script>
<script src="../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
......