polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
304 lines • 11.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_VERIFICATION_SETTINGS = exports.DEFAULT_QUALITY_THRESHOLDS = void 0;
exports.generateVerificationId = generateVerificationId;
exports.extractQualityMetrics = extractQualityMetrics;
exports.analyzeQualityMetrics = analyzeQualityMetrics;
exports.createVerificationPoint = createVerificationPoint;
exports.calculateAverageMetrics = calculateAverageMetrics;
exports.generateVerificationSummary = generateVerificationSummary;
exports.generateViewerLinks = generateViewerLinks;
exports.createVerificationResult = createVerificationResult;
exports.createVerificationReport = createVerificationReport;
exports.saveVerificationReport = saveVerificationReport;
exports.formatFPS = formatFPS;
exports.formatLatency = formatLatency;
exports.getStatusEmoji = getStatusEmoji;
exports.getSeverityEmoji = getSeverityEmoji;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const formatter_1 = require("./formatter");
exports.DEFAULT_QUALITY_THRESHOLDS = {
FPS_MINIMUM: 15,
BANDWIDTH_MINIMUM: 1000000,
LATENCY_MAXIMUM: 5000,
LFR_MAXIMUM: 5
};
exports.DEFAULT_VERIFICATION_SETTINGS = {
DURATION: 60,
INTERVAL: 10,
QUALITY_THRESHOLD: exports.DEFAULT_QUALITY_THRESHOLDS.FPS_MINIMUM,
MONITOR_REFRESH: 5
};
function generateVerificationId() {
const timestamp = Date.now().toString();
const random = crypto.randomBytes(4).toString('hex');
return `verify_${timestamp}_${random}`;
}
function extractQualityMetrics(statusInfo) {
const metrics = {
fps: statusInfo.metrics?.fps || 0,
bandwidth: statusInfo.metrics?.bandwidth || 0,
lfr: statusInfo.metrics?.lfr || 0
};
return metrics;
}
function analyzeQualityMetrics(metrics, thresholds = exports.DEFAULT_QUALITY_THRESHOLDS) {
const issues = [];
const timestamp = new Date();
if (metrics.fps < thresholds.FPS_MINIMUM) {
issues.push({
timestamp,
type: 'fps_low',
severity: metrics.fps < thresholds.FPS_MINIMUM / 2 ? 'critical' : 'warning',
message: `FPS is below threshold: ${metrics.fps.toFixed(1)} < ${thresholds.FPS_MINIMUM}`,
metrics: { fps: metrics.fps }
});
}
if (metrics.bandwidth < thresholds.BANDWIDTH_MINIMUM) {
issues.push({
timestamp,
type: 'bandwidth_low',
severity: metrics.bandwidth < thresholds.BANDWIDTH_MINIMUM / 2 ? 'critical' : 'warning',
message: `Bandwidth is below threshold: ${(0, formatter_1.formatBandwidth)(metrics.bandwidth)} < ${(0, formatter_1.formatBandwidth)(thresholds.BANDWIDTH_MINIMUM)}`,
metrics: { bandwidth: metrics.bandwidth }
});
}
if (metrics.latency && metrics.latency > thresholds.LATENCY_MAXIMUM) {
issues.push({
timestamp,
type: 'other',
severity: metrics.latency > thresholds.LATENCY_MAXIMUM * 2 ? 'critical' : 'warning',
message: `Latency is above threshold: ${metrics.latency}ms > ${thresholds.LATENCY_MAXIMUM}ms`,
metrics: { latency: metrics.latency }
});
}
if (metrics.lfr && metrics.lfr > thresholds.LFR_MAXIMUM) {
issues.push({
timestamp,
type: 'other',
severity: metrics.lfr > thresholds.LFR_MAXIMUM * 2 ? 'critical' : 'warning',
message: `Frame loss rate is above threshold: ${metrics.lfr.toFixed(1)}% > ${thresholds.LFR_MAXIMUM}%`,
metrics: { lfr: metrics.lfr }
});
}
return issues;
}
function createVerificationPoint(checkNumber, statusInfo, qualityThreshold = exports.DEFAULT_QUALITY_THRESHOLDS.FPS_MINIMUM) {
const metrics = extractQualityMetrics(statusInfo);
const issues = analyzeQualityMetrics(metrics, {
...exports.DEFAULT_QUALITY_THRESHOLDS,
FPS_MINIMUM: qualityThreshold
});
let status;
if (issues.length === 0) {
status = 'healthy';
}
else if (issues.some(issue => issue.severity === 'critical')) {
status = 'error';
}
else {
status = 'warning';
}
return {
checkNumber,
timestamp: new Date(),
status,
metrics,
issues
};
}
function calculateAverageMetrics(points) {
if (points.length === 0) {
return { fps: 0, bandwidth: 0, lfr: 0 };
}
const totals = points.reduce((acc, point) => ({
fps: acc.fps + point.metrics.fps,
bandwidth: acc.bandwidth + point.metrics.bandwidth,
lfr: acc.lfr + (point.metrics.lfr || 0),
latency: acc.latency + (point.metrics.latency || 0)
}), { fps: 0, bandwidth: 0, lfr: 0, latency: 0 });
const count = points.length;
return {
fps: totals.fps / count,
bandwidth: totals.bandwidth / count,
lfr: totals.lfr / count,
latency: totals.latency / count
};
}
function generateVerificationSummary(points, qualityThreshold = exports.DEFAULT_QUALITY_THRESHOLDS.FPS_MINIMUM) {
if (points.length === 0) {
return {
overallStatus: 'poor',
reliability: 0,
averageQuality: { fps: 0, bandwidth: 0 },
totalIssues: 0,
recommendations: ['No data available for analysis']
};
}
const healthyChecks = points.filter(p => p.status === 'healthy').length;
const reliability = (healthyChecks / points.length) * 100;
const allIssues = points.flatMap(p => p.issues);
const criticalIssues = allIssues.filter(i => i.severity === 'critical').length;
let overallStatus;
if (reliability >= 95 && criticalIssues === 0) {
overallStatus = 'excellent';
}
else if (reliability >= 80 && criticalIssues <= 1) {
overallStatus = 'good';
}
else if (reliability >= 60 && criticalIssues <= 3) {
overallStatus = 'fair';
}
else {
overallStatus = 'poor';
}
const averageQuality = calculateAverageMetrics(points);
const recommendations = [];
if (averageQuality.fps < qualityThreshold) {
recommendations.push('Consider increasing source video quality or encoding settings');
}
if (averageQuality.bandwidth < exports.DEFAULT_QUALITY_THRESHOLDS.BANDWIDTH_MINIMUM) {
recommendations.push('Check network connection and increase bitrate');
}
if (reliability < 90) {
recommendations.push('Monitor network stability and FFmpeg configuration');
}
if (criticalIssues > 0) {
recommendations.push('Address critical quality issues immediately');
}
if (recommendations.length === 0) {
recommendations.push('Stream quality is performing well');
}
return {
overallStatus,
reliability,
averageQuality,
totalIssues: allIssues.length,
recommendations
};
}
function generateViewerLinks(channelId) {
const links = [
{
protocol: 'HLS',
url: `https://player.polyv.net/live/${channelId}`,
description: 'Web player (recommended for browsers)'
},
{
protocol: 'RTMP',
url: `rtmp://live.polyv.net/live/${channelId}`,
description: 'RTMP stream (for media players)'
},
{
protocol: 'HTTP-FLV',
url: `https://live.polyv.net/live/${channelId}.flv`,
description: 'HTTP-FLV stream (low latency)'
}
];
return links;
}
function createVerificationResult(channelId, verificationId, startTime, points, qualityThreshold = exports.DEFAULT_QUALITY_THRESHOLDS.FPS_MINIMUM) {
const endTime = new Date();
const successfulChecks = points.filter(p => p.status === 'healthy').length;
const failedChecks = points.length - successfulChecks;
const averageMetrics = calculateAverageMetrics(points);
const qualityIssues = points.flatMap(p => p.issues);
const viewerLinks = generateViewerLinks(channelId);
const summary = generateVerificationSummary(points, qualityThreshold);
return {
channelId,
verificationId,
startTime,
endTime,
totalChecks: points.length,
successfulChecks,
failedChecks,
averageMetrics,
qualityIssues,
viewerLinks,
summary
};
}
function createVerificationReport(result, testDuration, testInterval, version = '1.0.0') {
return {
metadata: {
channelId: result.channelId,
testDuration,
testInterval,
timestamp: new Date(),
version
},
summary: result.summary,
timeline: [],
issues: result.qualityIssues,
viewerLinks: result.viewerLinks
};
}
async function saveVerificationReport(report, filePath) {
try {
const jsonContent = JSON.stringify(report, null, 2);
await fs.promises.writeFile(filePath, jsonContent, 'utf8');
}
catch (error) {
throw new Error(`Failed to save verification report: ${error instanceof Error ? error.message : String(error)}`);
}
}
function formatFPS(fps) {
return `${fps.toFixed(1)} FPS`;
}
function formatLatency(latency) {
if (!latency)
return 'N/A';
return `${latency.toFixed(0)}ms`;
}
function getStatusEmoji(status) {
switch (status) {
case 'healthy': return '✅';
case 'warning': return '⚠️';
case 'error': return '❌';
default: return '❓';
}
}
function getSeverityEmoji(severity) {
switch (severity) {
case 'warning': return '⚠️';
case 'error': return '❌';
case 'critical': return '🚨';
default: return '❓';
}
}
//# sourceMappingURL=stream-verification.js.map