|
@@ -15,10 +15,13 @@ var mime = require('mime-types'),
|
|
|
writeFileAtomic = require('write-file-atomic'),
|
|
writeFileAtomic = require('write-file-atomic'),
|
|
|
debug = require('debug')('hls:recorder');
|
|
debug = require('debug')('hls:recorder');
|
|
|
|
|
|
|
|
|
|
+var SegmentDecrypt = require('./segment-decrypt');
|
|
|
|
|
+
|
|
|
// add custom extensions
|
|
// add custom extensions
|
|
|
mime.extensions['audio/aac'] = ['aac'];
|
|
mime.extensions['audio/aac'] = ['aac'];
|
|
|
mime.extensions['audio/ac3'] = ['ac3'];
|
|
mime.extensions['audio/ac3'] = ['ac3'];
|
|
|
|
|
|
|
|
|
|
+
|
|
|
function HlsStreamRecorder(reader, dst, options) {
|
|
function HlsStreamRecorder(reader, dst, options) {
|
|
|
options = options || {};
|
|
options = options || {};
|
|
|
|
|
|
|
@@ -32,6 +35,7 @@ function HlsStreamRecorder(reader, dst, options) {
|
|
|
this.startOffset = parseFloat(options.startOffset);
|
|
this.startOffset = parseFloat(options.startOffset);
|
|
|
this.subreader = options.subreader;
|
|
this.subreader = options.subreader;
|
|
|
this.collect = !!options.collect; // collect into a single file (v4 feature)
|
|
this.collect = !!options.collect; // collect into a single file (v4 feature)
|
|
|
|
|
+ this.decrypt = options.decrypt;
|
|
|
|
|
|
|
|
this.recorders = [];
|
|
this.recorders = [];
|
|
|
}
|
|
}
|
|
@@ -97,7 +101,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
var rec = this.recorderForUrl(programUrl);
|
|
var rec = this.recorderForUrl(programUrl);
|
|
|
if (!rec || !rec.localUrl) {
|
|
if (!rec || !rec.localUrl) {
|
|
|
var dir = self.variantName(program.info, index);
|
|
var dir = self.variantName(program.info, index);
|
|
|
- rec = new HlsStreamRecorder(self.subreader(programUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect });
|
|
|
|
|
|
|
+ rec = new HlsStreamRecorder(self.subreader(programUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect, decrypt: this.decrypt });
|
|
|
rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
|
|
rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
|
|
|
rec.remoteUrl = programUrl;
|
|
rec.remoteUrl = programUrl;
|
|
|
|
|
|
|
@@ -121,7 +125,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
var rec = this.recorderForUrl(itemUrl);
|
|
var rec = this.recorderForUrl(itemUrl);
|
|
|
if (!rec || !rec.localUrl) {
|
|
if (!rec || !rec.localUrl) {
|
|
|
var dir = self.groupSrcName(groupItem, index);
|
|
var dir = self.groupSrcName(groupItem, index);
|
|
|
- rec = new HlsStreamRecorder(self.subreader(itemUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect });
|
|
|
|
|
|
|
+ rec = new HlsStreamRecorder(self.subreader(itemUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect, decrypt: this.decrypt });
|
|
|
rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
|
|
rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
|
|
|
rec.remoteUrl = itemUrl;
|
|
rec.remoteUrl = itemUrl;
|
|
|
|
|
|
|
@@ -152,6 +156,10 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
debug('done');
|
|
debug('done');
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ if (this.decrypt) {
|
|
|
|
|
+ this.decrypt.base = this.reader.baseUrl;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// validate update
|
|
// validate update
|
|
@@ -159,7 +167,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
throw new Error('Invalid index');
|
|
throw new Error('Invalid index');
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-HlsStreamRecorder.prototype.process = function(segmentInfo, next) {
|
|
|
|
|
|
|
+HlsStreamRecorder.prototype.process = function(segmentInfo, done) {
|
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
|
|
var segment = new m3u8parse.M3U8Segment(segmentInfo.details, true);
|
|
var segment = new m3u8parse.M3U8Segment(segmentInfo.details, true);
|
|
@@ -187,29 +195,38 @@ HlsStreamRecorder.prototype.process = function(segmentInfo, next) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// save the stream segment
|
|
// save the stream segment
|
|
|
- var stream = oncemore(segmentInfo.stream);
|
|
|
|
|
- stream.pipe(fs.createWriteStream(path.join(this.dst, segment.uri), { flags: newFile ? 'w' : 'a' }));
|
|
|
|
|
|
|
+ SegmentDecrypt.decrypt(segmentInfo.stream, segmentInfo.details.key, this.decrypt, function (err, stream) {
|
|
|
|
|
+ if (err) {
|
|
|
|
|
+ console.error('decrypt failed', err.stack);
|
|
|
|
|
+ stream = segmentInfo.stream;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ segment.key = null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var bytesWritten = 0;
|
|
|
|
|
- if (this.collect) {
|
|
|
|
|
- stream.on('data', function(chunk) {
|
|
|
|
|
- bytesWritten += chunk.length;
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ stream = oncemore(stream);
|
|
|
|
|
+ stream.pipe(fs.createWriteStream(path.join(self.dst, segment.uri), { flags: newFile ? 'w' : 'a' }));
|
|
|
|
|
|
|
|
- stream.once('end', 'error', function(err) {
|
|
|
|
|
- // only to report errors
|
|
|
|
|
- if (err) debug('stream error', err.stack || err);
|
|
|
|
|
|
|
+ var bytesWritten = 0;
|
|
|
|
|
+ if (self.collect) {
|
|
|
|
|
+ stream.on('data', function(chunk) {
|
|
|
|
|
+ bytesWritten += chunk.length;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (segment.byterange)
|
|
|
|
|
- segment.byterange.length = bytesWritten;
|
|
|
|
|
|
|
+ stream.once('end', 'error', function(err) {
|
|
|
|
|
+ // only to report errors
|
|
|
|
|
+ if (err) debug('stream error', err.stack || err);
|
|
|
|
|
|
|
|
- // update index
|
|
|
|
|
- self.index.segments.push(segment);
|
|
|
|
|
- self.flushIndex(next);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ if (segment.byterange)
|
|
|
|
|
+ segment.byterange.length = bytesWritten;
|
|
|
|
|
|
|
|
- this.seq++;
|
|
|
|
|
|
|
+ // update index
|
|
|
|
|
+ self.index.segments.push(segment);
|
|
|
|
|
+ self.flushIndex(done);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ self.seq++;
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
HlsStreamRecorder.prototype.variantName = function(info, index) {
|
|
HlsStreamRecorder.prototype.variantName = function(info, index) {
|