@koush/ring-client-api
Version:
Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting
137 lines (136 loc) • 5.91 kB
JavaScript
;
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;