ring-client-api 9.25.0-beta.0 → 10.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,216 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.SipSession = void 0;
13
- const rxjs_1 = require("rxjs");
14
- const rtp_utils_1 = require("./rtp-utils");
15
- const camera_utils_1 = require("@homebridge/camera-utils");
16
- const sip_call_1 = require("./sip-call");
17
- const subscribed_1 = require("./subscribed");
18
- const util_1 = require("./util");
19
- const ffmpeg_1 = require("./ffmpeg");
20
- const operators_1 = require("rxjs/operators");
21
- class SipSession extends subscribed_1.Subscribed {
22
- constructor(sipOptions, rtpOptions, audioSplitter, audioRtcpSplitter, videoSplitter, videoRtcpSplitter, tlsPort, camera) {
23
- super();
24
- this.sipOptions = sipOptions;
25
- this.rtpOptions = rtpOptions;
26
- this.audioSplitter = audioSplitter;
27
- this.audioRtcpSplitter = audioRtcpSplitter;
28
- this.videoSplitter = videoSplitter;
29
- this.videoRtcpSplitter = videoRtcpSplitter;
30
- this.tlsPort = tlsPort;
31
- this.camera = camera;
32
- this.hasStarted = false;
33
- this.hasCallEnded = false;
34
- this.onCallEndedSubject = new rxjs_1.ReplaySubject(1);
35
- this.onCallEnded = this.onCallEndedSubject.asObservable();
36
- this.sipCall = this.createSipCall(this.sipOptions);
37
- }
38
- createSipCall(sipOptions) {
39
- if (this.sipCall) {
40
- this.sipCall.destroy();
41
- }
42
- const call = (this.sipCall = new sip_call_1.SipCall(sipOptions, this.rtpOptions, this.tlsPort));
43
- this.addSubscriptions(call.onEndedByRemote.subscribe(() => this.callEnded(false)));
44
- return this.sipCall;
45
- }
46
- start(ffmpegOptions) {
47
- return __awaiter(this, void 0, void 0, function* () {
48
- if (this.hasStarted) {
49
- throw new Error('SIP Session has already been started');
50
- }
51
- this.hasStarted = true;
52
- if (this.hasCallEnded) {
53
- throw new Error('SIP Session has already ended');
54
- }
55
- try {
56
- const videoPort = yield this.reservePort(1), audioPort = yield this.reservePort(1), rtpDescription = yield this.sipCall.invite(), sendStunRequests = () => {
57
- (0, rtp_utils_1.sendStunBindingRequest)({
58
- rtpSplitter: this.audioSplitter,
59
- rtcpSplitter: this.audioRtcpSplitter,
60
- rtpDescription,
61
- localUfrag: this.sipCall.audioUfrag,
62
- type: 'audio',
63
- });
64
- (0, rtp_utils_1.sendStunBindingRequest)({
65
- rtpSplitter: this.videoSplitter,
66
- rtcpSplitter: this.videoRtcpSplitter,
67
- rtpDescription,
68
- localUfrag: this.sipCall.videoUfrag,
69
- type: 'video',
70
- });
71
- };
72
- if (ffmpegOptions) {
73
- this.startTranscoder(ffmpegOptions, rtpDescription, audioPort, videoPort);
74
- }
75
- // if rtcp-mux is supported, rtp splitter will be used for both rtp and rtcp
76
- if (rtpDescription.audio.port === rtpDescription.audio.rtcpPort) {
77
- this.audioRtcpSplitter.close();
78
- this.audioRtcpSplitter = this.audioSplitter;
79
- }
80
- if (rtpDescription.video.port === rtpDescription.video.rtcpPort) {
81
- this.videoRtcpSplitter.close();
82
- this.videoRtcpSplitter = this.videoSplitter;
83
- }
84
- if (rtpDescription.video.iceUFrag) {
85
- // ICE is supported
86
- (0, util_1.logDebug)(`Connecting to ${this.camera.name} using ICE`);
87
- (0, rtp_utils_1.createStunResponder)(this.audioSplitter);
88
- (0, rtp_utils_1.createStunResponder)(this.videoSplitter);
89
- sendStunRequests();
90
- }
91
- else {
92
- // ICE is not supported, use stun as keep alive
93
- (0, util_1.logDebug)(`Connecting to ${this.camera.name} using STUN`);
94
- this.addSubscriptions(
95
- // hole punch every .5 seconds to keep stream alive and port open (matches behavior from Ring app)
96
- (0, rxjs_1.timer)(0, 500).subscribe(sendStunRequests));
97
- }
98
- this.addSubscriptions(this.audioSplitter.onMessage.pipe((0, operators_1.take)(1)).subscribe(() => {
99
- (0, util_1.logDebug)(`Audio stream latched for ${this.camera.name}`);
100
- }), this.videoSplitter.onMessage.pipe((0, operators_1.take)(1)).subscribe(() => {
101
- (0, util_1.logDebug)(`Video stream latched for ${this.camera.name}`);
102
- }));
103
- return rtpDescription;
104
- }
105
- catch (e) {
106
- if (e === sip_call_1.expiredDingError) {
107
- const sipOptions = yield this.camera.getUpdatedSipOptions(this.sipOptions.dingId);
108
- this.createSipCall(sipOptions);
109
- this.hasStarted = false;
110
- return this.start(ffmpegOptions);
111
- }
112
- this.callEnded(true);
113
- throw e;
114
- }
115
- });
116
- }
117
- prepareTranscoder(transcodeVideoStream, ffmpegInputOptions, remoteRtpOptions, audioPort, videoPort, sdpInput) {
118
- const ffmpegInputArguments = [
119
- '-hide_banner',
120
- '-protocol_whitelist',
121
- 'pipe,udp,rtp,file,crypto',
122
- '-f',
123
- 'sdp',
124
- ...(ffmpegInputOptions || []),
125
- '-i',
126
- sdpInput,
127
- ], inputSdpLines = [
128
- 'v=0',
129
- 'o=105202070 3747 461 IN IP4 127.0.0.1',
130
- 's=Talk',
131
- 'c=IN IP4 127.0.0.1',
132
- 'b=AS:380',
133
- 't=0 0',
134
- 'a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics',
135
- `m=audio ${audioPort} RTP/SAVP 0 101`,
136
- 'a=rtpmap:0 PCMU/8000',
137
- (0, camera_utils_1.createCryptoLine)(remoteRtpOptions.audio),
138
- 'a=rtcp-mux',
139
- ];
140
- if (transcodeVideoStream) {
141
- inputSdpLines.push(`m=video ${videoPort} RTP/SAVP 99`, 'a=rtpmap:99 H264/90000', (0, camera_utils_1.createCryptoLine)(remoteRtpOptions.video), 'a=rtcp-mux');
142
- let haveReceivedStreamPacket = false;
143
- this.videoSplitter.addMessageHandler(({ isRtpMessage, message }) => {
144
- if ((0, rtp_utils_1.isStunMessage)(message)) {
145
- return null;
146
- }
147
- if (!haveReceivedStreamPacket) {
148
- this.sipCall.requestKeyFrame().catch(rxjs_1.noop);
149
- haveReceivedStreamPacket = true;
150
- }
151
- return {
152
- port: isRtpMessage ? videoPort : videoPort + 1,
153
- };
154
- });
155
- }
156
- this.audioSplitter.addMessageHandler(({ isRtpMessage, message }) => {
157
- if ((0, rtp_utils_1.isStunMessage)(message)) {
158
- return null;
159
- }
160
- return {
161
- port: isRtpMessage ? audioPort : audioPort + 1,
162
- };
163
- });
164
- return {
165
- ffmpegInputArguments,
166
- inputSdpLines,
167
- };
168
- }
169
- startTranscoder(ffmpegOptions, remoteRtpOptions, audioPort, videoPort) {
170
- const transcodeVideoStream = ffmpegOptions.video !== false, { ffmpegInputArguments, inputSdpLines } = this.prepareTranscoder(transcodeVideoStream, ffmpegOptions.input, remoteRtpOptions, audioPort, videoPort, 'pipe:'), ff = new camera_utils_1.FfmpegProcess({
171
- ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
172
- ? ffmpegOptions.video || ['-vcodec', 'copy']
173
- : []), ...(ffmpegOptions.output || [])),
174
- ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
175
- exitCallback: () => this.callEnded(true),
176
- logLabel: `From Ring (${this.camera.name})`,
177
- logger: {
178
- error: util_1.logError,
179
- info: util_1.logDebug,
180
- },
181
- });
182
- this.onCallEnded.subscribe(() => ff.stop());
183
- ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n'));
184
- }
185
- reservePort(bufferPorts = 0) {
186
- return __awaiter(this, void 0, void 0, function* () {
187
- const ports = yield (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
188
- return ports[0];
189
- });
190
- }
191
- requestKeyFrame() {
192
- return this.sipCall.requestKeyFrame();
193
- }
194
- activateCameraSpeaker() {
195
- return this.sipCall.activateCameraSpeaker();
196
- }
197
- callEnded(sendBye) {
198
- if (this.hasCallEnded) {
199
- return;
200
- }
201
- this.hasCallEnded = true;
202
- if (sendBye) {
203
- this.sipCall.sendBye().catch(util_1.logError);
204
- }
205
- // clean up
206
- this.onCallEndedSubject.next(null);
207
- this.sipCall.destroy();
208
- this.videoSplitter.close();
209
- this.audioSplitter.close();
210
- this.unsubscribe();
211
- }
212
- stop() {
213
- this.callEnded(true);
214
- }
215
- }
216
- exports.SipSession = SipSession;
package/lib/api/sip.d.ts DELETED
@@ -1,87 +0,0 @@
1
- export function copyMessage(msg: any, deep: any): any;
2
- export function create(options: any, callback: any): {
3
- send: (m: any, callback: any) => void;
4
- encodeFlowUri: (flow: any) => {
5
- schema: string;
6
- user: any;
7
- host: any;
8
- params: {};
9
- };
10
- decodeFlowUri: (uri: any) => {
11
- protocol: any;
12
- address: any;
13
- port: number;
14
- local: {
15
- address: any;
16
- port: number;
17
- };
18
- } | undefined;
19
- isFlowUri: (uri: any) => boolean;
20
- hostname: () => any;
21
- destroy: () => void;
22
- makeResponse: typeof makeResponse;
23
- };
24
- export function start(options: any, callback: any): void;
25
- export function parseAOR(data: any): any;
26
- export function parseUri(s: any): any;
27
- export function stringifyUri(uri: any): string;
28
- export function stringifyAuthHeader(a: any): string;
29
- export function stringify(m: any): string;
30
- export function makeResponse(rq: any, status: any, reason: any, extension: any): {
31
- status: any;
32
- reason: any;
33
- version: any;
34
- headers: {
35
- via: any;
36
- to: any;
37
- from: any;
38
- 'call-id': any;
39
- cseq: any;
40
- };
41
- };
42
- export function makeStreamParser(onMessage: any, onFlood: any, maxBytesHeaders: any, maxContentLength: any): (data: any) => void;
43
- declare function parseMessage(s: any): {
44
- headers: {};
45
- } | undefined;
46
- export function makeTransport(options: any, callback: any): {
47
- open: (target: any, error: any) => any;
48
- get: (target: any, error: any) => any;
49
- send: (target: any, message: any) => void;
50
- destroy: () => void;
51
- };
52
- export function resolve(uri: any, action: any): any;
53
- export function generateBranch(): string;
54
- export function makeTransactionLayer(options: any, transport: any): {
55
- createServerTransaction: (rq: any, cn: any) => {
56
- send: (...args: any[]) => void;
57
- message: (...args: any[]) => void;
58
- shutdown: () => void;
59
- };
60
- createClientTransaction: (connection: any, rq: any, callback: any) => {
61
- message: (...args: any[]) => void;
62
- shutdown: () => void;
63
- };
64
- getServer: (m: any) => any;
65
- getClient: (m: any) => any;
66
- destroy: () => void;
67
- };
68
- export function send(m: any, callback: any): void;
69
- declare function destroy(): void;
70
- export function encodeFlowUri(flow: any): {
71
- schema: string;
72
- user: any;
73
- host: any;
74
- params: {};
75
- };
76
- export function decodeFlowUri(uri: any): {
77
- protocol: any;
78
- address: any;
79
- port: number;
80
- local: {
81
- address: any;
82
- port: number;
83
- };
84
- } | undefined;
85
- export function isFlowUri(uri: any): boolean;
86
- export function hostname(): any;
87
- export { parseMessage as parse, destroy as stop };