@eleven-am/transcoder
Version:
High-performance HLS transcoding library with hardware acceleration, intelligent client management, and distributed processing support for Node.js
169 lines • 6.69 kB
JavaScript
"use strict";
/*
* @eleven-am/transcoder
* Copyright (C) 2025 Roy OSSAI
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SegmentPipelineProcessor = void 0;
const events_1 = require("events");
const fp_1 = require("@eleven-am/fp");
/**
* Pipelined segment processor that allows overlapping segment processing
* for improved throughput and reduced latency
*/
class SegmentPipelineProcessor extends events_1.EventEmitter {
constructor(maxPipelineDepth = 3) {
super();
this.pipeline = new Map();
this.processingTimes = [];
this.completedCount = 0;
this.failedCount = 0;
this.maxProcessingTimes = 100;
this.handleSegmentSuccess = (segmentNumber, startTime) => fp_1.TaskEither.of(null).ioSync(() => {
this.completedCount++;
const processingTime = Date.now() - startTime;
this.recordProcessingTime(processingTime);
this.emit('segment:completed', { segmentNumber, processingTime });
});
this.handleSegmentFailure = (segmentNumber) => fp_1.TaskEither.of(null).ioSync(() => {
this.failedCount++;
this.emit('segment:failed', { segmentNumber });
});
this.cleanupSegment = (segmentNumber) => {
this.pipeline.delete(segmentNumber);
this.emit('pipeline:updated', this.getMetrics());
};
this.maxPipelineDepth = maxPipelineDepth;
}
/**
* Process a segment with pipeline management
*/
async processSegment(segmentNumber, processor, data) {
// Wait for pipeline space
await this.waitForPipelineSpace();
const startTime = Date.now();
// Create processing task
const processingTask = fp_1.TaskEither
.tryCatch(() => processor(data), 'Failed to process segment')
.io(() => this.handleSegmentSuccess(segmentNumber, startTime))
.ioError(() => this.handleSegmentFailure(segmentNumber))
.toPromise()
.finally(() => this.cleanupSegment(segmentNumber));
// Track in pipeline
this.pipeline.set(segmentNumber, processingTask);
this.emit('segment:started', { segmentNumber });
return fp_1.TaskEither.tryCatch(() => processingTask, 'Failed to process segment in pipeline');
}
/**
* Process multiple segments in batches with pipeline optimization
*/
async processBatch(segments, processor) {
const processSegmentTask = (segment) => this.processSegment(segment.number, processor, segment.data)
.then(task => task.toPromise())
.then(result => ({ index: segment.number, result }))
.catch(error => ({ index: segment.number, error }));
return fp_1.TaskEither.of(segments)
.chain(segs => fp_1.TaskEither.tryCatch(() => Promise.all(segs.map(processSegmentTask)), 'Failed to process batch segments'))
.map(results => results.reduce((acc, item) => {
if ('error' in item) {
acc.failures.push(item);
}
else {
acc.successes.push(item);
}
return acc;
}, { successes: [], failures: [] }))
.filter(({ failures }) => failures.length === 0, ({ failures }) => (0, fp_1.createInternalError)(`Failed to process ${failures.length} segments`))
.map(({ successes }) => {
const results = [];
successes.forEach(({ index, result }) => {
results[index] = result;
});
return results;
});
}
/**
* Wait for all segments in pipeline to complete
*/
async waitForPipeline() {
await Promise.all(Array.from(this.pipeline.values()));
}
/**
* Get current pipeline metrics
*/
getMetrics() {
return {
activeSegments: this.pipeline.size,
completedSegments: this.completedCount,
failedSegments: this.failedCount,
averageProcessingTime: this.calculateAverageProcessingTime(),
pipelineDepth: this.maxPipelineDepth,
};
}
/**
* Dynamically adjust pipeline depth based on performance
*/
adjustPipelineDepth(cpuUsage, memoryUsage) {
const currentDepth = this.maxPipelineDepth;
let newDepth = currentDepth;
// Reduce depth if system is under stress
if (cpuUsage > 0.8 || memoryUsage > 0.8) {
newDepth = Math.max(1, currentDepth - 1);
}
else if (cpuUsage < 0.5 && memoryUsage < 0.5) {
// Increase depth if system has capacity
newDepth = Math.min(6, currentDepth + 1);
}
if (newDepth !== currentDepth) {
this.emit('pipeline:depthChanged', {
oldDepth: currentDepth,
newDepth,
reason: cpuUsage > 0.8 ? 'high_cpu' : memoryUsage > 0.8 ? 'high_memory' : 'low_usage',
});
}
}
/**
* Clear the pipeline and reset metrics
*/
clear() {
this.pipeline.clear();
this.processingTimes.length = 0;
this.completedCount = 0;
this.failedCount = 0;
}
// Helper methods
async waitForPipelineSpace() {
while (this.pipeline.size >= this.maxPipelineDepth) {
await Promise.race(Array.from(this.pipeline.values()));
}
}
recordProcessingTime(time) {
this.processingTimes.push(time);
// Keep only recent processing times
if (this.processingTimes.length > this.maxProcessingTimes) {
this.processingTimes.shift();
}
}
calculateAverageProcessingTime() {
if (this.processingTimes.length === 0) {
return 0;
}
const sum = this.processingTimes.reduce((a, b) => a + b, 0);
return Math.round(sum / this.processingTimes.length);
}
}
exports.SegmentPipelineProcessor = SegmentPipelineProcessor;
//# sourceMappingURL=segmentPipelineProcessor.js.map