UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

304 lines 11.1 kB
"use strict"; 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