|
|
@@ -3,6 +3,7 @@
|
|
|
const Path = require('path');
|
|
|
const Url = require('url');
|
|
|
|
|
|
+const Bounce = require('bounce');
|
|
|
const Mime = require('mime-types');
|
|
|
const StreamEach = require('stream-each');
|
|
|
const M3U8Parse = require('m3u8parse');
|
|
|
@@ -151,13 +152,11 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
}
|
|
|
|
|
|
// hook end listener
|
|
|
- this.reader.on('end', () => {
|
|
|
+ this.reader.on('end', async () => {
|
|
|
|
|
|
this.index.ended = true;
|
|
|
- this.flushIndex((/*err*/) => {
|
|
|
-
|
|
|
- debug('done');
|
|
|
- });
|
|
|
+ await this.flushIndex();
|
|
|
+ debug('done');
|
|
|
});
|
|
|
|
|
|
if (this.decrypt) {
|
|
|
@@ -171,48 +170,58 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-HlsStreamRecorder.prototype.process = function (segmentInfo, next) {
|
|
|
-
|
|
|
- if (segmentInfo.type === 'segment') {
|
|
|
- return this.processSegment(segmentInfo, next);
|
|
|
- }
|
|
|
+HlsStreamRecorder.prototype.process = async function (segmentInfo, done) {
|
|
|
|
|
|
- if (segmentInfo.type === 'init') {
|
|
|
- return this.processInfo(segmentInfo, next);
|
|
|
- }
|
|
|
+ let result;
|
|
|
+ try {
|
|
|
+ if (segmentInfo.type === 'segment') {
|
|
|
+ return await this.processSegment(segmentInfo);
|
|
|
+ }
|
|
|
|
|
|
- debug('unknown segment type: ' + segmentInfo.type);
|
|
|
+ if (segmentInfo.type === 'init') {
|
|
|
+ return await this.processInfo(segmentInfo);
|
|
|
+ }
|
|
|
|
|
|
- return next();
|
|
|
+ debug('unknown segment type: ' + segmentInfo.type);
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ result = err;
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ done(result);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-HlsStreamRecorder.prototype.processInfo = function (segmentInfo, callback) {
|
|
|
+HlsStreamRecorder.prototype.processInfo = async function (segmentInfo) {
|
|
|
|
|
|
const meta = segmentInfo.file;
|
|
|
const uri = `${this.segmentName(this.mapSeq, true)}.${Mime.extension(meta.mime)}`;
|
|
|
|
|
|
- this.writeStream(segmentInfo.stream, uri, meta, (err, bytesWritten) => {
|
|
|
+ this.mapSeq++;
|
|
|
|
|
|
+ let bytesWritten = 0;
|
|
|
+ try {
|
|
|
+ bytesWritten = await this.writeStream(segmentInfo.stream, uri, meta);
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ Bounce.rethrow(err, 'system');
|
|
|
// only to report errors
|
|
|
- if (err) debug('stream error', err.stack || err);
|
|
|
-
|
|
|
- const map = new M3U8Parse.AttrList();
|
|
|
+ debug('stream error', err.stack || err);
|
|
|
+ }
|
|
|
|
|
|
- map.quotedString('uri', uri);
|
|
|
+ const map = new M3U8Parse.AttrList();
|
|
|
|
|
|
- // handle byterange
|
|
|
- if (this.collect) {
|
|
|
- map.quotedString('byterange', `${bytesWritten}@${this.uploader.segmentBytes - bytesWritten}`);
|
|
|
- }
|
|
|
+ map.quotedString('uri', uri);
|
|
|
|
|
|
- this.nextMap = map;
|
|
|
- return callback();
|
|
|
- });
|
|
|
+ // handle byterange
|
|
|
+ if (this.collect) {
|
|
|
+ map.quotedString('byterange', `${bytesWritten}@${this.uploader.segmentBytes - bytesWritten}`);
|
|
|
+ }
|
|
|
|
|
|
- this.mapSeq++;
|
|
|
+ this.nextMap = map;
|
|
|
};
|
|
|
|
|
|
-HlsStreamRecorder.prototype.processSegment = function (segmentInfo, callback) {
|
|
|
+HlsStreamRecorder.prototype.processSegment = async function (segmentInfo) {
|
|
|
|
|
|
let segment = new M3U8Parse.M3U8Segment(segmentInfo.segment.details, true);
|
|
|
let meta = segmentInfo.file;
|
|
|
@@ -236,45 +245,52 @@ HlsStreamRecorder.prototype.processSegment = function (segmentInfo, callback) {
|
|
|
delete segment.byterange;
|
|
|
|
|
|
// save the stream segment
|
|
|
- SegmentDecrypt.decrypt(segmentInfo.stream, segmentInfo.segment.details.keys, this.decrypt, (err, stream, decrypted) => {
|
|
|
+ let stream;
|
|
|
+ try {
|
|
|
+ stream = await SegmentDecrypt.decrypt(segmentInfo.stream, segmentInfo.segment.details.keys, this.decrypt);
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ console.error('decrypt failed', err.stack);
|
|
|
+ stream = segmentInfo.stream;
|
|
|
+ }
|
|
|
|
|
|
- if (err) {
|
|
|
- console.error('decrypt failed', err.stack);
|
|
|
- stream = segmentInfo.stream;
|
|
|
- }
|
|
|
- else if (decrypted) {
|
|
|
- segment.keys = null;
|
|
|
- meta = { mime: meta.mime, modified: meta.modified }; // size is no longer valid
|
|
|
- }
|
|
|
+ if (stream !== segmentInfo.stream) {
|
|
|
+ segment.keys = null;
|
|
|
+ meta = { mime: meta.mime, modified: meta.modified }; // size is no longer valid
|
|
|
+ }
|
|
|
|
|
|
- this.writeStream(stream, segment.uri, meta, (err, bytesWritten) => {
|
|
|
+ this.seq++;
|
|
|
|
|
|
- // only to report errors
|
|
|
- if (err) debug('stream error', err.stack || err);
|
|
|
+ let bytesWritten = 0;
|
|
|
+ try {
|
|
|
+ bytesWritten = await this.writeStream(stream, segment.uri, meta);
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ Bounce.rethrow(err, 'system');
|
|
|
|
|
|
- // handle byterange
|
|
|
- if (this.collect) {
|
|
|
- const isContigious = this.segmentHead > 0 && ((this.segmentHead + bytesWritten) === this.uploader.segmentBytes);
|
|
|
- segment.byterange = {
|
|
|
- length: bytesWritten,
|
|
|
- offset: isContigious ? null : this.uploader.segmentBytes - bytesWritten
|
|
|
- }
|
|
|
+ // only report errors
|
|
|
+ debug('stream error', err.stack || err);
|
|
|
+ }
|
|
|
|
|
|
- this.segmentHead = this.uploader.segmentBytes;
|
|
|
- }
|
|
|
+ // handle byterange
|
|
|
+ if (this.collect) {
|
|
|
+ const isContigious = this.segmentHead > 0 && ((this.segmentHead + bytesWritten) === this.uploader.segmentBytes);
|
|
|
+ segment.byterange = {
|
|
|
+ length: bytesWritten,
|
|
|
+ offset: isContigious ? null : this.uploader.segmentBytes - bytesWritten
|
|
|
+ }
|
|
|
|
|
|
- // update index
|
|
|
- this.index.segments.push(segment);
|
|
|
- this.flushIndex(callback);
|
|
|
- });
|
|
|
+ this.segmentHead = this.uploader.segmentBytes;
|
|
|
+ }
|
|
|
|
|
|
- this.seq++;
|
|
|
- });
|
|
|
+ // update index
|
|
|
+ this.index.segments.push(segment);
|
|
|
+ return this.flushIndex();
|
|
|
};
|
|
|
|
|
|
-HlsStreamRecorder.prototype.writeStream = function (stream, name, meta, callback) {
|
|
|
+HlsStreamRecorder.prototype.writeStream = function (stream, name, meta) {
|
|
|
|
|
|
- this.uploader.pushSegment(stream, name, meta).then((written) => callback(null, written), callback);
|
|
|
+ return this.uploader.pushSegment(stream, name, meta);
|
|
|
};
|
|
|
|
|
|
HlsStreamRecorder.prototype.variantName = function(info, index) {
|
|
|
@@ -302,9 +318,9 @@ HlsStreamRecorder.prototype.segmentName = function(seqNo, isInit) {
|
|
|
return this.collect ? 'stream' : (isInit ? 'init-' : '') + name(seqNo);
|
|
|
};
|
|
|
|
|
|
-HlsStreamRecorder.prototype.flushIndex = function(cb) {
|
|
|
+HlsStreamRecorder.prototype.flushIndex = function() {
|
|
|
|
|
|
- this.uploader.flushIndex(this.index).then(() => cb(), cb);
|
|
|
+ return this.uploader.flushIndex(this.index);
|
|
|
};
|
|
|
|
|
|
HlsStreamRecorder.prototype.recorderForUrl = function(remoteUrl) {
|