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 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
@@ -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(1), audioIntervalScale = audioSampleRate / 8, audioSrtpSession = new werift_1.SrtpSession(getSessionConfig(this.audioSrtp));
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(werift_1.RtpPacket.deSerialize(message));
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 * 180 * audioIntervalScale) %
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, audio: { srtp_key: remoteAudioSrtpKey, srtp_salt: remoteAudioSrtpSalt }, video: { port: videoPort }, } = this.prepareStreamRequest,
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
- 'voip',
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=188`,
211
+ .portPromise}?pkt_size=376`,
210
212
  ],
211
213
  video: false,
212
214
  output: [],
213
215
  });
214
216
  let cameraSpeakerActive = false;
215
- // used to decrypt return audio from HomeKit to Ring
216
- const remoteAudioSrtpOptions = {
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
- // decrypt the message
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
- 'voip',
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: {
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  // OpusRepacketizer is borrowed from scrypted
3
- // Original source: https://github.com/koush/scrypted/blob/c13ba09889c3e0d9d3724cb7d49253c9d787fb97/plugins/homekit/src/types/camera/opus-repacketizer.ts
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.packets = [];
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
- if (this.framesPerPacket === 1)
66
- return packet;
67
- if (packet.payload[0] & 0b00000011) {
68
- throw new Error('expected opus packet with a single frame.');
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
- this.packets.push(packet);
71
- if (this.packets.length !== this.framesPerPacket) {
72
- return;
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
- const first = this.packets[0], depacketized = this.packets.map((p) => p.payload.subarray(1));
75
- this.packets = [];
76
- let config = first.payload[0];
77
- config |= 0b00000011;
78
- const frameCount = 0b10000000 | this.framesPerPacket, newHeader = [config, frameCount];
79
- // depacketize by stripping off the config byte
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
- "version": "12.0.0",
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.0",
15
- "@homebridge/plugin-ui-utils": "^0.1.0",
16
- "ring-client-api": "12.0.0",
17
- "werift": "0.18.5"
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.0",
21
+ "concurrently": "^8.2.2",
21
22
  "eslint-config-shared": "*",
22
- "homebridge": "1.6.1",
23
- "nodemon": "^3.0.1",
23
+ "homebridge": "1.7.0",
24
+ "nodemon": "^3.0.2",
24
25
  "tsconfig": "*",
25
- "typescript": "5.1.6"
26
+ "typescript": "5.2.2"
26
27
  },
27
28
  "engines": {
28
29
  "node": "^18 || ^20",