homebridge-ring 12.0.0 → 12.1.0
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 +22 -0
- package/branding/icon.png +0 -0
- package/lib/camera-source.js +17 -22
- package/lib/opus-repacketizer.js +95 -16
- package/package.json +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# homebridge-ring
|
|
2
2
|
|
|
3
|
+
## 12.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`7354a21`](https://github.com/dgreif/ring/commit/7354a21e23a75ba7b7f2e67098bf0816c75c3c42) Thanks [@dgreif](https://github.com/dgreif)! - Update dependencies
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`7354a21`](https://github.com/dgreif/ring/commit/7354a21e23a75ba7b7f2e67098bf0816c75c3c42)]:
|
|
12
|
+
- ring-client-api@12.1.0
|
|
13
|
+
|
|
14
|
+
## 12.0.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [`3604f30`](https://github.com/dgreif/ring/commit/3604f30b272f97968fd0f73368de720e33991555) Thanks [@dgreif](https://github.com/dgreif)! - Update dependencies
|
|
19
|
+
|
|
20
|
+
- [#1307](https://github.com/dgreif/ring/pull/1307) [`c197ad1`](https://github.com/dgreif/ring/commit/c197ad14b106bbfaf9080112296a0ffd9136ec84) Thanks [@tsightler](https://github.com/tsightler)! - Update Opus processing to handle all possible bitrates and packet lengths which should fix audio issues over LTE/5G
|
|
21
|
+
Minor tweaks to AAC-ELD processing for slightly improved audio
|
|
22
|
+
- Updated dependencies [[`3604f30`](https://github.com/dgreif/ring/commit/3604f30b272f97968fd0f73368de720e33991555)]:
|
|
23
|
+
- ring-client-api@12.0.1
|
|
24
|
+
|
|
3
25
|
## 12.0.0
|
|
4
26
|
|
|
5
27
|
### Breaking Changes
|
|
Binary file
|
package/lib/camera-source.js
CHANGED
|
@@ -92,16 +92,16 @@ class StreamingSessionWrapper {
|
|
|
92
92
|
}));
|
|
93
93
|
}
|
|
94
94
|
listenForAudioPackets(startStreamRequest) {
|
|
95
|
-
const { targetAddress, audio: { port: audioPort }, } = this.prepareStreamRequest, { audio: { codec: audioCodec, sample_rate: audioSampleRate }, } = startStreamRequest,
|
|
95
|
+
const { targetAddress, audio: { port: audioPort }, } = this.prepareStreamRequest, { audio: { codec: audioCodec, sample_rate: audioSampleRate, packet_time: audioPacketTime, }, } = startStreamRequest,
|
|
96
96
|
// Repacketize the audio stream after it's been transcoded
|
|
97
|
-
opusRepacketizer = new opus_repacketizer_1.OpusRepacketizer(
|
|
97
|
+
opusRepacketizer = new opus_repacketizer_1.OpusRepacketizer(audioPacketTime / 20), audioIntervalScale = ((audioSampleRate / 8) * audioPacketTime) / 20, audioSrtpSession = new werift_1.SrtpSession(getSessionConfig(this.audioSrtp));
|
|
98
98
|
let firstTimestamp, audioPacketCount = 0;
|
|
99
99
|
this.repacketizeAudioSplitter.addMessageHandler(({ message }) => {
|
|
100
100
|
let rtp = werift_1.RtpPacket.deSerialize(message);
|
|
101
101
|
if (audioCodec === "OPUS" /* AudioStreamingCodecType.OPUS */) {
|
|
102
102
|
// borrowed from scrypted
|
|
103
103
|
// Original source: https://github.com/koush/scrypted/blob/c13ba09889c3e0d9d3724cb7d49253c9d787fb97/plugins/homekit/src/types/camera/camera-streaming-srtp-sender.ts#L124-L143
|
|
104
|
-
rtp = opusRepacketizer.repacketize(
|
|
104
|
+
rtp = opusRepacketizer.repacketize(rtp);
|
|
105
105
|
if (!rtp) {
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
@@ -123,7 +123,7 @@ class StreamingSessionWrapper {
|
|
|
123
123
|
// HAP requests, and the packet time is respected,
|
|
124
124
|
// opus 48khz will work just fine.
|
|
125
125
|
rtp.header.timestamp =
|
|
126
|
-
(firstTimestamp + audioPacketCount *
|
|
126
|
+
(firstTimestamp + audioPacketCount * 160 * audioIntervalScale) %
|
|
127
127
|
0xffffffff;
|
|
128
128
|
audioPacketCount++;
|
|
129
129
|
}
|
|
@@ -141,7 +141,7 @@ class StreamingSessionWrapper {
|
|
|
141
141
|
}
|
|
142
142
|
async activate(request) {
|
|
143
143
|
let sentVideo = false;
|
|
144
|
-
const { targetAddress,
|
|
144
|
+
const { targetAddress, video: { port: videoPort }, } = this.prepareStreamRequest,
|
|
145
145
|
// use to encrypt Ring video to HomeKit
|
|
146
146
|
videoSrtpSession = new werift_1.SrtpSession(getSessionConfig(this.videoSrtp));
|
|
147
147
|
// Set up packet forwarding for video stream
|
|
@@ -177,9 +177,7 @@ class StreamingSessionWrapper {
|
|
|
177
177
|
'-frame_duration',
|
|
178
178
|
request.audio.packet_time,
|
|
179
179
|
'-application',
|
|
180
|
-
'
|
|
181
|
-
'-vbr',
|
|
182
|
-
'off',
|
|
180
|
+
'lowdelay',
|
|
183
181
|
]
|
|
184
182
|
: [
|
|
185
183
|
// AAC-eld specific
|
|
@@ -187,6 +185,10 @@ class StreamingSessionWrapper {
|
|
|
187
185
|
'libfdk_aac',
|
|
188
186
|
'-profile:a',
|
|
189
187
|
'aac_eld',
|
|
188
|
+
'-eld_sbr:a',
|
|
189
|
+
'1',
|
|
190
|
+
'-eld_v2',
|
|
191
|
+
'1',
|
|
190
192
|
]),
|
|
191
193
|
// Shared options
|
|
192
194
|
'-flags',
|
|
@@ -206,26 +208,21 @@ class StreamingSessionWrapper {
|
|
|
206
208
|
'-f',
|
|
207
209
|
'rtp',
|
|
208
210
|
`rtp://127.0.0.1:${await this.repacketizeAudioSplitter
|
|
209
|
-
.portPromise}?pkt_size=
|
|
211
|
+
.portPromise}?pkt_size=376`,
|
|
210
212
|
],
|
|
211
213
|
video: false,
|
|
212
214
|
output: [],
|
|
213
215
|
});
|
|
214
216
|
let cameraSpeakerActive = false;
|
|
215
|
-
// used to
|
|
216
|
-
const
|
|
217
|
-
srtpKey: remoteAudioSrtpKey,
|
|
218
|
-
srtpSalt: remoteAudioSrtpSalt,
|
|
219
|
-
}, audioSrtpSession = new werift_1.SrtpSession(getSessionConfig(remoteAudioSrtpOptions)), returnAudioTranscodedSplitter = new camera_utils_1.RtpSplitter(({ message }) => {
|
|
217
|
+
// used to send return audio from HomeKit to Ring
|
|
218
|
+
const returnAudioTranscodedSplitter = new camera_utils_1.RtpSplitter(({ message }) => {
|
|
220
219
|
if (!cameraSpeakerActive) {
|
|
221
220
|
cameraSpeakerActive = true;
|
|
222
221
|
this.streamingSession.activateCameraSpeaker();
|
|
223
222
|
}
|
|
224
|
-
//
|
|
223
|
+
// deserialize and send to Ring - werift will handle encryption and other header params
|
|
225
224
|
try {
|
|
226
225
|
const rtp = werift_1.RtpPacket.deSerialize(message);
|
|
227
|
-
rtp.payload = audioSrtpSession.decrypt(rtp.payload);
|
|
228
|
-
// send to Ring - werift will handle encryption and other header params
|
|
229
226
|
this.streamingSession.sendAudioPacket(rtp);
|
|
230
227
|
}
|
|
231
228
|
catch (_) {
|
|
@@ -245,15 +242,13 @@ class StreamingSessionWrapper {
|
|
|
245
242
|
? [
|
|
246
243
|
'libopus',
|
|
247
244
|
'-ac',
|
|
248
|
-
1,
|
|
245
|
+
'1',
|
|
249
246
|
'-ar',
|
|
250
247
|
'24k',
|
|
251
|
-
'-vbr',
|
|
252
|
-
'off',
|
|
253
248
|
'-b:a',
|
|
254
249
|
'24k',
|
|
255
250
|
'-application',
|
|
256
|
-
'
|
|
251
|
+
'lowdelay',
|
|
257
252
|
]
|
|
258
253
|
: ['pcm_mulaw', '-ac', 1, '-ar', '8k']),
|
|
259
254
|
'-flags',
|
|
@@ -303,7 +298,7 @@ class CameraSource {
|
|
|
303
298
|
[480, 360, 30],
|
|
304
299
|
[480, 270, 30],
|
|
305
300
|
[320, 240, 30],
|
|
306
|
-
[320, 240, 15],
|
|
301
|
+
[320, 240, 15], // Apple Watch requires this configuration
|
|
307
302
|
[320, 180, 30],
|
|
308
303
|
],
|
|
309
304
|
codec: {
|
package/lib/opus-repacketizer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// OpusRepacketizer is borrowed from scrypted
|
|
3
|
-
// Original source: https://github.com/koush/scrypted/blob/
|
|
3
|
+
// Original source: https://github.com/koush/scrypted/blob/3150a3033515a3886af1e6b35a0ba7432b63e02b/plugins/homekit/src/types/camera/opus-repacketizer.ts
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.OpusRepacketizer = void 0;
|
|
6
6
|
// https://datatracker.ietf.org/doc/html/rfc6716
|
|
7
|
-
// INPUT
|
|
7
|
+
// INPUT (for single frame sample, see RFC for other 4 code values)
|
|
8
8
|
// 0 1 2 3
|
|
9
9
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
10
10
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
@@ -58,25 +58,104 @@ exports.OpusRepacketizer = void 0;
|
|
|
58
58
|
class OpusRepacketizer {
|
|
59
59
|
constructor(framesPerPacket) {
|
|
60
60
|
this.framesPerPacket = framesPerPacket;
|
|
61
|
-
this.
|
|
61
|
+
this.depacketized = [];
|
|
62
62
|
}
|
|
63
63
|
// repacketize a packet with a single frame into a packet with multiple frames.
|
|
64
64
|
repacketize(packet) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
const code = packet.payload[0] & 0b00000011;
|
|
66
|
+
let offset;
|
|
67
|
+
// see Frame Length Coding in RFC
|
|
68
|
+
const decodeFrameLength = () => {
|
|
69
|
+
let frameLength = packet.payload.readUInt8(offset);
|
|
70
|
+
if (frameLength >= 252) {
|
|
71
|
+
offset++;
|
|
72
|
+
frameLength += packet.payload.readUInt8(offset) * 4;
|
|
73
|
+
}
|
|
74
|
+
return frameLength;
|
|
75
|
+
};
|
|
76
|
+
// code 0: cbr, 1 packet
|
|
77
|
+
// code 1: cbr, 2 packets
|
|
78
|
+
// code 2: vbr, 2 packets
|
|
79
|
+
// code 3: cbr/vbr signaled, variable packets
|
|
80
|
+
if (code === 0) {
|
|
81
|
+
if (this.framesPerPacket === 1 && !this.depacketized.length)
|
|
82
|
+
return packet;
|
|
83
|
+
// depacketize by stripping off the config byte
|
|
84
|
+
this.depacketized.push(packet.payload.subarray(1));
|
|
69
85
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
else if (code === 1) {
|
|
87
|
+
if (this.framesPerPacket === 2 && !this.depacketized.length)
|
|
88
|
+
return packet;
|
|
89
|
+
// depacketize by dividing the remaining payload into two equal sized frames
|
|
90
|
+
const remaining = packet.payload.length - 1;
|
|
91
|
+
if (remaining % 2) {
|
|
92
|
+
throw new Error('expected equal sized opus packets (code 1)');
|
|
93
|
+
}
|
|
94
|
+
const frameLength = remaining / 2;
|
|
95
|
+
this.depacketized.push(packet.payload.subarray(1, 1 + frameLength));
|
|
96
|
+
this.depacketized.push(packet.payload.subarray(1 + frameLength));
|
|
97
|
+
}
|
|
98
|
+
else if (code === 2) {
|
|
99
|
+
if (this.framesPerPacket === 2 && !this.depacketized.length)
|
|
100
|
+
return packet;
|
|
101
|
+
offset = 1;
|
|
102
|
+
// depacketize by dividing the remaining payload into two inequal sized frames
|
|
103
|
+
const frameLength = decodeFrameLength();
|
|
104
|
+
this.depacketized.push(packet.payload.subarray(offset, offset + frameLength));
|
|
105
|
+
this.depacketized.push(packet.payload.subarray(offset + frameLength));
|
|
73
106
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
else if (code === 3) {
|
|
108
|
+
// code 3 packet will have a frame count and padding indicator, and whether the packets
|
|
109
|
+
// are equal size or not.
|
|
110
|
+
const frameCountByte = packet.payload[1], packetFrameCount = frameCountByte & 0b00111111, vbr = frameCountByte & 0b10000000;
|
|
111
|
+
if (this.framesPerPacket === packetFrameCount &&
|
|
112
|
+
!this.depacketized.length) {
|
|
113
|
+
return packet;
|
|
114
|
+
}
|
|
115
|
+
const paddingIndicator = frameCountByte & 0b01000000;
|
|
116
|
+
offset = 2;
|
|
117
|
+
let padding = 0;
|
|
118
|
+
if (paddingIndicator) {
|
|
119
|
+
padding = packet.payload.readUInt8(offset);
|
|
120
|
+
offset++;
|
|
121
|
+
if (padding === 255) {
|
|
122
|
+
padding = 254 + packet.payload.readUInt8(offset);
|
|
123
|
+
offset++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!vbr) {
|
|
127
|
+
const remaining = packet.payload.length - offset - padding;
|
|
128
|
+
if (remaining % packetFrameCount) {
|
|
129
|
+
throw new Error('expected equal sized opus packets (code 3)');
|
|
130
|
+
}
|
|
131
|
+
const frameLength = remaining / packetFrameCount;
|
|
132
|
+
for (let i = 0; i < packetFrameCount; i++) {
|
|
133
|
+
const start = offset + i * frameLength, end = start + frameLength;
|
|
134
|
+
this.depacketized.push(packet.payload.subarray(start, end));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const frameLengths = [];
|
|
139
|
+
for (let i = 0; i < packetFrameCount; i++) {
|
|
140
|
+
const frameLength = decodeFrameLength();
|
|
141
|
+
frameLengths.push(frameLength);
|
|
142
|
+
}
|
|
143
|
+
for (let i = 0; i < packetFrameCount; i++) {
|
|
144
|
+
const frameLength = frameLengths[i], start = offset;
|
|
145
|
+
offset += frameLength;
|
|
146
|
+
this.depacketized.push(packet.payload.subarray(start, offset));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (this.depacketized.length < this.framesPerPacket)
|
|
151
|
+
return;
|
|
152
|
+
const depacketized = this.depacketized.slice(0, this.framesPerPacket);
|
|
153
|
+
this.depacketized = this.depacketized.slice(this.framesPerPacket);
|
|
154
|
+
// reuse the config and stereo indicator, but change the code to 3.
|
|
155
|
+
let toc = packet.payload[0];
|
|
156
|
+
toc |= 0b00000011;
|
|
157
|
+
// vbr | padding indicator | packet count
|
|
158
|
+
const frameCountByte = 0b10000000 | this.framesPerPacket, newHeader = [toc, frameCountByte];
|
|
80
159
|
// M-1 length bytes
|
|
81
160
|
newHeader.push(...depacketized.slice(0, -1).map((data) => data.length));
|
|
82
161
|
const headerBuffer = Buffer.from(newHeader), payload = Buffer.concat([headerBuffer, ...depacketized]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-ring",
|
|
3
|
-
"
|
|
3
|
+
"displayName": "Ring",
|
|
4
|
+
"version": "12.1.0",
|
|
4
5
|
"description": "Homebridge plugin for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
6
|
"main": "lib/index.js",
|
|
6
7
|
"scripts": {
|
|
@@ -11,18 +12,18 @@
|
|
|
11
12
|
"dev:run": "nodemon --watch . --watch ../ring-client-api/lib --exec 'RING_DEBUG=true homebridge -P ./ -U ../../.homebridge'"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
|
-
"@homebridge/camera-utils": "^2.2.
|
|
15
|
-
"@homebridge/plugin-ui-utils": "^
|
|
16
|
-
"ring-client-api": "12.
|
|
17
|
-
"werift": "0.18.
|
|
15
|
+
"@homebridge/camera-utils": "^2.2.4",
|
|
16
|
+
"@homebridge/plugin-ui-utils": "^1.0.0",
|
|
17
|
+
"ring-client-api": "12.1.0",
|
|
18
|
+
"werift": "0.18.17"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"concurrently": "^8.2.
|
|
21
|
+
"concurrently": "^8.2.2",
|
|
21
22
|
"eslint-config-shared": "*",
|
|
22
|
-
"homebridge": "1.
|
|
23
|
-
"nodemon": "^3.0.
|
|
23
|
+
"homebridge": "1.7.0",
|
|
24
|
+
"nodemon": "^3.0.2",
|
|
24
25
|
"tsconfig": "*",
|
|
25
|
-
"typescript": "5.
|
|
26
|
+
"typescript": "5.2.2"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": "^18 || ^20",
|