polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
706 lines β’ 38.2 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.StreamHandler = void 0;
const base_handler_1 = require("./base.handler");
const stream_service_sdk_1 = require("../services/stream.service.sdk");
const errors_1 = require("../utils/errors");
const formatter_1 = require("../utils/formatter");
const ffmpeg_1 = require("../utils/ffmpeg");
const stream_verification_1 = require("../utils/stream-verification");
const formatter_2 = require("../utils/formatter");
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const api_command_1 = require("../utils/api-command");
class StreamHandler extends base_handler_1.BaseHandler {
constructor(authConfig, serviceConfig) {
super();
this.streamService = new stream_service_sdk_1.StreamServiceSdk(authConfig, serviceConfig);
}
async getStreamKey(options) {
return this.executeWithErrorHandling(async () => {
this.validateGetStreamKeyOptions(options);
const credentials = await this.streamService.getStreamKey({
channelId: options.channelId
});
try {
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId
});
console.log('\nπ Stream Status:');
console.log(` ${(0, formatter_1.formatCompactStatus)(statusInfo)}`);
if (statusInfo.isLive && statusInfo.metrics) {
console.log(` Performance: ${statusInfo.metrics.fps.toFixed(1)} FPS, ${statusInfo.metrics.bandwidthText}`);
}
}
catch (error) {
console.log('\nβ οΈ Status: Unable to retrieve current status');
}
const outputFormat = options.output || 'table';
if (outputFormat === 'json') {
this.displayStreamCredentialsAsJson(credentials);
}
else {
this.displayStreamCredentialsAsTable(credentials);
}
this.displaySecurityWarning();
}, 'stream.getStreamKey');
}
validateGetStreamKeyOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
if (options.output && !['table', 'json'].includes(options.output)) {
throw new errors_1.PolyVValidationError('Output format must be either "table" or "json"', 'output', options.output, 'invalid_value');
}
}
displayStreamCredentialsAsTable(credentials) {
const displayModel = this.createSecureDisplayModel(credentials);
const streamInfo = {
'Channel ID': credentials.channelId,
'RTMP URL': credentials.rtmpUrl,
'Stream Key': displayModel.streamKey,
'Deploy Address': credentials.deployAddress || '-',
'Input Address': credentials.inAddress || '-'
};
console.log('\nπ‘ Stream Information:');
this.displayData(streamInfo, 'table');
if (credentials.metrics.fps > 0 || credentials.metrics.bandwidth > 0) {
const performanceInfo = {
'FPS': credentials.metrics.fps.toFixed(2),
'LFR': credentials.metrics.lfr.toFixed(2),
'Bandwidth': (0, formatter_2.formatBandwidth)(credentials.metrics.bandwidth)
};
console.log('\nπ Performance Metrics:');
this.displayData(performanceInfo, 'table');
}
if (displayModel.isMasked) {
console.log('\nπ‘ Stream key is partially hidden for security. Use --output json to see full credentials.');
}
}
displayStreamCredentialsAsJson(credentials) {
console.log('\nπ‘ Stream Information (Full):');
console.log(JSON.stringify(credentials, null, 2));
}
createSecureDisplayModel(credentials) {
const streamKey = credentials.streamKey;
let maskedStreamKey = streamKey;
let isMasked = false;
if (streamKey && streamKey.length > 8) {
const prefix = streamKey.substring(0, 4);
const suffix = streamKey.substring(streamKey.length - 4);
const maskLength = streamKey.length - 8;
const mask = '*'.repeat(maskLength);
maskedStreamKey = `${prefix}${mask}${suffix}`;
isMasked = true;
}
return {
channelId: credentials.channelId,
rtmpUrl: credentials.rtmpUrl,
streamKey: maskedStreamKey,
isMasked,
streamInfo: {
fps: credentials.metrics.fps,
bandwidth: credentials.metrics.bandwidth
}
};
}
async startStream(options) {
return this.executeWithErrorHandling(async () => {
this.validateStartStreamOptions(options);
const response = await this.streamService.startStream({
channelId: options.channelId
});
if (response.data === 'success') {
this.displaySuccess(`Stream started successfully for channel ${options.channelId}`);
try {
await this.displayEnhancedStatus(options.channelId);
}
catch (error) {
this.displayStreamStatus(options.channelId, 'live');
}
}
else {
this.displayError(`Failed to start stream for channel ${options.channelId}`);
}
}, 'stream.startStream');
}
validateStartStreamOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
}
displayStreamStatus(channelId, status) {
const statusInfo = {
'Channel ID': channelId,
'Stream Status': status,
'Timestamp': new Date().toISOString()
};
console.log('\nπ Stream Status:');
this.displayData(statusInfo, 'table');
}
async stopStream(options) {
return this.executeWithErrorHandling(async () => {
this.validateStopStreamOptions(options);
const response = await this.streamService.stopStream({
channelId: options.channelId
});
if (response.data === 'success') {
this.displaySuccess(`Stream stopped successfully for channel ${options.channelId}`);
try {
await this.displayEnhancedStatus(options.channelId);
}
catch (error) {
this.displayStreamStatus(options.channelId, 'stopped');
}
}
else {
this.displayError(`Failed to stop stream for channel ${options.channelId}`);
}
}, 'stream.stopStream');
}
validateStopStreamOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
}
async getStreamStatus(options) {
return this.executeWithErrorHandling(async () => {
this.validateStreamStatusOptions(options);
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId
});
const outputFormat = options.output || 'table';
if (outputFormat === 'json') {
this.displayStreamStatusAsJson(statusInfo);
}
else {
this.displayStreamStatusAsTable(statusInfo);
}
}, 'stream.getStreamStatus');
}
async getHlsPullUrl(options) {
return this.executeWithErrorHandling(async () => {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
const hlsPullUrl = await this.streamService.getHlsPullUrl(options.channelId);
this.displayData({
channelId: options.channelId,
hlsPullUrl,
}, options.output || 'table');
}, 'stream.getHlsPullUrl');
}
validateStreamStatusOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
if (options.output && !['table', 'json'].includes(options.output)) {
throw new errors_1.PolyVValidationError('Output format must be either "table" or "json"', 'output', options.output, 'invalid_value');
}
}
displayStreamStatusAsTable(statusInfo) {
console.log('\nπ Stream Status Information:');
const formattedStatus = (0, formatter_1.formatStreamStatusForTable)(statusInfo);
this.displayData(formattedStatus, 'table');
if (statusInfo.isLive && statusInfo.metrics) {
console.log('\nπ‘ Stream is currently live and broadcasting');
if (statusInfo.network && statusInfo.network.streamName) {
console.log(` Stream Name: ${statusInfo.network.streamName}`);
}
}
else if (statusInfo.status === 'waiting') {
console.log('\nβ³ Stream is ready but not yet started');
}
else if (statusInfo.status === 'stopped') {
console.log('\nβΉοΈ Stream has ended');
}
else if (statusInfo.status === 'error') {
console.log('\nβ Stream has an error condition');
}
}
displayStreamStatusAsJson(statusInfo) {
console.log('\nπ Stream Status Information (Full):');
const formattedStatus = (0, formatter_1.formatStreamStatusForJson)(statusInfo);
console.log(JSON.stringify(formattedStatus, null, 2));
}
async displayEnhancedStatus(channelId) {
const statusInfo = await this.streamService.getStreamStatus({
channelId
});
console.log('\nπ Current Status:');
const compactStatus = (0, formatter_1.formatCompactStatus)(statusInfo);
console.log(` ${compactStatus}`);
if (statusInfo.isLive && statusInfo.metrics) {
console.log(` Performance: ${statusInfo.metrics.fps.toFixed(1)} FPS, ${statusInfo.metrics.bandwidthText}`);
}
if (statusInfo.error) {
console.log(` β οΈ Error: ${statusInfo.error.message}`);
}
}
displaySecurityWarning() {
console.log('\nπ Security Notice:');
console.log(' β’ Keep your stream key confidential');
console.log(' β’ Do not share stream credentials in public channels');
console.log(' β’ Regenerate stream key if compromised');
}
async pushStream(options) {
return this.executeWithErrorHandling(async () => {
this.validatePushStreamOptions(options);
console.log('π Checking FFmpeg installation...');
const ffmpegInstalled = await (0, ffmpeg_1.isFFmpegInstalled)();
if (!ffmpegInstalled) {
throw new Error('FFmpeg is not installed or not found in PATH. Please install FFmpeg to use this feature.');
}
console.log('β
FFmpeg is available');
console.log('π Getting stream credentials...');
const credentials = await this.streamService.getStreamKey({
channelId: options.channelId
});
console.log('β
Stream credentials retrieved');
if (options.verify) {
const interval = options.verificationInterval || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.INTERVAL;
const threshold = options.qualityThreshold || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.QUALITY_THRESHOLD;
console.log('π Verification mode enabled:');
console.log(` Interval: ${interval}s, Quality threshold: ${threshold} FPS`);
}
if (options.showViewerLinks || options.verify) {
console.log('π Viewer Links:');
const viewerLinks = (0, stream_verification_1.generateViewerLinks)(options.channelId);
viewerLinks.forEach(link => {
console.log(` β’ ${link.protocol}: ${link.url}`);
});
console.log('');
}
const rtmpUrl = `${credentials.rtmpUrl}/${credentials.streamKey}`;
const ffmpegArgs = [
'-re',
'-i', options.file,
'-c:v', 'copy',
'-c:a', 'aac',
'-f', 'flv',
rtmpUrl
];
console.log('π¬ Starting stream push...');
console.log(` File: ${options.file}`);
console.log(` Channel: ${options.channelId}`);
console.log(' Press Ctrl+C to stop streaming');
console.log('');
const ffmpegProcess = (0, child_process_1.spawn)('ffmpeg', ffmpegArgs);
let verificationInterval = null;
let verificationCheckCount = 0;
if (options.verify) {
const interval = (options.verificationInterval || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.INTERVAL) * 1000;
const threshold = options.qualityThreshold || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.QUALITY_THRESHOLD;
verificationInterval = setInterval(async () => {
try {
verificationCheckCount++;
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId
});
const verificationPoint = (0, stream_verification_1.createVerificationPoint)(verificationCheckCount, statusInfo, threshold);
const statusEmoji = (0, stream_verification_1.getStatusEmoji)(verificationPoint.status);
console.log(`\nβ±οΈ Verification Check #${verificationCheckCount} (${this.formatDuration(verificationCheckCount * interval / 1000)}):`);
console.log(` Status: ${statusEmoji} ${verificationPoint.status} | FPS: ${(0, stream_verification_1.formatFPS)(verificationPoint.metrics.fps)} | Bandwidth: ${(0, formatter_2.formatBandwidth)(verificationPoint.metrics.bandwidth)}`);
if (verificationPoint.issues.length > 0) {
verificationPoint.issues.forEach(issue => {
console.log(` Issue: ${issue.message}`);
});
}
}
catch (error) {
console.log(` β οΈ Verification check failed: ${error instanceof Error ? error.message : String(error)}`);
}
}, interval);
if (process.env['NODE_ENV'] !== 'test' && verificationInterval?.unref) {
verificationInterval.unref();
}
}
ffmpegProcess.stdout.on('data', (data) => {
process.stdout.write(data);
});
ffmpegProcess.stderr.on('data', (data) => {
process.stderr.write(data);
});
return new Promise((resolve, reject) => {
ffmpegProcess.on('close', (code) => {
if (verificationInterval) {
clearInterval(verificationInterval);
}
if (code === 0) {
console.log('\nβ
Stream completed successfully');
if (options.verify && verificationCheckCount > 0) {
console.log(`π Verification Summary: ${verificationCheckCount} checks performed`);
}
resolve();
}
else if (code === null) {
console.log('\nβΉοΈ Stream stopped by user');
if (options.verify && verificationCheckCount > 0) {
console.log(`π Verification Summary: ${verificationCheckCount} checks performed`);
}
resolve();
}
else {
console.log(`\nβ FFmpeg process exited with code ${code}`);
reject(new Error(`FFmpeg process failed with exit code ${code}`));
}
});
ffmpegProcess.on('error', (error) => {
if (verificationInterval) {
clearInterval(verificationInterval);
}
console.log(`\nβ FFmpeg process error: ${error.message}`);
reject(error);
});
const sigintHandler = () => {
console.log('\nβΉοΈ Stopping stream...');
if (verificationInterval) {
clearInterval(verificationInterval);
}
ffmpegProcess.kill('SIGINT');
process.removeListener('SIGINT', sigintHandler);
};
process.on('SIGINT', sigintHandler);
});
}, 'stream.pushStream');
}
validatePushStreamOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
if (!options.file || typeof options.file !== 'string' || options.file.trim() === '') {
throw new errors_1.PolyVValidationError('File path cannot be empty', 'file', options.file, 'required');
}
if (!fs.existsSync(options.file)) {
throw new errors_1.PolyVValidationError(`File not found: ${options.file}`, 'file', options.file, 'not_found');
}
}
async verifyStream(options) {
return this.executeWithErrorHandling(async () => {
this.validateVerifyStreamOptions(options);
const duration = options.duration || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.DURATION;
const interval = options.interval || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.INTERVAL;
const threshold = options.qualityThreshold || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.QUALITY_THRESHOLD;
const expectedChecks = Math.floor(duration / interval);
console.log(`π Starting stream verification for channel ${options.channelId}...`);
console.log(` Duration: ${duration}s | Interval: ${interval}s | Expected checks: ${expectedChecks}`);
console.log('');
const verificationId = (0, stream_verification_1.generateVerificationId)();
const startTime = new Date();
const verificationPoints = [];
if (options.showViewerLinks) {
console.log('π Viewer Links:');
const viewerLinks = (0, stream_verification_1.generateViewerLinks)(options.channelId);
viewerLinks.forEach(link => {
console.log(` β’ ${link.protocol}: ${link.url}`);
});
console.log('');
}
console.log('π Verification Progress:');
for (let i = 0; i < expectedChecks; i++) {
const checkNumber = i + 1;
const elapsedTime = checkNumber * interval;
try {
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId
});
const verificationPoint = (0, stream_verification_1.createVerificationPoint)(checkNumber, statusInfo, threshold);
verificationPoints.push(verificationPoint);
const statusEmoji = (0, stream_verification_1.getStatusEmoji)(verificationPoint.status);
const timeStr = this.formatDuration(elapsedTime);
console.log(` ${checkNumber.toString().padStart(2)}. ${timeStr} | ${statusEmoji} ${verificationPoint.status.padEnd(7)} | FPS: ${(0, stream_verification_1.formatFPS)(verificationPoint.metrics.fps).padEnd(8)} | Bandwidth: ${(0, formatter_2.formatBandwidth)(verificationPoint.metrics.bandwidth).padEnd(10)} | ${statusEmoji}`);
if (verificationPoint.issues.length > 0) {
verificationPoint.issues.forEach(issue => {
console.log(` Issue: ${issue.message}`);
});
}
}
catch (error) {
console.log(` ${checkNumber.toString().padStart(2)}. ${this.formatDuration(elapsedTime)} | β Error | Failed to get status: ${error instanceof Error ? error.message : String(error)}`);
}
if (i < expectedChecks - 1) {
await this.sleep(interval * 1000);
}
}
const result = (0, stream_verification_1.createVerificationResult)(options.channelId, verificationId, startTime, verificationPoints, threshold);
console.log('\nπ Verification Summary:');
console.log(` β’ Overall Status: ${result.summary.overallStatus} (${result.summary.reliability.toFixed(0)}% reliability)`);
console.log(` β’ Average FPS: ${(0, stream_verification_1.formatFPS)(result.averageMetrics.fps)} (Target: >${threshold} FPS)`);
console.log(` β’ Average Bandwidth: ${(0, formatter_2.formatBandwidth)(result.averageMetrics.bandwidth)}`);
console.log(` β’ Issues Found: ${result.summary.totalIssues}`);
if (result.summary.recommendations.length > 0) {
console.log(' β’ Recommendations:');
result.summary.recommendations.forEach(rec => {
console.log(` - ${rec}`);
});
}
if (options.saveReport) {
const { saveVerificationReport, createVerificationReport } = await Promise.resolve().then(() => __importStar(require('../utils/stream-verification')));
const report = createVerificationReport(result, duration, interval);
report.timeline = verificationPoints;
await saveVerificationReport(report, options.saveReport);
console.log(`\nπΎ Verification report saved to: ${options.saveReport}`);
}
if (options.output === 'json') {
console.log('\nπ JSON Report:');
console.log(JSON.stringify(result, null, 2));
}
}, 'stream.verifyStream');
}
async monitorStream(options) {
return this.executeWithErrorHandling(async () => {
this.validateMonitorStreamOptions(options);
const refresh = options.refresh || stream_verification_1.DEFAULT_VERIFICATION_SETTINGS.MONITOR_REFRESH;
if (options.output === 'json') {
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId,
});
this.displayData(statusInfo, 'json');
return;
}
console.log(`π Stream Monitor - Channel: ${options.channelId} (Refreshing every ${refresh}s)`);
console.log('Press Ctrl+C to stop monitoring\n');
let monitorInterval = null;
let activityLog = [];
const performCheck = async () => {
try {
const statusInfo = await this.streamService.getStreamStatus({
channelId: options.channelId
});
process.stdout.write('\x1Bc');
console.log(`π Stream Monitor - Channel: ${options.channelId} (Refreshing every ${refresh}s)`);
console.log('Press Ctrl+C to stop monitoring\n');
const statusEmoji = statusInfo.isLive ? 'β
' : 'βΈοΈ';
const status = statusInfo.isLive ? 'Live and Healthy' : statusInfo.statusText;
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log(`β Current Status: ${statusEmoji} ${status.padEnd(40)} β`);
if (statusInfo.isLive && statusInfo.metrics) {
const duration = statusInfo.durationText || 'Unknown';
const fps = (0, stream_verification_1.formatFPS)(statusInfo.metrics.fps);
const bandwidth = (0, formatter_2.formatBandwidth)(statusInfo.metrics.bandwidth);
const latency = statusInfo.metrics.lfr ? `${statusInfo.metrics.lfr.toFixed(1)}% LFR` : 'N/A';
console.log(`β Uptime: ${duration.padEnd(20)} | Viewers: Unknown${' '.padEnd(14)} β`);
console.log(`β FPS: ${fps.padEnd(8)} | Bandwidth: ${bandwidth.padEnd(10)} | Latency: ${latency.padEnd(8)} β`);
}
else {
console.log(`β Stream is not currently live${' '.padEnd(32)} β`);
}
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
if (options.alerts && statusInfo.isLive && statusInfo.metrics) {
const qualityIssues = (0, stream_verification_1.analyzeQualityMetrics)({
fps: statusInfo.metrics.fps,
bandwidth: statusInfo.metrics.bandwidth,
lfr: statusInfo.metrics.lfr
});
if (qualityIssues.length > 0) {
console.log('β οΈ Quality Alerts:');
qualityIssues.forEach(issue => {
const severityEmoji = (0, stream_verification_1.getSeverityEmoji)(issue.severity);
console.log(` ${severityEmoji} ${issue.message}`);
});
console.log('');
}
}
console.log('Recent Activity:');
const currentTime = new Date().toLocaleTimeString();
const newActivity = ` ${currentTime} - Stream status: ${status}`;
activityLog.unshift(newActivity);
if (activityLog.length > 5) {
activityLog = activityLog.slice(0, 5);
}
activityLog.forEach(activity => console.log(activity));
console.log(`\nβ±οΈ Last updated: ${currentTime}`);
console.log(`π Refreshing in ${refresh} seconds...`);
}
catch (error) {
console.log(`β Error monitoring stream: ${error instanceof Error ? error.message : String(error)}`);
}
};
await performCheck();
monitorInterval = setInterval(performCheck, refresh * 1000);
if (process.env['NODE_ENV'] !== 'test' && monitorInterval?.unref) {
monitorInterval.unref();
}
return new Promise((resolve) => {
const sigintHandler = () => {
if (monitorInterval) {
clearInterval(monitorInterval);
}
console.log('\n\nπ Monitoring stopped. Goodbye!');
process.removeListener('SIGINT', sigintHandler);
resolve();
};
process.on('SIGINT', sigintHandler);
});
}, 'stream.monitorStream');
}
async getLiveStatus(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['stream']);
const result = await this.streamService.getLiveStatus(options.stream);
this.displayData({ stream: options.stream, status: result }, options.output || 'table');
}, 'stream.live-status.get');
}
async getLiveStatusList(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelIds']);
const result = await this.streamService.getLiveStatusList({ channelIds: options.channelIds });
this.displayData(result, options.output || 'table');
}, 'stream.live-status.list');
}
async getStreams(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelIds']);
const result = await this.streamService.getStreams({ channelIds: options.channelIds });
this.displayData(result, options.output || 'table');
}, 'stream.streams.list');
}
async listDiskVideo(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId']);
const result = await this.streamService.listDiskVideo(this.compactOptions(options, ['output', 'force']));
this.displayData(result, options.output || 'table');
}, 'stream.disk-video.list');
}
async getCaptureImage(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId']);
const result = await this.streamService.getCaptureImage(options.channelId);
this.displayData({ channelId: options.channelId, captureImage: result }, options.output || 'table');
}, 'stream.capture.get');
}
async addDiskVideos(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId', 'vids']);
await (0, api_command_1.confirmWrite)(options.force, `Configure disk video(s) for channel ${options.channelId}?`);
const result = await this.streamService.addDiskVideos(this.compactOptions(options, ['output', 'force']));
this.displayWriteResult('Disk videos configured successfully', result, options.output);
}, 'stream.disk-video.add');
}
async deleteDiskVideos(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId']);
if (!options.vids && !options.videoIds) {
throw new errors_1.PolyVValidationError('vids or videoIds is required', 'options', options, 'validation_failed');
}
await (0, api_command_1.confirmWrite)(options.force, `Delete disk video(s) from channel ${options.channelId}?`);
const result = await this.streamService.deleteDiskVideos(this.compactOptions(options, ['output', 'force']));
this.displayWriteResult('Disk video(s) deleted successfully', result, options.output);
}, 'stream.disk-video.delete');
}
async endDiskPush(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId', 'diskVideoId']);
await (0, api_command_1.confirmWrite)(options.force, `Stop disk push for channel ${options.channelId}?`);
const result = await this.streamService.endDiskPush(this.compactOptions(options, ['output', 'force']));
this.displayWriteResult('Disk push stopped successfully', result, options.output);
}, 'stream.disk-video.end');
}
async banPush(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId', 'userId']);
await (0, api_command_1.confirmWrite)(options.force, `Ban push stream for channel ${options.channelId}?`);
const result = await this.streamService.banPush(this.compactOptions(options, ['output', 'force']));
this.displayWriteResult('Push stream banned successfully', result, options.output);
}, 'stream.ban-push');
}
async resumePush(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId', 'userId']);
await (0, api_command_1.confirmWrite)(options.force, `Resume push stream for channel ${options.channelId}?`);
const result = await this.streamService.resume({ channelId: options.channelId, userId: options.userId });
this.displayWriteResult('Push stream resumed successfully', result, options.output);
}, 'stream.resume');
}
async updateStreamType(options) {
return this.executeWithErrorHandling(async () => {
this.requireFields(options, ['channelId', 'streamType']);
await (0, api_command_1.confirmWrite)(options.force, `Update stream type for channel ${options.channelId}?`);
const result = await this.streamService.updateStreamType(this.compactOptions(options, ['output', 'force']));
this.displayWriteResult('Stream type updated successfully', result, options.output);
}, 'stream.type.update');
}
requireFields(options, fields) {
const missing = fields.filter((field) => {
const value = options[field];
return value === undefined || value === null || value === '';
});
if (missing.length > 0) {
throw new errors_1.PolyVValidationError(`Missing required option(s): ${missing.join(', ')}`, 'options', options, 'validation_failed');
}
}
compactOptions(options, skip = []) {
const skipped = new Set(skip);
return Object.fromEntries(Object.entries(options).filter(([key, value]) => !skipped.has(key) && value !== undefined && value !== ''));
}
displayWriteResult(message, data, output) {
if (output === 'json') {
this.displayData({ success: true, data }, 'json');
}
else {
this.displaySuccess(message, data, 'table');
}
}
validateVerifyStreamOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
if (options.duration && (options.duration < 10 || options.duration > 3600)) {
throw new errors_1.PolyVValidationError('Duration must be between 10 and 3600 seconds', 'duration', options.duration, 'out_of_range');
}
if (options.interval && (options.interval < 5 || options.interval > 300)) {
throw new errors_1.PolyVValidationError('Interval must be between 5 and 300 seconds', 'interval', options.interval, 'out_of_range');
}
}
validateMonitorStreamOptions(options) {
if (!options.channelId || typeof options.channelId !== 'string' || options.channelId.trim() === '') {
throw new errors_1.PolyVValidationError('Channel ID cannot be empty', 'channelId', options.channelId, 'required');
}
if (options.refresh !== undefined && (options.refresh < 1 || options.refresh > 60)) {
throw new errors_1.PolyVValidationError('Refresh interval must be between 1 and 60 seconds', 'refresh', options.refresh, 'out_of_range');
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
formatDuration(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
}
exports.StreamHandler = StreamHandler;
//# sourceMappingURL=stream.handler.js.map