Ver código fonte

use newly abstracted m3u8parse module

Gil Pedersen 12 anos atrás
pai
commit
072eebc2f5
4 arquivos alterados com 4 adições e 419 exclusões
  1. 0 290
      lib/m3u8.js
  2. 2 3
      lib/reader.js
  3. 2 1
      package.json
  4. 0 125
      test/m3u8.test.js

+ 0 - 290
lib/m3u8.js

@@ -1,290 +0,0 @@
-"use strict";
-
-var path = require('path'),
-    util = require('util'),
-    carrier = require('carrier'),
-    debug = require('debug')('hls:m3u8');
-
-exports.M3U8Playlist = M3U8Playlist;
-exports.M3U8Segment = M3U8Segment;
-exports.ParserError = ParserError;
-
-exports.parse = parse;
-//exports.stringify = stringify;
-
-function M3U8Playlist() {
-  this.variant = false;
-
-  // initialize to default values
-  this.version = 1; // V1
-  this.allow_cache = true;
-  this.i_frames_only = false; // V4
-  this.target_duration = undefined;
-  this.first_seq_no = 0;
-  this.type = undefined; // V?
-  this.ended = false;
-
-  this.segments = [];
-
-  // for variant streams
-  this.programs = {};
-  this.groups = {};
-}
-
-M3U8Playlist.prototype.PlaylistType = {
-  EVENT: 'EVENT',
-  VOD: 'VOD'
-};
-
-M3U8Playlist.prototype.totalDuration = function() {
-  return this.segments.reduce(function(sum, segment) {
-    return sum + segment.duration;
-  }, 0);
-};
-
-M3U8Playlist.prototype.isLive = function() {
-  return !(this.ended || this.type === this.PlaylistType.VOD);
-};
-
-M3U8Playlist.prototype.startSeqNo = function(full) {
-  if (!this.isLive() || full) return this.first_seq_no;
-
-  var duration = this.target_duration * 3;
-  for (var i=this.segments.length-1; i>0; i--) {
-    duration -= this.segments[i].duration;
-    if (duration < 0) break;
-  }
-  // TODO: validate that correct seqNo is returned
-  return this.first_seq_no + i;
-};
-
-M3U8Playlist.prototype.lastSeqNo = function() {
-  return this.first_seq_no + this.segments.length - 1;
-};
-
-// return whether the seqNo is in the index
-M3U8Playlist.prototype.isValidSeqNo = function(seqNo) {
-  return (seqNo >= this.first_seq_no) && (seqNo <= this.lastSeqNo());
-};
-
-M3U8Playlist.prototype.getSegment = function(seqNo) {
-  // TODO: should we check for number type and throw if not?
-  var index = seqNo-this.first_seq_no;
-  if (index < 0 || index > this.segments.length)
-    return null;
-  return this.segments[index];
-};
-
-M3U8Playlist.prototype.toString = function() {
-  // TODO:
-  return 'M3U8Playlist';
-};
-
-function M3U8Segment(uri, meta, version) {
-  this.duration = meta.duration;
-  this.title = meta.title;
-  this.uri = uri;
-  this.discontinuity = meta.discontinuity || false;
-
-  // optional
-  if (meta.program_time)
-    this.program_time = meta.program_time;
-  if (meta.key)
-    this.key = meta.key;
-
-  if (version >= 5 && meta.map)
-    this.map = meta.map;
-}
-
-M3U8Segment.prototype.toString = function() {
-  // TODO: support writing all the information
-  return '#EXTINF '+this.duration.toFixed(3)+','+this.title + '\n' + this.uri + '\n';
-};
-
-function parse(stream, cb) {
-  var m3u8 = new M3U8Playlist(),
-      line_no = 0,
-      meta = {};
-
-  stream.on('error', ReportError);
-
-  var cr = carrier.carry(stream);
-  cr.on('line', ParseLine);
-  cr.on('end', Complete);
-
-  function cleanup() {
-    stream.removeListener('error', ReportError);
-    cr.removeListener('line', ParseLine);
-    cr.removeListener('end', Complete);
-  }
-
-  function ReportError(err) {
-    cleanup();
-    cb(err);
-  }
-
-  function Complete() {
-    if (line_no === 0)
-      return ReportError(new ParserError('No line data', '', -1))
-    cleanup();
-    cb(null, m3u8);
-  }
-
-  function ParseExt(cmd, arg) {
-    if (!(cmd in extParser))
-      return false;
-
-    debug('parsing ext', cmd, arg);
-    extParser[cmd](arg);
-    return true;
-  }
-  
-  // AttrList's are currently handled without any implicit knowledge of key/type mapping
-  function ParseAttrList(input) {
-    // TODO: handle newline escapes in quoted-string's
-    var re = /(.+?)=((?:\".*?\")|.*?)(?:,|$)/g;
-//    var re = /(.+?)=(?:(?:\"(.*?)\")|(.*?))(?:,|$)/g;
-    var match, attrs = {};
-    while ((match = re.exec(input)) !== null)
-      attrs[match[1].toLowerCase()] = match[2];
-
-    debug('parsed attributes', attrs);
-    return attrs;
-  }
-
-  function unquote(str) {
-    return str.slice(1,-1);
-  }
-
-  function ParseLine(line) {
-    line_no += 1;
-
-    if (line_no === 1) {
-      if (line !== '#EXTM3U')
-        return ReportError(new ParserError('Missing required #EXTM3U header', line, line_no));
-      return;
-    }
-
-    if (!line.length) return; // blank lines are ignored (3.1)
-        
-    if (line[0] === '#') {
-      var matches = line.match(/^(#EXT[^:]*):?(.*)/);
-      if (!matches)
-        return debug('ignoring comment', line);
-
-      var cmd = matches[1],
-          arg = matches[2];
-
-      if (!ParseExt(cmd, arg))
-        return ReportError(new ParserError('Unknown #EXT: '+cmd, line, line_no));
-    } else if (m3u8.variant) {
-      var id = meta.info['program-id'];
-      if (!(id in m3u8.programs))
-        m3u8.programs[id] = [];
-
-      meta.uri = line;
-      m3u8.programs[id].push(meta);
-      meta = {};
-    } else {
-      if (!('duration' in meta))
-        return ReportError(new ParserError('Missing #EXTINF before media file URI', line, line_no));
-
-      m3u8.segments.push(new M3U8Segment(line, meta, m3u8.version));
-      meta = {};
-    }
-  }
-
-  // TODO: add more validation logic
-  var extParser = {
-    '#EXT-X-VERSION': function(arg) {
-      m3u8.version = parseInt(arg, 10);
-
-      if (m3u8.version >= 4)
-        for (var attrname in extParserV4) { extParser[attrname] = extParser[attrname]; }
-      if (m3u8.version >= 5)
-        for (var attrname in extParserV5) { extParser[attrname] = extParser[attrname]; }
-    },
-    '#EXT-X-TARGETDURATION': function(arg) {
-      m3u8.target_duration = parseInt(arg, 10);
-    },
-    '#EXT-X-ALLOW-CACHE': function(arg) {
-      m3u8.allow_cache = (arg!=='NO');
-    },
-    '#EXT-X-MEDIA-SEQUENCE': function(arg) {
-      m3u8.first_seq_no = parseInt(arg, 10);
-    },
-    '#EXT-X-PLAYLIST-TYPE': function(arg) {
-      m3u8.type = arg;
-    },
-    '#EXT-X-ENDLIST': function(arg) {
-      m3u8.ended = true;
-    },
-
-    '#EXTINF': function(arg) {
-      var n = arg.split(',');
-      meta.duration = parseFloat(n.shift());
-      meta.title = n.join(',');
-
-      if (meta.duration <= 0)
-        return ReportError(new ParserError('Invalid duration', line, line_no));
-    },
-    '#EXT-X-KEY': function(arg) {
-      meta.key = ParseAttrList(arg);
-    },
-    '#EXT-X-PROGRAM-DATE-TIME': function(arg) {
-      meta.program_time = new Date(arg);
-    },
-    '#EXT-X-DISCONTINUITY': function(arg) {
-      meta.discontinuity = true;
-    },
-
-    // variant
-    '#EXT-X-STREAM-INF': function(arg) {
-      m3u8.variant = true;
-      meta.info = ParseAttrList(arg);
-    },
-    // variant v4 since variant streams are not required to specify version
-    '#EXT-X-MEDIA': function(arg) {
-      //m3u8.variant = true;
-      var attrs = ParseAttrList(arg),
-          id = unquote(attrs['group-id']);
-
-      if (!(id in m3u8.groups)) {
-        m3u8.groups[id] = [];
-        m3u8.groups[id].type = attrs.type;
-      }
-      m3u8.groups[id].push(attrs);
-    },
-    '#EXT-X-I-FRAME-STREAM-INF': function(arg) {
-      m3u8.variant = true;
-      debug('not yet supported', '#EXT-X-I-FRAME-STREAM-INF');
-    }
-  };
-
-  var extParserV4 = {
-    '#EXT-X-I-FRAMES-ONLY': function(arg) {
-      m3u8.i_frames_only = true;
-    },
-    '#EXT-X-BYTERANGE': function(arg) {
-      var n = arg.split('@');
-      meta.byterange = {length:parseInt(n[0], 10)};
-      if (n.length > 1)
-        meta.byterange.offset = parseInt(n[1], 10);
-    }
-  }
-
-  var extParserV5 = {
-    '#EXT-X-MAP': function(arg) {
-      meta.map = ParseAttrList(arg);
-    }
-  }
-}
-
-function ParserError(msg, line, line_no, constr) {
-  Error.captureStackTrace(this, constr || this);
-  this.message = msg || 'Error';
-  this.line = line;
-  this.lineNumber = line_no;
-}
-util.inherits(ParserError, Error);
-ParserError.prototype.name = 'Parser Error';

+ 2 - 3
lib/reader.js

@@ -10,6 +10,7 @@ var util = require('util'),
 var request = require('request'),
     extend = require('xtend'),
     oncemore = require('oncemore'),
+    m3u8parse = require('m3u8parse'),
     uristream = require('./uristream'),
     debug = require('debug')('hls:reader');
 
@@ -20,8 +21,6 @@ try {
   var Readable = require('readable-stream');
 }
 
-var m3u8 = require('./m3u8');
-
 function noop() {};
 
 module.exports = hlsreader;
@@ -187,7 +186,7 @@ function HlsStreamReader(src, options) {
       self.baseUrl = meta.url;
     });
 
-    m3u8.parse(stream, function(err, index) {
+    m3u8parse(stream, function(err, index) {
       if (err) {
         self.emit('error', err);
         updatecheck(false);

+ 2 - 1
package.json

@@ -34,7 +34,8 @@
     "deep-equal": "0.0.0",
     "measured": "~0.1.3",
     "streamprocess": "0.0.1",
-    "oncemore": "~0.1.0"
+    "oncemore": "~0.1.0",
+    "m3u8parse": "0.0.1"
   },
   "devDependencies": {
     "mocha": "~1.11.0",

+ 0 - 125
test/m3u8.test.js

@@ -1,125 +0,0 @@
-var fs = require('fs'),
-    path = require('path'),
-    should = require('should');
-
-var m3u8 = require('..').m3u8;
-
-var fixtureDir = path.join(__dirname, 'fixtures');
-
-describe('M3U8', function() {
-
-  it('should parse a valid file', function(done) {
-  	var stream = fs.createReadStream(path.join(fixtureDir, 'enc.m3u8'));
-    m3u8.parse(stream, function(err, index) {
-      should.not.exist(err);
-      should.exist(index);
-      index.variant.should.be.false;
-      done();
-    });
-  })
-
-  it('should parse a basic variant file', function(done) {
-  	var stream = fs.createReadStream(path.join(fixtureDir, 'variant.m3u8'));
-    m3u8.parse(stream, function(err, index) {
-      should.not.exist(err);
-      should.exist(index);
-      index.variant.should.be.true;
-      done();
-    });
-  })
-
-  it('should parse an advanced variant file', function(done) {
-  	var stream = fs.createReadStream(path.join(fixtureDir, 'variant_v4.m3u8'));
-    m3u8.parse(stream, function(err, index) {
-      should.not.exist(err);
-      should.exist(index);
-      index.variant.should.be.true;
-      done();
-    });
-  })
-
-})
-
-describe('M3U8Playlist', function() {
-  var testIndex = null;
-  var variantIndex = null;
-
-  before(function(done) {
-  	var stream = fs.createReadStream(path.join(fixtureDir, 'enc.m3u8'));
-    m3u8.parse(stream, function(err, index) {
-      should.not.exist(err);
-      testIndex = index;
-      done();
-    });
-  })
-
-  before(function(done) {
-  	var stream = fs.createReadStream(path.join(fixtureDir, 'variant_v4.m3u8'));
-    m3u8.parse(stream, function(err, index) {
-      should.not.exist(err);
-      variantIndex = index;
-      done();
-    });
-  })
-
-  describe('#totalDuration()', function() {
-    it('should calculate total of all segments durations', function() {
-      testIndex.totalDuration().should.equal(46.166);
-      variantIndex.totalDuration().should.equal(0);
-    })
-  })
-
-  describe('#isLive()', function() {
-    it('should return true when no #EXT-X-ENDLIST is present', function() {
-      testIndex.ended.should.be.false;
-      testIndex.isLive().should.be.true;
-    })
-  })
-
-  describe('#startSeqNo()', function() {
-    it('should return the sequence number to start streaming from', function() {
-      testIndex.startSeqNo().should.equal(7794);
-      variantIndex.startSeqNo().should.equal(-1);
-    })
-  })
-
-  describe('#lastSeqNo()', function() {
-    it('should return the sequence number of the final segment', function() {
-      testIndex.lastSeqNo().should.equal(7797);
-      variantIndex.lastSeqNo().should.equal(-1);
-    })
-  })
-
-  describe('#isValidSeqNo()', function() {
-    it('should return false for early numbers', function() {
-      testIndex.isValidSeqNo(-1000).should.be.false;
-      testIndex.isValidSeqNo(0).should.be.false;
-      testIndex.isValidSeqNo("100").should.be.false;
-    })
-    it('should return false for future numbers', function() {
-      testIndex.isValidSeqNo(10000).should.be.false;
-      testIndex.isValidSeqNo("10000").should.be.false;
-    })
-    it('should return true for numbers in range', function() {
-      testIndex.isValidSeqNo(7794).should.be.true;
-      testIndex.isValidSeqNo("7795").should.be.true;
-      testIndex.isValidSeqNo(7796).should.be.true;
-      testIndex.isValidSeqNo(7797).should.be.true;
-    })
-  })
-
-  describe('#getSegment()', function() {
-    it('should return segment data for valid sequence numbers', function() {
-      testIndex.getSegment(7794).should.be.an.instanceof(m3u8.M3U8Segment);
-      testIndex.getSegment(7797).should.be.an.instanceof(m3u8.M3U8Segment);
-    })
-    it('should return null for out of bounds sequence numbers', function() {
-      should.not.exist(testIndex.getSegment(-1));
-      should.not.exist(testIndex.getSegment(7793));
-      should.not.exist(testIndex.getSegment(7798));
-
-      should.not.exist(variantIndex.getSegment(0));
-    })
-  })
-
-})