homebridge-ring 11.5.0 → 11.5.2

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,23 +1,17 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "CameraSource", {
6
- enumerable: true,
7
- get: ()=>CameraSource
8
- });
9
- const _hap = require("./hap");
10
- const _cameraUtils = require("@homebridge/camera-utils");
11
- const _homebridge = require("homebridge");
12
- const _util = require("ring-client-api/util");
13
- const _operators = require("rxjs/operators");
14
- const _rxjs = require("rxjs");
15
- const _fs = require("fs");
16
- const _util1 = require("util");
17
- const _ffmpeg = require("ring-client-api/ffmpeg");
18
- const _werift = require("werift");
19
- const _opusRepacketizer = require("./opus-repacketizer");
20
- const readFileAsync = (0, _util1.promisify)(_fs.readFile), cameraOfflinePath = require.resolve('../media/camera-offline.jpg'), snapshotsBlockedPath = require.resolve('../media/snapshots-blocked.jpg');
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CameraSource = void 0;
4
+ const hap_1 = require("./hap");
5
+ const camera_utils_1 = require("@homebridge/camera-utils");
6
+ const util_1 = require("ring-client-api/util");
7
+ const operators_1 = require("rxjs/operators");
8
+ const rxjs_1 = require("rxjs");
9
+ const fs_1 = require("fs");
10
+ const util_2 = require("util");
11
+ const ffmpeg_1 = require("ring-client-api/ffmpeg");
12
+ const werift_1 = require("werift");
13
+ const opus_repacketizer_1 = require("./opus-repacketizer");
14
+ const readFileAsync = (0, util_2.promisify)(fs_1.readFile), cameraOfflinePath = require.resolve('../media/camera-offline.jpg'), snapshotsBlockedPath = require.resolve('../media/snapshots-blocked.jpg');
21
15
  function getDurationSeconds(start) {
22
16
  return (Date.now() - start) / 1000;
23
17
  }
@@ -27,22 +21,87 @@ function getSessionConfig(srtpOptions) {
27
21
  localMasterKey: srtpOptions.srtpKey,
28
22
  localMasterSalt: srtpOptions.srtpSalt,
29
23
  remoteMasterKey: srtpOptions.srtpKey,
30
- remoteMasterSalt: srtpOptions.srtpSalt
24
+ remoteMasterSalt: srtpOptions.srtpSalt,
31
25
  },
32
- profile: 1
26
+ profile: 1,
33
27
  };
34
28
  }
35
29
  class StreamingSessionWrapper {
30
+ constructor(streamingSession, prepareStreamRequest, ringCamera, start) {
31
+ this.streamingSession = streamingSession;
32
+ this.prepareStreamRequest = prepareStreamRequest;
33
+ this.ringCamera = ringCamera;
34
+ this.start = start;
35
+ this.audioSsrc = hap_1.hap.CameraController.generateSynchronisationSource();
36
+ this.videoSsrc = hap_1.hap.CameraController.generateSynchronisationSource();
37
+ this.audioSrtp = (0, camera_utils_1.generateSrtpOptions)();
38
+ this.videoSrtp = (0, camera_utils_1.generateSrtpOptions)();
39
+ this.audioSplitter = new camera_utils_1.RtpSplitter();
40
+ this.videoSplitter = new camera_utils_1.RtpSplitter();
41
+ this.repacketizeAudioSplitter = new camera_utils_1.RtpSplitter();
42
+ this.libfdkAacInstalledPromise = (0, camera_utils_1.doesFfmpegSupportCodec)('libfdk_aac', (0, ffmpeg_1.getFfmpegPath)())
43
+ .then((supported) => {
44
+ if (!supported) {
45
+ (0, util_1.logError)('Streaming video only - found ffmpeg, but libfdk_aac is not installed. See https://github.com/dgreif/ring/wiki/FFmpeg for details.');
46
+ }
47
+ return supported;
48
+ })
49
+ .catch(() => {
50
+ (0, util_1.logError)('Streaming video only - ffmpeg was not found. See https://github.com/dgreif/ring/wiki/FFmpeg for details.');
51
+ return false;
52
+ });
53
+ const { targetAddress, video: { port: videoPort }, } = prepareStreamRequest,
54
+ // used to encrypt rtcp to HomeKit for keepalive
55
+ videoSrtcpSession = new werift_1.SrtcpSession(getSessionConfig(this.videoSrtp)), onReturnPacketReceived = new rxjs_1.Subject();
56
+ // Watch return packets to detect a dead stream from the HomeKit side
57
+ // This can happen if the user force-quits the Home app
58
+ this.videoSplitter.addMessageHandler(() => {
59
+ // return packet from HomeKit
60
+ onReturnPacketReceived.next(null);
61
+ return null;
62
+ });
63
+ this.audioSplitter.addMessageHandler(() => {
64
+ // return packet from HomeKit
65
+ onReturnPacketReceived.next(null);
66
+ return null;
67
+ });
68
+ streamingSession.addSubscriptions((0, rxjs_1.merge)((0, rxjs_1.of)(true).pipe((0, operators_1.delay)(15000)), onReturnPacketReceived)
69
+ .pipe((0, operators_1.debounceTime)(5000))
70
+ .subscribe(() => {
71
+ (0, util_1.logInfo)(`Live stream for ${this.ringCamera.name} appears to be inactive. (${getDurationSeconds(start)}s)`);
72
+ streamingSession.stop();
73
+ }));
74
+ // Periodically send a blank RTCP packet to the HomeKit video port
75
+ // Without this, HomeKit assumes the stream is dead after 30 second and sends a stop request
76
+ streamingSession.addSubscriptions((0, rxjs_1.interval)(500).subscribe(() => {
77
+ const senderInfo = new werift_1.RtcpSenderInfo({
78
+ ntpTimestamp: BigInt(0),
79
+ packetCount: 0,
80
+ octetCount: 0,
81
+ rtpTimestamp: 0,
82
+ }), senderReport = new werift_1.RtcpSrPacket({
83
+ ssrc: this.videoSsrc,
84
+ senderInfo: senderInfo,
85
+ }), message = videoSrtcpSession.encrypt(senderReport.serialize());
86
+ this.videoSplitter
87
+ .send(message, {
88
+ port: videoPort,
89
+ address: targetAddress,
90
+ })
91
+ .catch(util_1.logError);
92
+ }));
93
+ }
36
94
  listenForAudioPackets(startStreamRequest) {
37
- const { targetAddress , audio: { port: audioPort } } = this.prepareStreamRequest, { audio: { codec: audioCodec , sample_rate: audioSampleRate } } = startStreamRequest, // Repacketize the audio stream after it's been transcoded
38
- opusRepacketizer = new _opusRepacketizer.OpusRepacketizer(1), audioIntervalScale = audioSampleRate / 8, audioSrtpSession = new _werift.SrtpSession(getSessionConfig(this.audioSrtp));
95
+ const { targetAddress, audio: { port: audioPort }, } = this.prepareStreamRequest, { audio: { codec: audioCodec, sample_rate: audioSampleRate }, } = startStreamRequest,
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));
39
98
  let firstTimestamp, audioPacketCount = 0;
40
- this.repacketizeAudioSplitter.addMessageHandler(({ message })=>{
41
- let rtp = _werift.RtpPacket.deSerialize(message);
42
- if (audioCodec === _homebridge.AudioStreamingCodecType.OPUS) {
99
+ this.repacketizeAudioSplitter.addMessageHandler(({ message }) => {
100
+ let rtp = werift_1.RtpPacket.deSerialize(message);
101
+ if (audioCodec === "OPUS" /* AudioStreamingCodecType.OPUS */) {
43
102
  // borrowed from scrypted
44
103
  // Original source: https://github.com/koush/scrypted/blob/c13ba09889c3e0d9d3724cb7d49253c9d787fb97/plugins/homekit/src/types/camera/camera-streaming-srtp-sender.ts#L124-L143
45
- rtp = opusRepacketizer.repacketize(_werift.RtpPacket.deSerialize(message));
104
+ rtp = opusRepacketizer.repacketize(werift_1.RtpPacket.deSerialize(message));
46
105
  if (!rtp) {
47
106
  return null;
48
107
  }
@@ -63,63 +122,70 @@ class StreamingSessionWrapper {
63
122
  // audio will work so long as the rtp timestamps are created properly: which is a construct of the sample rate
64
123
  // HAP requests, and the packet time is respected,
65
124
  // opus 48khz will work just fine.
66
- rtp.header.timestamp = (firstTimestamp + audioPacketCount * 180 * audioIntervalScale) % 0xffffffff;
125
+ rtp.header.timestamp =
126
+ (firstTimestamp + audioPacketCount * 180 * audioIntervalScale) %
127
+ 0xffffffff;
67
128
  audioPacketCount++;
68
129
  }
69
130
  // encrypt the packet
70
131
  const encryptedPacket = audioSrtpSession.encrypt(rtp.payload, rtp.header);
71
132
  // send the encrypted packet to HomeKit
72
- this.audioSplitter.send(encryptedPacket, {
133
+ this.audioSplitter
134
+ .send(encryptedPacket, {
73
135
  port: audioPort,
74
- address: targetAddress
75
- }).catch(_util.logError);
136
+ address: targetAddress,
137
+ })
138
+ .catch(util_1.logError);
76
139
  return null;
77
140
  });
78
141
  }
79
142
  async activate(request) {
80
143
  let sentVideo = false;
81
- const { targetAddress , audio: { srtp_key: remoteAudioSrtpKey , srtp_salt: remoteAudioSrtpSalt } , video: { port: videoPort } } = this.prepareStreamRequest, // use to encrypt Ring video to HomeKit
82
- videoSrtpSession = new _werift.SrtpSession(getSessionConfig(this.videoSrtp));
144
+ const { targetAddress, audio: { srtp_key: remoteAudioSrtpKey, srtp_salt: remoteAudioSrtpSalt }, video: { port: videoPort }, } = this.prepareStreamRequest,
145
+ // use to encrypt Ring video to HomeKit
146
+ videoSrtpSession = new werift_1.SrtpSession(getSessionConfig(this.videoSrtp));
83
147
  // Set up packet forwarding for video stream
84
- this.streamingSession.addSubscriptions(this.streamingSession.onVideoRtp.subscribe(({ header , payload })=>{
148
+ this.streamingSession.addSubscriptions(this.streamingSession.onVideoRtp.subscribe(({ header, payload }) => {
85
149
  header.ssrc = this.videoSsrc;
86
150
  header.payloadType = request.video.pt;
87
151
  const encryptedPacket = videoSrtpSession.encrypt(payload, header);
88
152
  if (!sentVideo) {
89
153
  sentVideo = true;
90
- (0, _util.logInfo)(`Received stream data from ${this.ringCamera.name} (${getDurationSeconds(this.start)}s)`);
154
+ (0, util_1.logInfo)(`Received stream data from ${this.ringCamera.name} (${getDurationSeconds(this.start)}s)`);
91
155
  }
92
- this.videoSplitter.send(encryptedPacket, {
156
+ this.videoSplitter
157
+ .send(encryptedPacket, {
93
158
  port: videoPort,
94
- address: targetAddress
95
- }).catch(_util.logError);
159
+ address: targetAddress,
160
+ })
161
+ .catch(util_1.logError);
96
162
  }));
97
163
  const shouldTranscodeAudio = await this.libfdkAacInstalledPromise;
98
164
  if (!shouldTranscodeAudio) {
99
165
  return this.streamingSession.requestKeyFrame();
100
166
  }
101
167
  const transcodingPromise = this.streamingSession.startTranscoding({
102
- input: [
103
- '-vn'
104
- ],
168
+ input: ['-vn'],
105
169
  audio: [
106
170
  '-map',
107
171
  '0:a',
108
- ...request.audio.codec === _homebridge.AudioStreamingCodecType.OPUS ? [
109
- // OPUS specific - it works, but audio is very choppy
110
- '-acodec',
111
- 'libopus',
112
- '-frame_duration',
113
- request.audio.packet_time,
114
- '-application',
115
- 'lowdelay'
116
- ] : [
117
- // AAC-eld specific
118
- '-acodec',
119
- 'libfdk_aac',
120
- '-profile:a',
121
- 'aac_eld'
122
- ],
172
+ ...(request.audio.codec === "OPUS" /* AudioStreamingCodecType.OPUS */
173
+ ? [
174
+ // OPUS specific - it works, but audio is very choppy
175
+ '-acodec',
176
+ 'libopus',
177
+ '-frame_duration',
178
+ request.audio.packet_time,
179
+ '-application',
180
+ 'lowdelay',
181
+ ]
182
+ : [
183
+ // AAC-eld specific
184
+ '-acodec',
185
+ 'libfdk_aac',
186
+ '-profile:a',
187
+ 'aac_eld',
188
+ ]),
123
189
  // Shared options
124
190
  '-flags',
125
191
  '+global_header',
@@ -137,68 +203,60 @@ class StreamingSessionWrapper {
137
203
  this.audioSsrc,
138
204
  '-f',
139
205
  'rtp',
140
- `rtp://127.0.0.1:${await this.repacketizeAudioSplitter.portPromise}?pkt_size=188`
206
+ `rtp://127.0.0.1:${await this.repacketizeAudioSplitter
207
+ .portPromise}?pkt_size=188`,
141
208
  ],
142
209
  video: false,
143
- output: []
210
+ output: [],
144
211
  });
145
212
  let cameraSpeakerActive = false;
146
213
  // used to decrypt return audio from HomeKit to Ring
147
214
  const remoteAudioSrtpOptions = {
148
215
  srtpKey: remoteAudioSrtpKey,
149
- srtpSalt: remoteAudioSrtpSalt
150
- }, audioSrtpSession = new _werift.SrtpSession(getSessionConfig(remoteAudioSrtpOptions)), returnAudioTranscodedSplitter = new _cameraUtils.RtpSplitter(({ message })=>{
216
+ srtpSalt: remoteAudioSrtpSalt,
217
+ }, audioSrtpSession = new werift_1.SrtpSession(getSessionConfig(remoteAudioSrtpOptions)), returnAudioTranscodedSplitter = new camera_utils_1.RtpSplitter(({ message }) => {
151
218
  if (!cameraSpeakerActive) {
152
219
  cameraSpeakerActive = true;
153
220
  this.streamingSession.activateCameraSpeaker();
154
221
  }
155
222
  // decrypt the message
156
223
  try {
157
- const rtp = _werift.RtpPacket.deSerialize(message);
224
+ const rtp = werift_1.RtpPacket.deSerialize(message);
158
225
  rtp.payload = audioSrtpSession.decrypt(rtp.payload);
159
226
  // send to Ring - werift will handle encryption and other header params
160
227
  this.streamingSession.sendAudioPacket(rtp);
161
- } catch (_) {
162
- // deSerialize will sometimes fail, but the errors can be ignored
228
+ }
229
+ catch (_) {
230
+ // deSerialize will sometimes fail, but the errors can be ignored
163
231
  }
164
232
  return null;
165
- }), isRingUsingOpus = await this.streamingSession.isUsingOpus, returnAudioTranscoder = new _cameraUtils.ReturnAudioTranscoder({
233
+ }), isRingUsingOpus = await this.streamingSession.isUsingOpus, returnAudioTranscoder = new camera_utils_1.ReturnAudioTranscoder({
166
234
  prepareStreamRequest: this.prepareStreamRequest,
167
235
  startStreamRequest: request,
168
236
  incomingAudioOptions: {
169
237
  ssrc: this.audioSsrc,
170
- rtcpPort: 0
238
+ rtcpPort: 0, // we don't care about rtcp for incoming audio
171
239
  },
172
240
  outputArgs: [
173
241
  '-acodec',
174
- ...isRingUsingOpus ? [
175
- 'libopus',
176
- '-ac',
177
- 2,
178
- '-ar',
179
- '48k'
180
- ] : [
181
- 'pcm_mulaw',
182
- '-ac',
183
- 1,
184
- '-ar',
185
- '8k'
186
- ],
242
+ ...(isRingUsingOpus
243
+ ? ['libopus', '-ac', 2, '-ar', '48k']
244
+ : ['pcm_mulaw', '-ac', 1, '-ar', '8k']),
187
245
  '-flags',
188
246
  '+global_header',
189
247
  '-f',
190
248
  'rtp',
191
- `rtp://127.0.0.1:${await returnAudioTranscodedSplitter.portPromise}`
249
+ `rtp://127.0.0.1:${await returnAudioTranscodedSplitter.portPromise}`,
192
250
  ],
193
- ffmpegPath: (0, _ffmpeg.getFfmpegPath)(),
251
+ ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
194
252
  logger: {
195
- info: _util.logDebug,
196
- error: _util.logError
253
+ info: util_1.logDebug,
254
+ error: util_1.logError,
197
255
  },
198
256
  logLabel: `Return Audio (${this.ringCamera.name})`,
199
- returnAudioSplitter: this.audioSplitter
257
+ returnAudioSplitter: this.audioSplitter,
200
258
  });
201
- this.streamingSession.onCallEnded.pipe((0, _operators.take)(1)).subscribe(()=>{
259
+ this.streamingSession.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => {
202
260
  returnAudioTranscoder.stop();
203
261
  returnAudioTranscodedSplitter.close();
204
262
  });
@@ -212,65 +270,62 @@ class StreamingSessionWrapper {
212
270
  this.videoSplitter.close();
213
271
  this.streamingSession.stop();
214
272
  }
215
- constructor(streamingSession, prepareStreamRequest, ringCamera, start){
216
- this.streamingSession = streamingSession;
217
- this.prepareStreamRequest = prepareStreamRequest;
273
+ }
274
+ class CameraSource {
275
+ constructor(ringCamera, useOpus = false) {
218
276
  this.ringCamera = ringCamera;
219
- this.start = start;
220
- this.audioSsrc = _hap.hap.CameraController.generateSynchronisationSource();
221
- this.videoSsrc = _hap.hap.CameraController.generateSynchronisationSource();
222
- this.audioSrtp = (0, _cameraUtils.generateSrtpOptions)();
223
- this.videoSrtp = (0, _cameraUtils.generateSrtpOptions)();
224
- this.audioSplitter = new _cameraUtils.RtpSplitter();
225
- this.videoSplitter = new _cameraUtils.RtpSplitter();
226
- this.repacketizeAudioSplitter = new _cameraUtils.RtpSplitter();
227
- this.libfdkAacInstalledPromise = (0, _cameraUtils.doesFfmpegSupportCodec)('libfdk_aac', (0, _ffmpeg.getFfmpegPath)()).then((supported)=>{
228
- if (!supported) {
229
- (0, _util.logError)('Streaming video only - found ffmpeg, but libfdk_aac is not installed. See https://github.com/dgreif/ring/wiki/FFmpeg for details.');
230
- }
231
- return supported;
232
- }).catch(()=>{
233
- (0, _util.logError)('Streaming video only - ffmpeg was not found. See https://github.com/dgreif/ring/wiki/FFmpeg for details.');
234
- return false;
235
- });
236
- const { targetAddress , video: { port: videoPort } } = prepareStreamRequest, // used to encrypt rtcp to HomeKit for keepalive
237
- videoSrtcpSession = new _werift.SrtcpSession(getSessionConfig(this.videoSrtp)), onReturnPacketReceived = new _rxjs.Subject();
238
- // Watch return packets to detect a dead stream from the HomeKit side
239
- // This can happen if the user force-quits the Home app
240
- this.videoSplitter.addMessageHandler(()=>{
241
- // return packet from HomeKit
242
- onReturnPacketReceived.next(null);
243
- return null;
244
- });
245
- this.audioSplitter.addMessageHandler(()=>{
246
- // return packet from HomeKit
247
- onReturnPacketReceived.next(null);
248
- return null;
277
+ this.useOpus = useOpus;
278
+ this.controller = new hap_1.hap.CameraController({
279
+ cameraStreamCount: 10,
280
+ delegate: this,
281
+ streamingOptions: {
282
+ supportedCryptoSuites: [0 /* SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80 */],
283
+ video: {
284
+ resolutions: [
285
+ [1280, 720, 30],
286
+ [1024, 768, 30],
287
+ [640, 480, 30],
288
+ [640, 360, 30],
289
+ [480, 360, 30],
290
+ [480, 270, 30],
291
+ [320, 240, 30],
292
+ [320, 240, 15],
293
+ [320, 180, 30],
294
+ ],
295
+ codec: {
296
+ profiles: [0 /* H264Profile.BASELINE */],
297
+ levels: [0 /* H264Level.LEVEL3_1 */],
298
+ },
299
+ },
300
+ audio: {
301
+ codecs: this.useOpus
302
+ ? [
303
+ {
304
+ type: "OPUS" /* AudioStreamingCodecType.OPUS */,
305
+ // required by watch
306
+ samplerate: 8 /* AudioStreamingSamplerate.KHZ_8 */,
307
+ },
308
+ {
309
+ type: "OPUS" /* AudioStreamingCodecType.OPUS */,
310
+ samplerate: 16 /* AudioStreamingSamplerate.KHZ_16 */,
311
+ },
312
+ {
313
+ type: "OPUS" /* AudioStreamingCodecType.OPUS */,
314
+ samplerate: 24 /* AudioStreamingSamplerate.KHZ_24 */,
315
+ },
316
+ ]
317
+ : [
318
+ {
319
+ type: "AAC-eld" /* AudioStreamingCodecType.AAC_ELD */,
320
+ samplerate: 16 /* AudioStreamingSamplerate.KHZ_16 */,
321
+ },
322
+ ],
323
+ },
324
+ },
249
325
  });
250
- streamingSession.addSubscriptions((0, _rxjs.merge)((0, _rxjs.of)(true).pipe((0, _operators.delay)(15000)), onReturnPacketReceived).pipe((0, _operators.debounceTime)(5000)).subscribe(()=>{
251
- (0, _util.logInfo)(`Live stream for ${this.ringCamera.name} appears to be inactive. (${getDurationSeconds(start)}s)`);
252
- streamingSession.stop();
253
- }));
254
- // Periodically send a blank RTCP packet to the HomeKit video port
255
- // Without this, HomeKit assumes the stream is dead after 30 second and sends a stop request
256
- streamingSession.addSubscriptions((0, _rxjs.interval)(500).subscribe(()=>{
257
- const senderInfo = new _werift.RtcpSenderInfo({
258
- ntpTimestamp: BigInt(0),
259
- packetCount: 0,
260
- octetCount: 0,
261
- rtpTimestamp: 0
262
- }), senderReport = new _werift.RtcpSrPacket({
263
- ssrc: this.videoSsrc,
264
- senderInfo: senderInfo
265
- }), message = videoSrtcpSession.encrypt(senderReport.serialize());
266
- this.videoSplitter.send(message, {
267
- port: videoPort,
268
- address: targetAddress
269
- }).catch(_util.logError);
270
- }));
326
+ this.sessions = {};
327
+ this.fn = 1;
271
328
  }
272
- }
273
- class CameraSource {
274
329
  async loadSnapshot(imageUuid) {
275
330
  // cache a promise of the snapshot load
276
331
  // This prevents multiple concurrent requests for snapshot from pilling up and creating lots of logs
@@ -280,37 +335,38 @@ class CameraSource {
280
335
  this.previousLoadSnapshotPromise = this.loadAndCacheSnapshot(imageUuid);
281
336
  try {
282
337
  await this.previousLoadSnapshotPromise;
283
- } catch (_) {
284
- // ignore errors
285
- } finally{
338
+ }
339
+ catch (_) {
340
+ // ignore errors
341
+ }
342
+ finally {
286
343
  // clear so another request can be made
287
344
  this.previousLoadSnapshotPromise = undefined;
288
345
  }
289
346
  }
290
347
  async loadAndCacheSnapshot(imageUuid) {
291
348
  const start = Date.now();
292
- (0, _util.logDebug)(`Loading new snapshot into cache for ${this.ringCamera.name}${imageUuid ? ' by uuid' : ''}`);
349
+ (0, util_1.logDebug)(`Loading new snapshot into cache for ${this.ringCamera.name}${imageUuid ? ' by uuid' : ''}`);
293
350
  try {
294
- const previousSnapshot = this.cachedSnapshot, newSnapshot = await this.ringCamera.getSnapshot({
295
- uuid: imageUuid
296
- });
351
+ const previousSnapshot = this.cachedSnapshot, newSnapshot = await this.ringCamera.getSnapshot({ uuid: imageUuid });
297
352
  this.cachedSnapshot = newSnapshot;
298
353
  if (previousSnapshot !== newSnapshot) {
299
354
  // Keep the snapshots in cache 2 minutes longer than their lifetime
300
355
  // This allows users on LTE with wired camera to get snapshots each 60 second pull even though the cached snapshot is out of date
301
- setTimeout(()=>{
356
+ setTimeout(() => {
302
357
  if (this.cachedSnapshot === newSnapshot) {
303
358
  this.cachedSnapshot = undefined;
304
359
  }
305
360
  }, this.ringCamera.snapshotLifeTime + 2 * 60 * 1000);
306
361
  }
307
- (0, _util.logDebug)(`Snapshot cached for ${this.ringCamera.name}${imageUuid ? ' by uuid' : ''} (${getDurationSeconds(start)}s)`);
308
- } catch (e) {
362
+ (0, util_1.logDebug)(`Snapshot cached for ${this.ringCamera.name}${imageUuid ? ' by uuid' : ''} (${getDurationSeconds(start)}s)`);
363
+ }
364
+ catch (e) {
309
365
  this.cachedSnapshot = undefined;
310
- (0, _util.logDebug)(`Failed to cache snapshot for ${this.ringCamera.name} (${getDurationSeconds(start)}s), The camera currently reports that it is ${this.ringCamera.isOffline ? 'offline' : 'online'}`);
366
+ (0, util_1.logDebug)(`Failed to cache snapshot for ${this.ringCamera.name} (${getDurationSeconds(start)}s), The camera currently reports that it is ${this.ringCamera.isOffline ? 'offline' : 'online'}`);
311
367
  // log additioanl snapshot error message if one is present
312
368
  if (e.message.includes('Snapshot')) {
313
- (0, _util.logDebug)(e.message);
369
+ (0, util_1.logDebug)(e.message);
314
370
  }
315
371
  }
316
372
  }
@@ -321,9 +377,9 @@ class CameraSource {
321
377
  if (this.ringCamera.snapshotsAreBlocked) {
322
378
  return readFileAsync(snapshotsBlockedPath);
323
379
  }
324
- (0, _util.logDebug)(`${this.cachedSnapshot ? 'Used cached snapshot' : 'No snapshot cached'} for ${this.ringCamera.name}`);
380
+ (0, util_1.logDebug)(`${this.cachedSnapshot ? 'Used cached snapshot' : 'No snapshot cached'} for ${this.ringCamera.name}`);
325
381
  if (!this.ringCamera.hasSnapshotWithinLifetime) {
326
- this.loadSnapshot().catch(_util.logError);
382
+ this.loadSnapshot().catch(util_1.logError);
327
383
  }
328
384
  // may or may not have a snapshot cached
329
385
  return this.cachedSnapshot;
@@ -338,36 +394,38 @@ class CameraSource {
338
394
  // Not currently resizing the image.
339
395
  // HomeKit does a good job of resizing and doesn't seem to care if it's not right
340
396
  callback(undefined, snapshot);
341
- } catch (e) {
342
- (0, _util.logError)(`Error fetching snapshot for ${this.ringCamera.name}`);
343
- (0, _util.logError)(e);
397
+ }
398
+ catch (e) {
399
+ (0, util_1.logError)(`Error fetching snapshot for ${this.ringCamera.name}`);
400
+ (0, util_1.logError)(e);
344
401
  callback(e);
345
402
  }
346
403
  }
347
404
  async prepareStream(request, callback) {
348
405
  const start = Date.now();
349
- (0, _util.logInfo)(`Preparing Live Stream for ${this.ringCamera.name}`);
406
+ (0, util_1.logInfo)(`Preparing Live Stream for ${this.ringCamera.name}`);
350
407
  try {
351
408
  const liveCall = await this.ringCamera.startLiveCall(), session = new StreamingSessionWrapper(liveCall, request, this.ringCamera, start);
352
409
  this.sessions[request.sessionID] = session;
353
- (0, _util.logInfo)(`Stream Prepared for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
410
+ (0, util_1.logInfo)(`Stream Prepared for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
354
411
  callback(undefined, {
355
412
  audio: {
356
413
  port: await session.audioSplitter.portPromise,
357
414
  ssrc: session.audioSsrc,
358
415
  srtp_key: session.audioSrtp.srtpKey,
359
- srtp_salt: session.audioSrtp.srtpSalt
416
+ srtp_salt: session.audioSrtp.srtpSalt,
360
417
  },
361
418
  video: {
362
419
  port: await session.videoSplitter.portPromise,
363
420
  ssrc: session.videoSsrc,
364
421
  srtp_key: session.videoSrtp.srtpKey,
365
- srtp_salt: session.videoSrtp.srtpSalt
366
- }
422
+ srtp_salt: session.videoSrtp.srtpSalt,
423
+ },
367
424
  });
368
- } catch (e) {
369
- (0, _util.logError)(`Failed to prepare stream for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
370
- (0, _util.logError)(e);
425
+ }
426
+ catch (e) {
427
+ (0, util_1.logError)(`Failed to prepare stream for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
428
+ (0, util_1.logError)(e);
371
429
  callback(e);
372
430
  }
373
431
  }
@@ -378,108 +436,16 @@ class CameraSource {
378
436
  return;
379
437
  }
380
438
  if (requestType === 'start') {
381
- (0, _util.logInfo)(`Activating stream for ${this.ringCamera.name} (${getDurationSeconds(session.start)}s)`);
439
+ (0, util_1.logInfo)(`Activating stream for ${this.ringCamera.name} (${getDurationSeconds(session.start)}s)`);
382
440
  await session.activate(request);
383
- (0, _util.logInfo)(`Streaming active for ${this.ringCamera.name} (${getDurationSeconds(session.start)}s)`);
384
- } else if (requestType === 'stop') {
385
- (0, _util.logInfo)(`Stopped Live Stream for ${this.ringCamera.name}`);
441
+ (0, util_1.logInfo)(`Streaming active for ${this.ringCamera.name} (${getDurationSeconds(session.start)}s)`);
442
+ }
443
+ else if (requestType === 'stop') {
444
+ (0, util_1.logInfo)(`Stopped Live Stream for ${this.ringCamera.name}`);
386
445
  session.stop();
387
446
  delete this.sessions[sessionID];
388
447
  }
389
448
  callback();
390
449
  }
391
- constructor(ringCamera, useOpus = false){
392
- this.ringCamera = ringCamera;
393
- this.useOpus = useOpus;
394
- this.controller = new _hap.hap.CameraController({
395
- cameraStreamCount: 10,
396
- delegate: this,
397
- streamingOptions: {
398
- supportedCryptoSuites: [
399
- _homebridge.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80
400
- ],
401
- video: {
402
- resolutions: [
403
- [
404
- 1280,
405
- 720,
406
- 30
407
- ],
408
- [
409
- 1024,
410
- 768,
411
- 30
412
- ],
413
- [
414
- 640,
415
- 480,
416
- 30
417
- ],
418
- [
419
- 640,
420
- 360,
421
- 30
422
- ],
423
- [
424
- 480,
425
- 360,
426
- 30
427
- ],
428
- [
429
- 480,
430
- 270,
431
- 30
432
- ],
433
- [
434
- 320,
435
- 240,
436
- 30
437
- ],
438
- [
439
- 320,
440
- 240,
441
- 15
442
- ],
443
- [
444
- 320,
445
- 180,
446
- 30
447
- ]
448
- ],
449
- codec: {
450
- profiles: [
451
- _homebridge.H264Profile.BASELINE
452
- ],
453
- levels: [
454
- _homebridge.H264Level.LEVEL3_1
455
- ]
456
- }
457
- },
458
- audio: {
459
- codecs: this.useOpus ? [
460
- {
461
- type: _homebridge.AudioStreamingCodecType.OPUS,
462
- // required by watch
463
- samplerate: _homebridge.AudioStreamingSamplerate.KHZ_8
464
- },
465
- {
466
- type: _homebridge.AudioStreamingCodecType.OPUS,
467
- samplerate: _homebridge.AudioStreamingSamplerate.KHZ_16
468
- },
469
- {
470
- type: _homebridge.AudioStreamingCodecType.OPUS,
471
- samplerate: _homebridge.AudioStreamingSamplerate.KHZ_24
472
- }
473
- ] : [
474
- {
475
- type: _homebridge.AudioStreamingCodecType.AAC_ELD,
476
- samplerate: _homebridge.AudioStreamingSamplerate.KHZ_16
477
- }
478
- ]
479
- }
480
- }
481
- });
482
- this.sessions = {};
483
- this.fn = 1;
484
- }
485
450
  }
451
+ exports.CameraSource = CameraSource;