UNPKG

@koush/ring-client-api

Version:

Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting

137 lines (136 loc) 5.91 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LiveCall = void 0; const peer_connection_1 = require("./peer-connection"); const util_1 = require("./util"); const operators_1 = require("rxjs/operators"); const camera_utils_1 = require("@homebridge/camera-utils"); const ffmpeg_1 = require("./ffmpeg"); const live_call_negotiation_1 = require("./live-call-negotiation"); class LiveCall extends live_call_negotiation_1.LiveCallNegotiation { constructor(sessionId, camera) { super(sessionId, camera); this.audioSplitter = new camera_utils_1.RtpSplitter(); this.videoSplitter = new camera_utils_1.RtpSplitter(); this.pc = new peer_connection_1.PeerConnection(); this.onAudioRtp = this.pc.onAudioRtp; this.onVideoRtp = this.pc.onVideoRtp; this.addSubscriptions(this.onMessage .pipe((0, operators_1.concatMap)((message) => { return this.handleMessage(message); })) .subscribe()); } handleMessage(message) { return __awaiter(this, void 0, void 0, function* () { switch (message.method) { case 'sdp': const answer = yield this.pc.createAnswer(message); this.sendAnswer(answer); return; case 'ice': yield this.pc.addIceCandidate({ candidate: message.ice, sdpMLineIndex: message.mlineindex, }); return; } }); } reservePort(bufferPorts = 0) { return __awaiter(this, void 0, void 0, function* () { const ports = yield (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 }); return ports[0]; }); } prepareTranscoder(transcodeVideoStream, ffmpegInputOptions, audioPort, videoPort, sdpInput) { const ffmpegInputArguments = [ '-hide_banner', '-protocol_whitelist', 'pipe,udp,rtp,file,crypto', '-acodec', 'libopus', '-f', 'sdp', ...(ffmpegInputOptions || []), '-i', sdpInput, ], inputSdpLines = [ 'v=0', 'o=105202070 3747 461 IN IP4 127.0.0.1', 's=Talk', 'c=IN IP4 127.0.0.1', 'b=AS:380', 't=0 0', 'a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics', `m=audio ${audioPort} RTP/SAVP 101`, 'a=rtpmap:101 OPUS/48000/2', 'a=rtcp-fb:101 nack pli', 'a=fmtp:101 useinbandfec=1;sprop-stereo=0', 'a=rtcp-mux', ]; if (transcodeVideoStream) { inputSdpLines.push(`m=video ${videoPort} RTP/SAVP 96`, 'a=rtpmap:96 H264/90000', 'a=rtcp-fb:96 nack', 'a=rtcp-fb:96 nack pli', 'a=fmtp:96 packetization-mode=1;profile-level-id=640029;level-asymmetry-allowed=1', 'a=rtcp-mux'); this.addSubscriptions(this.onVideoRtp .pipe((0, operators_1.concatMap)((rtp) => { return this.videoSplitter.send(rtp.serialize(), { port: videoPort, }); })) .subscribe()); } this.addSubscriptions(this.onAudioRtp .pipe((0, operators_1.concatMap)((rtp) => { return this.audioSplitter.send(rtp.serialize(), { port: audioPort, }); })) .subscribe()); return { ffmpegInputArguments, inputSdpLines, }; } startTranscoding(ffmpegOptions) { return __awaiter(this, void 0, void 0, function* () { const videoPort = yield this.reservePort(1), audioPort = yield this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, { ffmpegInputArguments, inputSdpLines } = this.prepareTranscoder(transcodeVideoStream, ffmpegOptions.input, audioPort, videoPort, 'pipe:'), ff = new camera_utils_1.FfmpegProcess({ ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream ? ffmpegOptions.video || ['-vcodec', 'copy'] : []), ...(ffmpegOptions.output || [])), ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(), exitCallback: () => this.callEnded(), logLabel: `From Ring (${this.camera.name})`, logger: { error: util_1.logError, info: util_1.logDebug, }, }); this.onCallEnded.subscribe(() => ff.stop()); ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n')); // Activate the stream now that ffmpeg is ready to receive yield this.activate(); }); } callEnded() { super.callEnded(); this.pc.close(); this.audioSplitter.close(); this.videoSplitter.close(); } stop() { this.callEnded(); } sendAudioPacket(rtp) { this.pc.returnAudioTrack.writeRtp(rtp); } } exports.LiveCall = LiveCall;