homebridge-ring 11.0.2-beta.1 → 11.0.4
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.
- package/CHANGELOG.md +21 -4
- package/config.schema.json +5 -0
- package/lib/api/streaming/peer-connection.d.ts +6 -2
- package/lib/api/streaming/peer-connection.js +21 -11
- package/lib/api/streaming/ring-edge-connection.js +23 -29
- package/lib/api/streaming/streaming-connection-base.d.ts +7 -3
- package/lib/api/streaming/streaming-connection-base.js +35 -13
- package/lib/api/streaming/streaming-session.d.ts +7 -3
- package/lib/api/streaming/streaming-session.js +25 -15
- package/lib/api/streaming/webrtc-connection.js +1 -4
- package/lib/homebridge/base-accessory.js +2 -2
- package/lib/homebridge/base-data-accessory.js +3 -3
- package/lib/homebridge/camera-source.js +7 -7
- package/lib/homebridge/location-mode-switch.js +3 -3
- package/lib/homebridge/ring-platform.js +2 -2
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,20 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
### [11.0.
|
|
5
|
+
### [11.0.4](https://github.com/dgreif/ring/compare/v11.0.3...v11.0.4) (2022-05-29)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
### Bug Fixes
|
|
9
9
|
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* handle unanswered calls when transcoding ([6f9d3df](https://github.com/dgreif/ring/commit/6f9d3dfa079f046a6b5cfa140fd67eb210669429))
|
|
11
|
+
* update werift and ring edge connection strategy ([7b786f3](https://github.com/dgreif/ring/commit/7b786f3ffd9ad8eb4a8f03f5eac9ca6c67517aae))
|
|
12
12
|
|
|
13
|
-
### [11.0.
|
|
13
|
+
### [11.0.3](https://github.com/dgreif/ring/compare/v11.0.2...v11.0.3) (2022-05-28)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
### Bug Fixes
|
|
17
17
|
|
|
18
|
+
* automatically activate streams and allow key frame requests ([b129f29](https://github.com/dgreif/ring/commit/b129f29d0a3774d779e9c159fe381e4e09066c0d))
|
|
19
|
+
* end streaming session when connection ends ([b6bf08b](https://github.com/dgreif/ring/commit/b6bf08ba01e344faf59f6b42ea0c3b5ce384e0bf))
|
|
20
|
+
* **homebridge:** allow ffmpeg in config schema ([#946](https://github.com/dgreif/ring/issues/946)) ([3242bbd](https://github.com/dgreif/ring/commit/3242bbd34dd6847500839396a9e10509e8216023))
|
|
21
|
+
* remove race condition for sending messages to ring edge connections ([a9438d3](https://github.com/dgreif/ring/commit/a9438d3dfc5e7ad8b96881b689534c84fab637f1))
|
|
22
|
+
* start ring edge pings as soon as session is started ([04ad614](https://github.com/dgreif/ring/commit/04ad614cb25c9aa4b11459e5fecde9ff7766a32d))
|
|
23
|
+
* update dependencies including ffmpeg ([f5cd553](https://github.com/dgreif/ring/commit/f5cd553506cf0ea31a309d1350eff19c48552809))
|
|
24
|
+
* use random client id for every ring edge connection ([3ba78e3](https://github.com/dgreif/ring/commit/3ba78e39b9da392779005d7d76828ddd2e425e6b))
|
|
25
|
+
|
|
26
|
+
### [11.0.2](https://github.com/dgreif/ring/compare/v11.0.1...v11.0.2) (2022-05-22)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **api:** prevent duplicate events from `onCallEnded` ([14ee6eb](https://github.com/dgreif/ring/commit/14ee6eb90bb6ae568dcfd9e8e176c3001e422f9c)), closes [#941](https://github.com/dgreif/ring/issues/941)
|
|
18
32
|
* handle pcmu audio streams for incoming audio ([06f6ff1](https://github.com/dgreif/ring/commit/06f6ff10ce7c8f2b05b1a12b8f244daa200fa9ab)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
33
|
+
* handle pcmu return audio ([7b86182](https://github.com/dgreif/ring/commit/7b8618261a6ccb85b9f760ee1fcc228be00fe5a6)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
34
|
+
* **homebride:** use shared engines config to enforce node 16 requirement ([b748cb0](https://github.com/dgreif/ring/commit/b748cb027db0c43ae109e1ea77c6bc83f86e4913))
|
|
35
|
+
* prevent hanging promises from streams that fail to connect ([dd7638e](https://github.com/dgreif/ring/commit/dd7638e32626499c0be501be6b19b5d874fa974f))
|
|
19
36
|
|
|
20
37
|
### [11.0.1](https://github.com/dgreif/ring/compare/v11.0.0...v11.0.1) (2022-05-17)
|
|
21
38
|
|
package/config.schema.json
CHANGED
|
@@ -62,6 +62,11 @@
|
|
|
62
62
|
"type": "boolean",
|
|
63
63
|
"description": "Creates a new `Panic Buttons` device in HomeKit with `Burglar Alarm` and `Fire Alarm` switches. **Use these at your own risk. I do not guarantee functionality in case of emergency, nor do I take responsibility for any false alarms**. These function just like the SOS sliders in the Ring app."
|
|
64
64
|
},
|
|
65
|
+
"ffmpegPath": {
|
|
66
|
+
"title": "Custom FFmpeg Path",
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "A custom path to the ffmpeg executable. By default, the static binaries built in ffmpeg-for-homebridge will be used. If you prefer to use your own version of ffmpeg, you can pass a complete path, or simply [ ffmpeg ] to use ffmpeg from your PATH."
|
|
69
|
+
},
|
|
65
70
|
"nightModeBypassFor": {
|
|
66
71
|
"title": "Night Mode as Bypass For Mode",
|
|
67
72
|
"type": "string",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConnectionState, MediaStreamTrack, RTCIceCandidate, RtcpPacket, RtpPacket } from 'werift';
|
|
2
2
|
import { Observable, ReplaySubject, Subject } from 'rxjs';
|
|
3
|
+
import { Subscribed } from '../subscribed';
|
|
3
4
|
export interface BasicPeerConnection {
|
|
4
5
|
createOffer(): Promise<{
|
|
5
6
|
sdp: string;
|
|
@@ -12,12 +13,13 @@ export interface BasicPeerConnection {
|
|
|
12
13
|
type: 'answer';
|
|
13
14
|
sdp: string;
|
|
14
15
|
}): Promise<void>;
|
|
15
|
-
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
16
|
+
addIceCandidate(candidate: Partial<RTCIceCandidate>): Promise<void>;
|
|
16
17
|
onIceCandidate: Observable<RTCIceCandidate>;
|
|
17
18
|
onConnectionState: Observable<ConnectionState>;
|
|
18
19
|
close(): void;
|
|
20
|
+
requestKeyFrame?: () => void;
|
|
19
21
|
}
|
|
20
|
-
export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
22
|
+
export declare class WeriftPeerConnection extends Subscribed implements BasicPeerConnection {
|
|
21
23
|
private pc;
|
|
22
24
|
onAudioRtp: Subject<RtpPacket>;
|
|
23
25
|
onAudioRtcp: Subject<RtcpPacket>;
|
|
@@ -26,6 +28,7 @@ export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
|
26
28
|
onIceCandidate: Subject<RTCIceCandidate>;
|
|
27
29
|
onConnectionState: ReplaySubject<"failed" | "closed" | "connected" | "connecting" | "new" | "disconnected">;
|
|
28
30
|
returnAudioTrack: MediaStreamTrack;
|
|
31
|
+
private onRequestKeyFrame;
|
|
29
32
|
constructor();
|
|
30
33
|
createOffer(): Promise<import("werift").RTCSessionDescription>;
|
|
31
34
|
createAnswer(offer: {
|
|
@@ -37,5 +40,6 @@ export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
|
37
40
|
sdp: string;
|
|
38
41
|
}): Promise<void>;
|
|
39
42
|
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
43
|
+
requestKeyFrame(): void;
|
|
40
44
|
close(): void;
|
|
41
45
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WeriftPeerConnection = void 0;
|
|
4
|
+
/* eslint-disable brace-style */
|
|
4
5
|
const werift_1 = require("werift");
|
|
5
6
|
const rxjs_1 = require("rxjs");
|
|
6
7
|
const util_1 = require("../util");
|
|
7
|
-
const
|
|
8
|
+
const subscribed_1 = require("../subscribed");
|
|
9
|
+
const ringIceServers = [
|
|
8
10
|
'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
|
|
9
11
|
'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
|
|
10
12
|
'stun:stun.kinesisvideo.us-west-2.amazonaws.com:443',
|
|
@@ -14,8 +16,9 @@ const debug = false, ringIceServers = [
|
|
|
14
16
|
'stun:stun3.l.google.com:19302',
|
|
15
17
|
'stun:stun4.l.google.com:19302',
|
|
16
18
|
];
|
|
17
|
-
class WeriftPeerConnection {
|
|
19
|
+
class WeriftPeerConnection extends subscribed_1.Subscribed {
|
|
18
20
|
constructor() {
|
|
21
|
+
super();
|
|
19
22
|
this.onAudioRtp = new rxjs_1.Subject();
|
|
20
23
|
this.onAudioRtcp = new rxjs_1.Subject();
|
|
21
24
|
this.onVideoRtp = new rxjs_1.Subject();
|
|
@@ -23,6 +26,7 @@ class WeriftPeerConnection {
|
|
|
23
26
|
this.onIceCandidate = new rxjs_1.Subject();
|
|
24
27
|
this.onConnectionState = new rxjs_1.ReplaySubject(1);
|
|
25
28
|
this.returnAudioTrack = new werift_1.MediaStreamTrack({ kind: 'audio' });
|
|
29
|
+
this.onRequestKeyFrame = new rxjs_1.Subject();
|
|
26
30
|
const pc = (this.pc = new werift_1.RTCPeerConnection({
|
|
27
31
|
codecs: {
|
|
28
32
|
audio: [
|
|
@@ -55,6 +59,7 @@ class WeriftPeerConnection {
|
|
|
55
59
|
},
|
|
56
60
|
iceServers: ringIceServers.map((server) => ({ urls: server })),
|
|
57
61
|
iceTransportPolicy: 'all',
|
|
62
|
+
bundlePolicy: 'disable',
|
|
58
63
|
})), audioTransceiver = pc.addTransceiver(this.returnAudioTrack, {
|
|
59
64
|
direction: 'sendrecv',
|
|
60
65
|
}), videoTransceiver = pc.addTransceiver('video', {
|
|
@@ -67,11 +72,9 @@ class WeriftPeerConnection {
|
|
|
67
72
|
track.onReceiveRtcp.subscribe((rtcp) => {
|
|
68
73
|
this.onAudioRtcp.next(rtcp);
|
|
69
74
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
+
track.onReceiveRtp.once(() => {
|
|
76
|
+
(0, util_1.logDebug)('received first audio packet');
|
|
77
|
+
});
|
|
75
78
|
});
|
|
76
79
|
videoTransceiver.onTrack.subscribe((track) => {
|
|
77
80
|
track.onReceiveRtp.subscribe((rtp) => {
|
|
@@ -81,10 +84,13 @@ class WeriftPeerConnection {
|
|
|
81
84
|
this.onVideoRtcp.next(rtcp);
|
|
82
85
|
});
|
|
83
86
|
track.onReceiveRtp.once(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
(0, util_1.logDebug)('received first video packet');
|
|
88
|
+
this.addSubscriptions((0, rxjs_1.merge)(this.onRequestKeyFrame, (0, rxjs_1.interval)(4000)).subscribe(() => {
|
|
89
|
+
videoTransceiver.receiver
|
|
90
|
+
.sendRtcpPLI(track.ssrc)
|
|
91
|
+
.catch((e) => (0, util_1.logError)(e));
|
|
92
|
+
}));
|
|
93
|
+
this.requestKeyFrame();
|
|
88
94
|
});
|
|
89
95
|
});
|
|
90
96
|
this.pc.onIceCandidate.subscribe((iceCandidate) => {
|
|
@@ -118,8 +124,12 @@ class WeriftPeerConnection {
|
|
|
118
124
|
addIceCandidate(candidate) {
|
|
119
125
|
return this.pc.addIceCandidate(candidate);
|
|
120
126
|
}
|
|
127
|
+
requestKeyFrame() {
|
|
128
|
+
this.onRequestKeyFrame.next();
|
|
129
|
+
}
|
|
121
130
|
close() {
|
|
122
131
|
this.pc.close().catch(util_1.logError);
|
|
132
|
+
this.unsubscribe();
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
135
|
exports.WeriftPeerConnection = WeriftPeerConnection;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.RingEdgeConnection = void 0;
|
|
4
7
|
const ws_1 = require("ws");
|
|
5
8
|
const rxjs_1 = require("rxjs");
|
|
6
9
|
const util_1 = require("../util");
|
|
7
|
-
const operators_1 = require("rxjs/operators");
|
|
8
10
|
const streaming_connection_base_1 = require("./streaming-connection-base");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
12
|
// eslint-disable-next-line no-shadow
|
|
10
13
|
var CloseReasonCode;
|
|
11
14
|
(function (CloseReasonCode) {
|
|
@@ -23,7 +26,9 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
23
26
|
headers: {
|
|
24
27
|
Authorization: `Bearer ${authToken}`,
|
|
25
28
|
'X-Sig-API-Version': '4.0',
|
|
26
|
-
'X-Sig-Client-ID':
|
|
29
|
+
'X-Sig-Client-ID': `ring_android-${crypto_1.default
|
|
30
|
+
.randomBytes(4)
|
|
31
|
+
.toString('hex')}`,
|
|
27
32
|
'X-Sig-Client-Info': 'Ring/3.49.0;Platform/Android;OS/7.0;Density/2.0;Device/samsung-SM-T710;Locale/en-US;TimeZone/GMT-07:00',
|
|
28
33
|
'X-Sig-Auth-Type': 'ring_oauth',
|
|
29
34
|
},
|
|
@@ -40,12 +45,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
40
45
|
});
|
|
41
46
|
}),
|
|
42
47
|
// The ring-edge session needs a ping every 5 seconds to keep the connection alive
|
|
43
|
-
|
|
44
|
-
.pipe((0, operators_1.switchMap)(() => (0, rxjs_1.interval)(5000)))
|
|
45
|
-
.subscribe(() => {
|
|
46
|
-
if (!this.sessionId) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
48
|
+
(0, rxjs_1.interval)(5000).subscribe(() => {
|
|
49
49
|
this.sendSessionMessage('ping');
|
|
50
50
|
}), this.pc.onIceCandidate.subscribe(async (iceCandidate) => {
|
|
51
51
|
await (0, rxjs_1.firstValueFrom)(this.onOfferSent);
|
|
@@ -54,17 +54,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
54
54
|
body: {
|
|
55
55
|
doorbot_id: camera.id,
|
|
56
56
|
ice: iceCandidate.candidate,
|
|
57
|
-
mlineindex:
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
// HACK: send ice candidate with both mline indexes to convince ring edge to connect both audio and video
|
|
61
|
-
// Without this, only audio will connect unless you connect from the network of the Ring Edge router
|
|
62
|
-
this.sendMessage({
|
|
63
|
-
method: 'ice',
|
|
64
|
-
body: {
|
|
65
|
-
doorbot_id: camera.id,
|
|
66
|
-
ice: iceCandidate.candidate,
|
|
67
|
-
mlineindex: 1,
|
|
57
|
+
mlineindex: iceCandidate.sdpMLineIndex,
|
|
68
58
|
},
|
|
69
59
|
});
|
|
70
60
|
}));
|
|
@@ -76,7 +66,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
76
66
|
body: {
|
|
77
67
|
doorbot_id: this.camera.id,
|
|
78
68
|
stream_options: { audio_enabled: true, video_enabled: true },
|
|
79
|
-
sdp
|
|
69
|
+
sdp,
|
|
80
70
|
},
|
|
81
71
|
});
|
|
82
72
|
this.onOfferSent.next();
|
|
@@ -104,6 +94,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
104
94
|
case 'sdp':
|
|
105
95
|
await this.pc.acceptAnswer(message.body);
|
|
106
96
|
this.onCallAnswered.next(message.body.sdp);
|
|
97
|
+
this.activate();
|
|
107
98
|
return;
|
|
108
99
|
case 'ice':
|
|
109
100
|
await this.pc.addIceCandidate({
|
|
@@ -123,22 +114,25 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
123
114
|
(0, util_1.logError)(message);
|
|
124
115
|
}
|
|
125
116
|
sendSessionMessage(method, body = {}) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
117
|
+
const sendSessionMessage = () => {
|
|
118
|
+
const message = {
|
|
119
|
+
method,
|
|
120
|
+
body: {
|
|
121
|
+
...body,
|
|
122
|
+
doorbot_id: this.camera.id,
|
|
123
|
+
session_id: this.sessionId,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
this.sendMessage(message);
|
|
133
127
|
};
|
|
134
128
|
if (this.sessionId) {
|
|
135
129
|
// Send immediately if we already have a session id
|
|
136
130
|
// This is needed to send `close` before closing the websocket
|
|
137
|
-
|
|
131
|
+
sendSessionMessage();
|
|
138
132
|
}
|
|
139
133
|
else {
|
|
140
134
|
(0, rxjs_1.firstValueFrom)(this.onSessionId)
|
|
141
|
-
.then(
|
|
135
|
+
.then(sendSessionMessage)
|
|
142
136
|
.catch((e) => (0, util_1.logError)(e));
|
|
143
137
|
}
|
|
144
138
|
}
|
|
@@ -11,6 +11,9 @@ export declare abstract class StreamingConnectionBase extends Subscribed {
|
|
|
11
11
|
protected options: StreamingConnectionOptions;
|
|
12
12
|
readonly onCallAnswered: ReplaySubject<string>;
|
|
13
13
|
readonly onCallEnded: ReplaySubject<void>;
|
|
14
|
+
readonly onMessage: ReplaySubject<{
|
|
15
|
+
method: string;
|
|
16
|
+
}>;
|
|
14
17
|
readonly onWsOpen: import("rxjs").Observable<unknown>;
|
|
15
18
|
protected readonly pc: BasicPeerConnection;
|
|
16
19
|
readonly onAudioRtp: Subject<RtpPacket>;
|
|
@@ -20,11 +23,12 @@ export declare abstract class StreamingConnectionBase extends Subscribed {
|
|
|
20
23
|
method: string;
|
|
21
24
|
}): Promise<void>;
|
|
22
25
|
protected abstract sendSessionMessage(method: string, body?: Record<any, any>): void;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
activateCameraSpeaker(): Promise<void>;
|
|
26
|
+
protected activate(): void;
|
|
27
|
+
activateCameraSpeaker(): void;
|
|
26
28
|
protected sendMessage(message: Record<any, any>): void;
|
|
27
29
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
30
|
+
private hasEnded;
|
|
28
31
|
protected callEnded(): void;
|
|
29
32
|
stop(): void;
|
|
33
|
+
requestKeyFrame(): void;
|
|
30
34
|
}
|
|
@@ -13,7 +13,8 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
13
13
|
this.options = options;
|
|
14
14
|
this.onCallAnswered = new rxjs_1.ReplaySubject(1);
|
|
15
15
|
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
16
|
-
this.
|
|
16
|
+
this.onMessage = new rxjs_1.ReplaySubject();
|
|
17
|
+
this.hasEnded = false;
|
|
17
18
|
if (options.createPeerConnection) {
|
|
18
19
|
// we were passed a custom peer connection factory
|
|
19
20
|
this.pc = options.createPeerConnection();
|
|
@@ -31,8 +32,10 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
31
32
|
this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
|
|
32
33
|
const onMessage = (0, rxjs_1.fromEvent)(this.ws, 'message'), onError = (0, rxjs_1.fromEvent)(this.ws, 'error'), onClose = (0, rxjs_1.fromEvent)(this.ws, 'close');
|
|
33
34
|
this.addSubscriptions(onMessage
|
|
34
|
-
.pipe((0, operators_1.concatMap)((
|
|
35
|
-
|
|
35
|
+
.pipe((0, operators_1.concatMap)((event) => {
|
|
36
|
+
const message = JSON.parse(event.data);
|
|
37
|
+
this.onMessage.next(message);
|
|
38
|
+
return this.handleMessage(message);
|
|
36
39
|
}))
|
|
37
40
|
.subscribe(), onError.subscribe((e) => {
|
|
38
41
|
(0, util_1.logError)(e);
|
|
@@ -50,25 +53,37 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
50
53
|
}
|
|
51
54
|
}));
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
if (this.activated) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
this.activated = true;
|
|
58
|
-
await (0, rxjs_1.firstValueFrom)(this.pc.onConnectionState.pipe((0, operators_1.filter)((state) => state === 'connected')));
|
|
56
|
+
activate() {
|
|
59
57
|
(0, util_1.logInfo)('Activating Session');
|
|
58
|
+
// the activate_session message is required to keep the stream alive longer than 70 seconds
|
|
60
59
|
this.sendSessionMessage('activate_session');
|
|
60
|
+
this.sendSessionMessage('stream_options', {
|
|
61
|
+
audio_enabled: true,
|
|
62
|
+
video_enabled: true,
|
|
63
|
+
});
|
|
61
64
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
activateCameraSpeaker() {
|
|
66
|
+
// Fire and forget this call so that callers don't get hung up waiting for answer (which might not happen)
|
|
67
|
+
(0, rxjs_1.firstValueFrom)(this.onCallAnswered)
|
|
68
|
+
.then(() => {
|
|
69
|
+
this.sendSessionMessage('camera_options', {
|
|
70
|
+
stealth_mode: false,
|
|
71
|
+
});
|
|
72
|
+
})
|
|
73
|
+
.catch((e) => {
|
|
74
|
+
(0, util_1.logError)(e);
|
|
66
75
|
});
|
|
67
76
|
}
|
|
68
77
|
sendMessage(message) {
|
|
78
|
+
if (this.hasEnded) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
69
81
|
this.ws.send(JSON.stringify(message));
|
|
70
82
|
}
|
|
71
83
|
sendAudioPacket(rtp) {
|
|
84
|
+
if (this.hasEnded) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
72
87
|
if (this.pc instanceof peer_connection_1.WeriftPeerConnection) {
|
|
73
88
|
this.pc.returnAudioTrack.writeRtp(rtp);
|
|
74
89
|
}
|
|
@@ -77,6 +92,9 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
callEnded() {
|
|
95
|
+
if (this.hasEnded) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
80
98
|
try {
|
|
81
99
|
this.sendMessage({
|
|
82
100
|
reason: { code: 0, text: '' },
|
|
@@ -87,6 +105,7 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
87
105
|
catch (_) {
|
|
88
106
|
// ignore any errors since we are stopping the call
|
|
89
107
|
}
|
|
108
|
+
this.hasEnded = true;
|
|
90
109
|
this.unsubscribe();
|
|
91
110
|
this.onCallEnded.next();
|
|
92
111
|
this.pc.close();
|
|
@@ -94,5 +113,8 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
94
113
|
stop() {
|
|
95
114
|
this.callEnded();
|
|
96
115
|
}
|
|
116
|
+
requestKeyFrame() {
|
|
117
|
+
this.pc.requestKeyFrame?.();
|
|
118
|
+
}
|
|
97
119
|
}
|
|
98
120
|
exports.StreamingConnectionBase = StreamingConnectionBase;
|
|
@@ -23,10 +23,13 @@ export declare class StreamingSession extends Subscribed {
|
|
|
23
23
|
private readonly returnAudioSplitter;
|
|
24
24
|
constructor(camera: RingCamera, connection: RingEdgeConnection | WebrtcConnection);
|
|
25
25
|
private bindToConnection;
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated
|
|
28
|
+
* activate will be removed in the future. Please use requestKeyFrame if you want to explicitly request an initial key frame
|
|
29
|
+
*/
|
|
30
|
+
activate(): void;
|
|
28
31
|
cameraSpeakerActivated: boolean;
|
|
29
|
-
activateCameraSpeaker():
|
|
32
|
+
activateCameraSpeaker(): void;
|
|
30
33
|
reservePort(bufferPorts?: number): Promise<number>;
|
|
31
34
|
get isUsingOpus(): Promise<boolean>;
|
|
32
35
|
startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
|
|
@@ -37,5 +40,6 @@ export declare class StreamingSession extends Subscribed {
|
|
|
37
40
|
private callEnded;
|
|
38
41
|
stop(): void;
|
|
39
42
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
43
|
+
requestKeyFrame(): void;
|
|
40
44
|
}
|
|
41
45
|
export {};
|
|
@@ -28,7 +28,6 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
28
28
|
this.audioSplitter = new camera_utils_1.RtpSplitter();
|
|
29
29
|
this.videoSplitter = new camera_utils_1.RtpSplitter();
|
|
30
30
|
this.returnAudioSplitter = new camera_utils_1.RtpSplitter();
|
|
31
|
-
this.activated = false;
|
|
32
31
|
this.cameraSpeakerActivated = false;
|
|
33
32
|
this.hasEnded = false;
|
|
34
33
|
this.bindToConnection(connection);
|
|
@@ -36,21 +35,21 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
36
35
|
bindToConnection(connection) {
|
|
37
36
|
this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp), connection.onCallAnswered.subscribe((sdp) => {
|
|
38
37
|
this.onUsingOpus.next(sdp.toLocaleLowerCase().includes(' opus/'));
|
|
39
|
-
}));
|
|
38
|
+
}), connection.onCallEnded.subscribe(() => this.callEnded()));
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated
|
|
42
|
+
* activate will be removed in the future. Please use requestKeyFrame if you want to explicitly request an initial key frame
|
|
43
|
+
*/
|
|
44
|
+
activate() {
|
|
45
|
+
this.requestKeyFrame();
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
activateCameraSpeaker() {
|
|
49
48
|
if (this.cameraSpeakerActivated || this.hasEnded) {
|
|
50
49
|
return;
|
|
51
50
|
}
|
|
52
51
|
this.cameraSpeakerActivated = true;
|
|
53
|
-
|
|
52
|
+
this.connection.activateCameraSpeaker();
|
|
54
53
|
}
|
|
55
54
|
async reservePort(bufferPorts = 0) {
|
|
56
55
|
const ports = await (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
|
|
@@ -63,7 +62,15 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
63
62
|
if (this.hasEnded) {
|
|
64
63
|
return;
|
|
65
64
|
}
|
|
66
|
-
const videoPort = await this.reservePort(1), audioPort = await this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, ringSdp = await
|
|
65
|
+
const videoPort = await this.reservePort(1), audioPort = await this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, ringSdp = await Promise.race([
|
|
66
|
+
(0, rxjs_1.firstValueFrom)(this.connection.onCallAnswered),
|
|
67
|
+
(0, rxjs_1.firstValueFrom)(this.onCallEnded),
|
|
68
|
+
]);
|
|
69
|
+
if (!ringSdp) {
|
|
70
|
+
(0, util_1.logDebug)('Call ended before answered');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const usingOpus = await this.isUsingOpus, ffmpegInputArguments = [
|
|
67
74
|
'-hide_banner',
|
|
68
75
|
'-protocol_whitelist',
|
|
69
76
|
'pipe,udp,rtp,file,crypto',
|
|
@@ -104,10 +111,10 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
104
111
|
}))
|
|
105
112
|
.subscribe());
|
|
106
113
|
}
|
|
107
|
-
this.onCallEnded.subscribe(() => ff.stop());
|
|
114
|
+
this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
|
|
108
115
|
ff.writeStdin(inputSdp);
|
|
109
|
-
//
|
|
110
|
-
|
|
116
|
+
// Request a key frame now that ffmpeg is ready to receive
|
|
117
|
+
this.requestKeyFrame();
|
|
111
118
|
}
|
|
112
119
|
async transcodeReturnAudio(ffmpegOptions) {
|
|
113
120
|
if (this.hasEnded) {
|
|
@@ -143,7 +150,7 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
143
150
|
info: util_1.logDebug,
|
|
144
151
|
},
|
|
145
152
|
});
|
|
146
|
-
this.onCallEnded.subscribe(() => ff.stop());
|
|
153
|
+
this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
|
|
147
154
|
}
|
|
148
155
|
callEnded() {
|
|
149
156
|
if (this.hasEnded) {
|
|
@@ -166,5 +173,8 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
166
173
|
}
|
|
167
174
|
this.connection.sendAudioPacket(rtp);
|
|
168
175
|
}
|
|
176
|
+
requestKeyFrame() {
|
|
177
|
+
this.connection.requestKeyFrame();
|
|
178
|
+
}
|
|
169
179
|
}
|
|
170
180
|
exports.StreamingSession = StreamingSession;
|
|
@@ -29,10 +29,7 @@ class WebrtcConnection extends streaming_connection_base_1.StreamingConnectionBa
|
|
|
29
29
|
const answer = await this.pc.createAnswer(message);
|
|
30
30
|
this.sendSessionMessage('sdp', answer);
|
|
31
31
|
this.onCallAnswered.next(message.sdp);
|
|
32
|
-
this.
|
|
33
|
-
audio_enabled: true,
|
|
34
|
-
video_enabled: true,
|
|
35
|
-
});
|
|
32
|
+
this.activate();
|
|
36
33
|
return;
|
|
37
34
|
case 'ice':
|
|
38
35
|
await this.pc.addIceCandidate({
|
|
@@ -45,7 +45,7 @@ class BaseAccessory {
|
|
|
45
45
|
// Only register for GET if an async request should be made to get an updated value
|
|
46
46
|
onCachedValue.pipe((0, operators_1.take)(1)).subscribe(() => {
|
|
47
47
|
// allow GET once a value is cached
|
|
48
|
-
characteristic.on("get" /* GET */, async (callback) => {
|
|
48
|
+
characteristic.on("get" /* CharacteristicEventTypes.GET */, async (callback) => {
|
|
49
49
|
try {
|
|
50
50
|
const value = await (0, rxjs_1.firstValueFrom)(onCachedValue);
|
|
51
51
|
callback(null, value);
|
|
@@ -58,7 +58,7 @@ class BaseAccessory {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
if (setValue) {
|
|
61
|
-
characteristic.on("set" /* SET */, (newValue, callback) => {
|
|
61
|
+
characteristic.on("set" /* CharacteristicEventTypes.SET */, (newValue, callback) => {
|
|
62
62
|
Promise.resolve(setValue(newValue)).catch((e) => {
|
|
63
63
|
this.logger.error(e);
|
|
64
64
|
});
|
|
@@ -9,7 +9,7 @@ class BaseDataAccessory extends base_accessory_1.BaseAccessory {
|
|
|
9
9
|
const service = this.getService(serviceType, name, serviceSubType), characteristic = service.getCharacteristic(characteristicType), { device } = this;
|
|
10
10
|
if (requestUpdate) {
|
|
11
11
|
// Only register for GET if an async request should be made to get an updated value
|
|
12
|
-
characteristic.on("get" /* GET */, (callback) => {
|
|
12
|
+
characteristic.on("get" /* CharacteristicEventTypes.GET */, (callback) => {
|
|
13
13
|
try {
|
|
14
14
|
const value = getValue(device.data);
|
|
15
15
|
callback(null, value);
|
|
@@ -22,14 +22,14 @@ class BaseDataAccessory extends base_accessory_1.BaseAccessory {
|
|
|
22
22
|
}
|
|
23
23
|
if (setValue && setValueDebounceTime) {
|
|
24
24
|
const onValueToSet = new rxjs_1.Subject();
|
|
25
|
-
characteristic.on("set" /* SET */, (newValue, callback) => {
|
|
25
|
+
characteristic.on("set" /* CharacteristicEventTypes.SET */, (newValue, callback) => {
|
|
26
26
|
onValueToSet.next(newValue);
|
|
27
27
|
callback();
|
|
28
28
|
});
|
|
29
29
|
onValueToSet.pipe((0, operators_1.debounceTime)(setValueDebounceTime)).subscribe(setValue);
|
|
30
30
|
}
|
|
31
31
|
else if (setValue) {
|
|
32
|
-
characteristic.on("set" /* SET */, (newValue, callback) => {
|
|
32
|
+
characteristic.on("set" /* CharacteristicEventTypes.SET */, (newValue, callback) => {
|
|
33
33
|
Promise.resolve(setValue(newValue)).catch((e) => {
|
|
34
34
|
this.logger.error(e);
|
|
35
35
|
});
|
|
@@ -112,7 +112,7 @@ class StreamingSessionWrapper {
|
|
|
112
112
|
}));
|
|
113
113
|
const shouldTranscodeAudio = await this.libfdkAacInstalledPromise;
|
|
114
114
|
if (!shouldTranscodeAudio) {
|
|
115
|
-
return this.streamingSession.
|
|
115
|
+
return this.streamingSession.requestKeyFrame();
|
|
116
116
|
}
|
|
117
117
|
const transcodingPromise = this.streamingSession.startTranscoding({
|
|
118
118
|
input: ['-vn'],
|
|
@@ -167,7 +167,7 @@ class StreamingSessionWrapper {
|
|
|
167
167
|
}, audioSrtpSession = new werift_1.SrtpSession(getSessionConfig(remoteAudioSrtpOptions)), returnAudioTranscodedSplitter = new camera_utils_1.RtpSplitter(({ message }) => {
|
|
168
168
|
if (!cameraSpeakerActive) {
|
|
169
169
|
cameraSpeakerActive = true;
|
|
170
|
-
this.streamingSession.activateCameraSpeaker()
|
|
170
|
+
this.streamingSession.activateCameraSpeaker();
|
|
171
171
|
}
|
|
172
172
|
// decrypt the message
|
|
173
173
|
try {
|
|
@@ -225,7 +225,7 @@ class CameraSource {
|
|
|
225
225
|
cameraStreamCount: 10,
|
|
226
226
|
delegate: this,
|
|
227
227
|
streamingOptions: {
|
|
228
|
-
supportedCryptoSuites: [0 /* AES_CM_128_HMAC_SHA1_80 */],
|
|
228
|
+
supportedCryptoSuites: [0 /* SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80 */],
|
|
229
229
|
video: {
|
|
230
230
|
resolutions: [
|
|
231
231
|
[1280, 720, 30],
|
|
@@ -239,15 +239,15 @@ class CameraSource {
|
|
|
239
239
|
[320, 180, 30],
|
|
240
240
|
],
|
|
241
241
|
codec: {
|
|
242
|
-
profiles: [0 /* BASELINE */],
|
|
243
|
-
levels: [0 /* LEVEL3_1 */],
|
|
242
|
+
profiles: [0 /* H264Profile.BASELINE */],
|
|
243
|
+
levels: [0 /* H264Level.LEVEL3_1 */],
|
|
244
244
|
},
|
|
245
245
|
},
|
|
246
246
|
audio: {
|
|
247
247
|
codecs: [
|
|
248
248
|
{
|
|
249
|
-
type: "AAC-eld" /* AAC_ELD */,
|
|
250
|
-
samplerate: 16 /* KHZ_16 */,
|
|
249
|
+
type: "AAC-eld" /* AudioStreamingCodecType.AAC_ELD */,
|
|
250
|
+
samplerate: 16 /* AudioStreamingSamplerate.KHZ_16 */,
|
|
251
251
|
},
|
|
252
252
|
],
|
|
253
253
|
},
|
|
@@ -40,7 +40,7 @@ class LocationModeSwitch extends base_accessory_1.BaseAccessory {
|
|
|
40
40
|
}
|
|
41
41
|
currentState.updateValue(state);
|
|
42
42
|
});
|
|
43
|
-
currentState.on("get" /* GET */, async (callback) => {
|
|
43
|
+
currentState.on("get" /* CharacteristicEventTypes.GET */, async (callback) => {
|
|
44
44
|
location.getLocationMode().catch((e) => {
|
|
45
45
|
(0, util_1.logError)('Failed to retrieve location mode for ' + location.name);
|
|
46
46
|
(0, util_1.logError)(e);
|
|
@@ -51,12 +51,12 @@ class LocationModeSwitch extends base_accessory_1.BaseAccessory {
|
|
|
51
51
|
}
|
|
52
52
|
callback(null, state);
|
|
53
53
|
});
|
|
54
|
-
targetState.on("get" /* GET */, async (callback) => {
|
|
54
|
+
targetState.on("get" /* CharacteristicEventTypes.GET */, async (callback) => {
|
|
55
55
|
callback(null, this.targetState !== undefined
|
|
56
56
|
? this.targetState
|
|
57
57
|
: await getCurrentState());
|
|
58
58
|
});
|
|
59
|
-
targetState.on("set" /* SET */, async (state, callback) => {
|
|
59
|
+
targetState.on("set" /* CharacteristicEventTypes.SET */, async (state, callback) => {
|
|
60
60
|
const { Characteristic: { SecuritySystemTargetState: State }, } = hap_1.hap;
|
|
61
61
|
callback();
|
|
62
62
|
if (state === State.NIGHT_ARM) {
|
|
@@ -217,8 +217,8 @@ class RingPlatform {
|
|
|
217
217
|
}
|
|
218
218
|
const createHomebridgeAccessory = () => {
|
|
219
219
|
const accessory = new api.platformAccessory(displayName, uuid, isCamera
|
|
220
|
-
? 17 /* CAMERA */
|
|
221
|
-
: 11 /* SECURITY_SYSTEM */);
|
|
220
|
+
? 17 /* hap.Categories.CAMERA */
|
|
221
|
+
: 11 /* hap.Categories.SECURITY_SYSTEM */);
|
|
222
222
|
this.log.info(`Adding new accessory ${uuid} ${deviceType} ${displayName}`);
|
|
223
223
|
platformAccessories.push(accessory);
|
|
224
224
|
if (isCamera &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-ring",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.4",
|
|
4
4
|
"description": "Homebridge plugin for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
5
|
"main": "lib/homebridge/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@eneris/push-receiver": "^3.0.1",
|
|
43
|
-
"@homebridge/camera-utils": "^2.1.
|
|
43
|
+
"@homebridge/camera-utils": "^2.1.1",
|
|
44
44
|
"@homebridge/plugin-ui-utils": "^0.0.19",
|
|
45
|
-
"werift": "
|
|
45
|
+
"werift": "0.15.4",
|
|
46
46
|
"@types/socket.io-client": "1.4.36",
|
|
47
47
|
"colors": "^1.4.0",
|
|
48
48
|
"debug": "^4.3.4",
|
|
@@ -51,24 +51,24 @@
|
|
|
51
51
|
"socket.io-client": "^2.4.0",
|
|
52
52
|
"systeminformation": "^5.11.15",
|
|
53
53
|
"uuid": "^8.3.2",
|
|
54
|
-
"ws": "^8.
|
|
54
|
+
"ws": "^8.7.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@swc-node/register": "^1.5.1",
|
|
58
58
|
"@types/debug": "4.1.7",
|
|
59
59
|
"@types/jest": "27.5.1",
|
|
60
|
-
"@types/node": "17.0.
|
|
60
|
+
"@types/node": "17.0.36",
|
|
61
61
|
"@types/uuid": "8.3.4",
|
|
62
62
|
"@types/ws": "^8.5.3",
|
|
63
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
64
|
-
"@typescript-eslint/parser": "5.
|
|
65
|
-
"concurrently": "^7.2.
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "5.26.0",
|
|
64
|
+
"@typescript-eslint/parser": "5.26.0",
|
|
65
|
+
"concurrently": "^7.2.1",
|
|
66
66
|
"conventional-github-releaser": "3.1.5",
|
|
67
67
|
"dotenv": "16.0.1",
|
|
68
|
-
"esbuild": "^0.14.
|
|
69
|
-
"eslint": "8.
|
|
68
|
+
"esbuild": "^0.14.42",
|
|
69
|
+
"eslint": "8.16.0",
|
|
70
70
|
"eslint-config-prettier": "8.5.0",
|
|
71
|
-
"eslint-plugin-jest": "26.
|
|
71
|
+
"eslint-plugin-jest": "26.4.2",
|
|
72
72
|
"eslint-plugin-prettier": "4.0.0",
|
|
73
73
|
"express": "4.18.1",
|
|
74
74
|
"homebridge": "1.4.1",
|
|
@@ -77,12 +77,12 @@
|
|
|
77
77
|
"reflect-metadata": "0.1.13",
|
|
78
78
|
"rimraf": "3.0.2",
|
|
79
79
|
"standard-version": "9.5.0",
|
|
80
|
-
"ts-jest": "28.0.
|
|
81
|
-
"typescript": "4.
|
|
80
|
+
"ts-jest": "28.0.3",
|
|
81
|
+
"typescript": "4.7.2"
|
|
82
82
|
},
|
|
83
83
|
"engines": {
|
|
84
|
-
"node": "
|
|
85
|
-
"homebridge": ">=
|
|
84
|
+
"node": "^16",
|
|
85
|
+
"homebridge": ">=1.4.0"
|
|
86
86
|
},
|
|
87
87
|
"bugs": {
|
|
88
88
|
"url": "https://github.com/dgreif/ring/issues"
|