UNPKG

homebridge-ezviz

Version:

EZVIZ plugin for homebridge: https://homebridge.io/

289 lines 12.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamingDelegate = void 0; const endpoints_1 = require("./ezviz/endpoints"); const rtp_1 = require("./util/rtp"); const camera_1 = require("./ezviz/models/camera"); const ffmpeg_1 = require("./ffmpeg"); const fs_1 = require("fs"); const path_1 = require("path"); const ffmpeg_for_homebridge_1 = __importDefault(require("ffmpeg-for-homebridge")); class StreamingDelegate { constructor(hap, camera, config, log) { var _a; this.ffmpegInstalled = true; this.ffmpegSupportsLibfdk_acc = true; this.pendingSessions = {}; this.ongoingSessions = {}; this.hap = hap; this.log = log; this.camera = camera; this.customFfmpeg = (_a = config.options) === null || _a === void 0 ? void 0 : _a.pathToFfmpeg; this.videoProcessor = this.customFfmpeg || ffmpeg_for_homebridge_1.default || 'ffmpeg'; ffmpeg_1.isFfmpegInstalled(this.videoProcessor) .then((installed) => { this.ffmpegInstalled = installed; }) .catch(() => { }); ffmpeg_1.getCodecsOutput(this.videoProcessor) .then((output) => { this.ffmpegSupportsLibfdk_acc = output.includes('libfdk_aac'); }) .catch(() => { }); } getOfflineImage(callback) { const log = this.log; fs_1.readFile(path_1.join(__dirname, `../images/offline.jpg`), (err, data) => { if (err) { log.error(err.message); callback(err); } else { callback(undefined, data); } }); } handleSnapshotRequest(request, callback) { const sleepSwitch = this.camera.info.switch.find((x) => x.type === 21); if (sleepSwitch && sleepSwitch.enable) { this.getOfflineImage(callback); } else { const url = `rtsp://admin:${this.camera.info.code}@${this.camera.info.connection.localIp}/Streaming/Channels/${this.camera.info.channelNumber}/`; ffmpeg_1.getSnapshot(url, this.customFfmpeg) .then((snapshot) => { callback(undefined, snapshot); }) .catch((error) => { endpoints_1.handleError(this.log, error, `Error fetching snapshot for ${this.camera.info.name}`); callback(error); }); } } prepareStream(request, callback) { return __awaiter(this, void 0, void 0, function* () { const sessionId = request.sessionID; const targetAddress = request.targetAddress; const video = request.video; const videoPort = video.port; const returnVideoPort = (yield rtp_1.reservePorts())[0]; const videoCryptoSuite = video.srtpCryptoSuite; const videoSrtpKey = video.srtp_key; const videoSrtpSalt = video.srtp_salt; const videoSSRC = this.hap.CameraController.generateSynchronisationSource(); const audio = request.audio; const audioPort = audio.port; const returnAudioPort = (yield rtp_1.reservePorts())[0]; const twoWayAudioPort = (yield rtp_1.reservePorts(2))[0]; const audioServerPort = (yield rtp_1.reservePorts())[0]; const audioCryptoSuite = video.srtpCryptoSuite; const audioSrtpKey = audio.srtp_key; const audioSrtpSalt = audio.srtp_salt; const audioSSRC = this.hap.CameraController.generateSynchronisationSource(); const sessionInfo = { address: targetAddress, videoPort: videoPort, returnVideoPort: returnVideoPort, videoCryptoSuite: videoCryptoSuite, videoSRTP: Buffer.concat([videoSrtpKey, videoSrtpSalt]), videoSSRC: videoSSRC, audioPort: audioPort, returnAudioPort: returnAudioPort, twoWayAudioPort: twoWayAudioPort, rtpSplitter: new rtp_1.RtpSplitter(audioServerPort, returnAudioPort, twoWayAudioPort), audioCryptoSuite: audioCryptoSuite, audioSRTP: Buffer.concat([audioSrtpKey, audioSrtpSalt]), audioSSRC: audioSSRC, }; const response = { video: { port: returnVideoPort, ssrc: videoSSRC, srtp_key: videoSrtpKey, srtp_salt: videoSrtpSalt, }, audio: { port: audioServerPort, ssrc: audioSSRC, srtp_key: audioSrtpKey, srtp_salt: audioSrtpSalt, }, }; this.pendingSessions[sessionId] = sessionInfo; callback(undefined, response); }); } getCommand(videoInfo, audioInfo, sessionId) { const sessionInfo = this.pendingSessions[sessionId]; const videoPort = sessionInfo.videoPort; const returnVideoPort = sessionInfo.returnVideoPort; const videoSsrc = sessionInfo.videoSSRC; const videoSRTP = sessionInfo.videoSRTP.toString('base64'); const address = sessionInfo.address; const bitrate = videoInfo.max_bit_rate * 4; const videoPayloadType = videoInfo.pt; const mtu = videoInfo.mtu; const audioPort = sessionInfo.audioPort; const returnAudioPort = sessionInfo.returnAudioPort; const audioSsrc = sessionInfo.audioSSRC; const audioSRTP = sessionInfo.audioSRTP.toString('base64'); const audioPayloadType = audioInfo.pt; const audioMaxBitrate = audioInfo.max_bit_rate; const sampleRate = audioInfo.sample_rate; let command = [ '-i', `rtsp://admin:${this.camera.info.code}@${this.camera.info.connection.localIp}/Streaming/Channels/${this.camera.info.channelNumber}/`, '-map', '0:0', '-c:v', 'copy', '-b:v', `${bitrate}k`, '-bufsize', `${bitrate}k`, '-maxrate', `${2 * bitrate}k`, '-pix_fmt', 'yuv420p', '-an', '-payload_type', videoPayloadType.toString(), '-ssrc', videoSsrc.toString(), '-f', 'rtp', '-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80', '-srtp_out_params', videoSRTP, `srtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${returnVideoPort}&pkt_size=${mtu}`, ]; if (this.ffmpegSupportsLibfdk_acc) { const audioSwitch = this.camera.info.switch.find((x) => x.type === camera_1.SwitchTypes.Audio); if (audioSwitch === null || audioSwitch === void 0 ? void 0 : audioSwitch.enable) { command = command.concat([ '-map', '0:1', '-c:a', 'libfdk_aac', '-profile:a', 'aac_eld', '-ac', '1', '-vn', '-ar', `${sampleRate}k`, '-b:a', `${audioMaxBitrate}k`, '-flags', '+global_header', '-payload_type', audioPayloadType.toString(), '-ssrc', audioSsrc.toString(), '-f', 'rtp', '-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80', '-srtp_out_params', audioSRTP, `srtp://${address}:${audioPort}?rtcpport=${audioPort}&localrtcpport=${returnAudioPort}&pkt_size=188`, ]); } } else { this.log.error("This version of FFMPEG does not support the audio codec 'libfdk_aac'. You may need to recompile FFMPEG using '--enable-libfdk_aac' and restart homebridge."); } const sleepSwitch = this.camera.info.switch.find((x) => x.type === 21); if (sleepSwitch && sleepSwitch.enable) { command = [ '-loop', '1', '-i', path_1.join(__dirname, `../images/offline.jpg`), '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'stillimage', '-pix_fmt', 'yuv420p', '-an', '-payload_type', videoPayloadType.toString(), '-ssrc', videoSsrc.toString(), '-f', 'rtp', '-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80', '-srtp_out_params', videoSRTP, `srtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${returnVideoPort}&pkt_size=${mtu}`, ]; } return command; } handleStreamRequest(request, callback) { const sessionId = request.sessionID; switch (request.type) { case "start": const video = request.video; const audio = request.audio; if (!this.ffmpegInstalled) { this.log.error('FFMPEG is not installed. Please install it and restart homebridge.'); callback(new Error('FFmpeg not installed')); break; } const ffmpegCommand = this.getCommand(video, audio, sessionId); const ffmpeg = new ffmpeg_1.FfmpegProcess('STREAM', ffmpegCommand, this.log, callback, this, sessionId, false, this.customFfmpeg); this.log.info(`Streaming started for ${this.camera.info.name}`); this.ongoingSessions[sessionId] = ffmpeg; break; case "reconfigure": this.log.debug('(Not implemented) Received request to reconfigure to: ' + JSON.stringify(request.video)); callback(); break; case "stop": this.stopStream(sessionId); callback(); break; } } stopStream(sessionId) { try { if (this.ongoingSessions[sessionId]) { const ffmpegVideoProcess = this.ongoingSessions[sessionId]; ffmpegVideoProcess === null || ffmpegVideoProcess === void 0 ? void 0 : ffmpegVideoProcess.stop(); this.log.info(`Streaming stopped for ${this.camera.info.name}`); } const sessionInfo = this.pendingSessions[sessionId]; if (sessionInfo) { sessionInfo.rtpSplitter.close(); } delete this.pendingSessions[sessionId]; delete this.ongoingSessions[sessionId]; } catch (e) { this.log.error('Error occurred terminating the video process!'); this.log.error(e); } } } exports.StreamingDelegate = StreamingDelegate; //# sourceMappingURL=streaming-delegate.js.map