#!/usr/bin/env node /* eslint-disable no-process-exit */ "use strict"; var hlsdump = require('commander'); hlsdump.version('0.0.0') .usage('[options] ') .option('-o, --output ', 'target file') .option('-u, --udp [host:port]', 'relay TS over UDP', function(val) { var r = { host: 'localhost', port: 1234 }; if (val) { var s = val.split(':'); if (s.length === 1) { r.port = parseInt(s[0], 10); } else { r.host = s[0]; r.port = parseInt(s[1], 10); } } return r; }) .option('-b, --buffer-size |full', 'try to buffer of input data (implies -s)', function(val) { if (val === 'full') return 0x80000000 - 1; return parseInt(val, 0); }) .option('-s, --sync', 'clock sync using stream PCR') .option('-f, --full-stream', 'fetch all stream data') .option('-c, --concurrent ', 'fetch using concurrent connections', parseInt) .option('-a, --user-agent ', 'HTTP User-Agent') .option('-i, --info-port ', 'report status using HTTP + json', parseInt) .option('--cookie ', 'add cookie header to key requests') .option('--key ', 'use oob key for decrypting segments', function(opt) {return new Buffer(opt, 'hex');}) .parse(process.argv); var fs = require('fs'), http = require('http'); var oncemore = require('oncemore'), HlsSegmentReader = require('hls-segment-reader'), UdpBlast = require('udp-blast'); var HlsReader = require('../lib/hls-reader'); var src = hlsdump.args[0]; if (!src) { hlsdump.help(); process.exit(-1); } if (hlsdump.bufferSize) hlsdump.sync = true; var segmentReader = new HlsSegmentReader(src, { withData: true, highWaterMark: (hlsdump.concurrent || 1) - 1, fullStream: hlsdump.fullStream }); var r = new HlsReader(segmentReader, hlsdump); segmentReader.once('index', function() { // wait until first index is returned before attaching error listener. // this will enable initials errors to throw segmentReader.on('error', function(err) { console.error('reader error', err.stack || err); }); }); if (hlsdump.udp) { var dst = (hlsdump.udp === true) ? null : hlsdump.udp; r.pipe(new UdpBlast(dst, { packetSize: 7 * 188 })); } if (hlsdump.output) { if (hlsdump.output === '-') r.pipe(process.stdout); else r.pipe(fs.createWriteStream(hlsdump.output)); } var startTime = process.hrtime(); r.on('ready', function() { var delay = process.hrtime(startTime); console.error('"ready" after delay of ' + (delay[0] * 1e3 + delay[1] / 1e6).toFixed(2) + 'ms'); }); r.on('end', function() { console.error('stream complete'); }) var totalDuration = 0; r.on('segment', function(segmentInfo) { var downloadSize = segmentInfo.file.size; var duration = segmentInfo.details.duration; totalDuration += duration; // calculate size when missing if (downloadSize === -1) { downloadSize = 0; segmentInfo.stream.on('data', function(chunk) { downloadSize += chunk.length; }); } oncemore(segmentInfo.stream).once('close', 'end', 'error', function(/*err*/) { console.error('segment done at ' + totalDuration.toFixed(0) + ' seconds, avg bitrate (kbps):', (downloadSize / (duration * 1024 / 8)).toFixed(1)); }); }); if (hlsdump.infoPort) { var stats = require('measured').createCollection(); var currentSegment = -1; // setup stat tracking stats.gauge('bufferBytes', function() { return r.buffer._readableState.length/* + buffer._writableState.length*/; }); stats.gauge('currentSegment', function() { return currentSegment; }); stats.gauge('index.first', function() { return segmentReader.index ? segmentReader.index.first_seq_no : -1; }); stats.gauge('index.last', function() { return segmentReader.index ? segmentReader.index.lastSeqNo() : -1; }); stats.gauge('totalDuration', function() { return totalDuration; }); stats.meter('streamErrors'); r.on('segment', function(segmentInfo) { currentSegment = segmentInfo.seq; var stopwatch = stats.timer('fetchTime').start(); oncemore(segmentInfo.stream).once('close', 'end', 'error', function(err) { stopwatch.end(); if (err) stats.meter('streamErrors').mark(); }); }); var server = http.createServer(function (req, res) { if (req.method === 'GET') { var data = JSON.stringify(stats, null, ' '); res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': data.length }); res.write(data); } res.end(); }).listen(hlsdump.infoPort); oncemore(r).once('end', 'error', function(/*err*/) { server.close(); }); }