homebridge-ring 9.24.0 → 9.25.0-beta.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 +9 -0
- package/lib/api/rest-client.d.ts +1 -0
- package/lib/api/rest-client.js +7 -2
- package/lib/api/ring-camera.d.ts +10 -7
- package/lib/api/ring-camera.js +66 -88
- package/lib/api/ring-types.d.ts +1 -4
- package/lib/api/ring-types.js +3 -1
- package/lib/api/sip-session.d.ts +4 -0
- package/lib/api/sip-session.js +25 -21
- package/lib/homebridge/camera-source.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
## [9.25.0-beta.0](https://github.com/dgreif/ring/compare/v9.24.0...v9.25.0-beta.0) (2022-01-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **api:** decouple ffmpeg prep from execution ([#859](https://github.com/dgreif/ring/issues/859)) ([e3cc9d1](https://github.com/dgreif/ring/commit/e3cc9d15584377bf2be94a9d457efc3dfcb4bd8b))
|
|
11
|
+
* handle new camera types with no battery ([11ae286](https://github.com/dgreif/ring/commit/11ae28697d8cb0e2da71e880c82e1883f04047b9))
|
|
12
|
+
* slightly faster snapshots via new endpoint ([b840471](https://github.com/dgreif/ring/commit/b840471bc6b7b66e5873370251bd57c1a35b267a))
|
|
13
|
+
|
|
5
14
|
## [9.24.0](https://github.com/dgreif/ring/compare/v9.23.0...v9.24.0) (2022-01-22)
|
|
6
15
|
|
|
7
16
|
|
package/lib/api/rest-client.d.ts
CHANGED
package/lib/api/rest-client.js
CHANGED
|
@@ -44,8 +44,13 @@ function requestWithRetry(requestOptions) {
|
|
|
44
44
|
return __awaiter(this, void 0, void 0, function* () {
|
|
45
45
|
try {
|
|
46
46
|
const options = Object.assign(Object.assign({}, defaultRequestOptions), requestOptions), { headers, body } = (yield (0, got_1.default)(options)), data = body;
|
|
47
|
-
if (data !== null && typeof data === 'object'
|
|
48
|
-
|
|
47
|
+
if (data !== null && typeof data === 'object') {
|
|
48
|
+
if (headers.date) {
|
|
49
|
+
data.responseTimestamp = new Date(headers.date).getTime();
|
|
50
|
+
}
|
|
51
|
+
if (headers['x-time-millis']) {
|
|
52
|
+
data.timeMillis = Number(headers['x-time-millis']);
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
return data;
|
|
51
56
|
}
|
package/lib/api/ring-camera.d.ts
CHANGED
|
@@ -23,7 +23,6 @@ export declare class RingCamera extends Subscribed {
|
|
|
23
23
|
onData: BehaviorSubject<CameraData>;
|
|
24
24
|
hasLight: boolean;
|
|
25
25
|
hasSiren: boolean;
|
|
26
|
-
hasBattery: boolean;
|
|
27
26
|
onRequestUpdate: Subject<unknown>;
|
|
28
27
|
onRequestActiveDings: Subject<unknown>;
|
|
29
28
|
onNewDing: Subject<ActiveDing>;
|
|
@@ -40,6 +39,7 @@ export declare class RingCamera extends Subscribed {
|
|
|
40
39
|
get name(): string;
|
|
41
40
|
get activeDings(): ActiveDing[];
|
|
42
41
|
get batteryLevel(): number | null;
|
|
42
|
+
get hasBattery(): boolean;
|
|
43
43
|
get hasLowBattery(): boolean;
|
|
44
44
|
get isCharging(): boolean;
|
|
45
45
|
get operatingOnBattery(): boolean;
|
|
@@ -75,18 +75,21 @@ export declare class RingCamera extends Subscribed {
|
|
|
75
75
|
}): Promise<string>;
|
|
76
76
|
private isTimestampInLifeTime;
|
|
77
77
|
get snapshotsAreBlocked(): boolean;
|
|
78
|
-
private getSnapshotTimestamp;
|
|
79
|
-
private refreshSnapshotInProgress?;
|
|
80
78
|
get snapshotLifeTime(): number;
|
|
79
|
+
private lastSnapshotTimestamp;
|
|
81
80
|
private lastSnapshotTimestampLocal;
|
|
82
81
|
private lastSnapshotPromise?;
|
|
83
82
|
get currentTimestampAge(): number;
|
|
84
|
-
get currentTimestampExpiresIn(): number;
|
|
85
83
|
get hasSnapshotWithinLifetime(): boolean;
|
|
86
84
|
private checkIfSnapshotsAreBlocked;
|
|
87
|
-
private
|
|
88
|
-
private
|
|
89
|
-
getSnapshot(): Promise<Buffer
|
|
85
|
+
private shouldUseExistingSnapshotPromise;
|
|
86
|
+
private fetchingSnapshot;
|
|
87
|
+
getSnapshot(): Promise<Buffer>;
|
|
88
|
+
getNextSnapshot({ afterMs, maxWaitMs, force, }: {
|
|
89
|
+
afterMs?: number;
|
|
90
|
+
maxWaitMs?: number;
|
|
91
|
+
force?: boolean;
|
|
92
|
+
}): Promise<Buffer & import("./rest-client").ExtendedResponse>;
|
|
90
93
|
getSipOptions(): Promise<SipOptions>;
|
|
91
94
|
getUpdatedSipOptions(expiredDingId: string): Promise<SipOptions>;
|
|
92
95
|
createSipSession(options?: {
|
package/lib/api/ring-camera.js
CHANGED
|
@@ -18,8 +18,7 @@ const camera_utils_1 = require("@homebridge/camera-utils");
|
|
|
18
18
|
const util_1 = require("./util");
|
|
19
19
|
const sip_session_1 = require("./sip-session");
|
|
20
20
|
const subscribed_1 = require("./subscribed");
|
|
21
|
-
const
|
|
22
|
-
maxSnapshotRefreshAttempts = (maxSnapshotRefreshSeconds * 1000) / snapshotRefreshDelay, fullDayMs = 24 * 60 * 60 * 1000;
|
|
21
|
+
const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
|
|
23
22
|
function parseBatteryLife(batteryLife) {
|
|
24
23
|
if (batteryLife === null || batteryLife === undefined) {
|
|
25
24
|
return null;
|
|
@@ -83,19 +82,15 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
83
82
|
this.onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.mapTo)(null), // no value needed, event is what matters
|
|
84
83
|
(0, operators_1.share)());
|
|
85
84
|
this.expiredDingIds = [];
|
|
85
|
+
this.lastSnapshotTimestamp = 0;
|
|
86
86
|
this.lastSnapshotTimestampLocal = 0;
|
|
87
|
+
this.fetchingSnapshot = false;
|
|
87
88
|
this.id = this.initialData.id;
|
|
88
89
|
this.deviceType = this.initialData.kind;
|
|
89
90
|
this.model = ring_types_1.RingCameraModel[this.initialData.kind] || 'Unknown Model';
|
|
90
91
|
this.onData = new rxjs_1.BehaviorSubject(this.initialData);
|
|
91
92
|
this.hasLight = this.initialData.led_status !== undefined;
|
|
92
93
|
this.hasSiren = this.initialData.siren_status !== undefined;
|
|
93
|
-
this.hasBattery =
|
|
94
|
-
(0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
|
|
95
|
-
(typeof this.initialData.battery_life === 'string' &&
|
|
96
|
-
this.batteryLevel !== null &&
|
|
97
|
-
this.batteryLevel < 100 &&
|
|
98
|
-
this.batteryLevel >= 0);
|
|
99
94
|
this.onBatteryLevel = this.onData.pipe((0, operators_1.map)(getBatteryLevel), (0, operators_1.distinctUntilChanged)());
|
|
100
95
|
this.onInHomeDoorbellStatus = this.onData.pipe((0, operators_1.map)(({ settings: { chime_settings } }) => {
|
|
101
96
|
return Boolean(chime_settings === null || chime_settings === void 0 ? void 0 : chime_settings.enable);
|
|
@@ -131,6 +126,15 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
131
126
|
get batteryLevel() {
|
|
132
127
|
return getBatteryLevel(this.data);
|
|
133
128
|
}
|
|
129
|
+
get hasBattery() {
|
|
130
|
+
if (this.batteryLevel === null) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return ((0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
|
|
134
|
+
(typeof this.initialData.battery_life === 'string' &&
|
|
135
|
+
this.batteryLevel < 100 &&
|
|
136
|
+
this.batteryLevel >= 0));
|
|
137
|
+
}
|
|
134
138
|
get hasLowBattery() {
|
|
135
139
|
return this.data.alerts.battery === 'low';
|
|
136
140
|
}
|
|
@@ -312,34 +316,14 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
312
316
|
get snapshotsAreBlocked() {
|
|
313
317
|
return this.data.settings.motion_detection_enabled === false;
|
|
314
318
|
}
|
|
315
|
-
getSnapshotTimestamp() {
|
|
316
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
-
const { timestamps, responseTimestamp } = yield this.restClient.request({
|
|
318
|
-
url: (0, rest_client_1.clientApi)('snapshots/timestamps'),
|
|
319
|
-
method: 'POST',
|
|
320
|
-
json: {
|
|
321
|
-
doorbot_ids: [this.id],
|
|
322
|
-
},
|
|
323
|
-
}), deviceTimestamp = timestamps[0], timestamp = deviceTimestamp ? deviceTimestamp.timestamp : 0, timestampAge = Math.abs(responseTimestamp - timestamp);
|
|
324
|
-
this.lastSnapshotTimestampLocal = timestamp ? Date.now() - timestampAge : 0;
|
|
325
|
-
return {
|
|
326
|
-
timestamp,
|
|
327
|
-
inLifeTime: this.isTimestampInLifeTime(timestampAge),
|
|
328
|
-
};
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
319
|
get snapshotLifeTime() {
|
|
332
320
|
return this.avoidSnapshotBatteryDrain && this.operatingOnBattery
|
|
333
321
|
? 600 * 1000 // battery cams only refresh timestamp every 10 minutes
|
|
334
|
-
: 10 * 1000; // snapshot updates will be forced. Limit to
|
|
322
|
+
: 10 * 1000; // snapshot updates will be forced. Limit to 10s lifetime
|
|
335
323
|
}
|
|
336
324
|
get currentTimestampAge() {
|
|
337
325
|
return Date.now() - this.lastSnapshotTimestampLocal;
|
|
338
326
|
}
|
|
339
|
-
get currentTimestampExpiresIn() {
|
|
340
|
-
// Gets 0 if stale snapshot is used because snapshot timestamp refused to update (recording in progress on battery cam)
|
|
341
|
-
return Math.max(this.lastSnapshotTimestampLocal - Date.now() + this.snapshotLifeTime, 0);
|
|
342
|
-
}
|
|
343
327
|
get hasSnapshotWithinLifetime() {
|
|
344
328
|
return this.isTimestampInLifeTime(this.currentTimestampAge);
|
|
345
329
|
}
|
|
@@ -347,79 +331,73 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
347
331
|
if (this.snapshotsAreBlocked) {
|
|
348
332
|
throw new Error(`Motion detection is disabled for ${this.name}, which prevents snapshots from this camera. This can be caused by Modes settings or by turning off the Record Motion setting.`);
|
|
349
333
|
}
|
|
334
|
+
if (this.isOffline) {
|
|
335
|
+
throw new Error(`Cannot fetch snapshot for ${this.name} because it is offline`);
|
|
336
|
+
}
|
|
350
337
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
(0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
this.checkIfSnapshotsAreBlocked();
|
|
368
|
-
if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
|
|
369
|
-
// tell the camera to update snapshot immediately.
|
|
370
|
-
// avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
|
|
371
|
-
yield this.requestSnapshotUpdate();
|
|
372
|
-
}
|
|
373
|
-
for (let i = 0; i < maxSnapshotRefreshAttempts; i++) {
|
|
374
|
-
this.checkIfSnapshotsAreBlocked();
|
|
375
|
-
const { timestamp, inLifeTime } = yield this.getSnapshotTimestamp();
|
|
376
|
-
if (!timestamp && this.isOffline) {
|
|
377
|
-
throw new Error(`No snapshot available and device ${this.name} is offline`);
|
|
378
|
-
}
|
|
379
|
-
if (inLifeTime) {
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
yield (0, util_1.delay)(snapshotRefreshDelay);
|
|
383
|
-
}
|
|
384
|
-
const extraMessageForBatteryCam = this.operatingOnBattery
|
|
385
|
-
? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
|
|
386
|
-
: '';
|
|
387
|
-
throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshAttempts} attempts${extraMessageForBatteryCam}`);
|
|
388
|
-
});
|
|
338
|
+
shouldUseExistingSnapshotPromise() {
|
|
339
|
+
if (this.fetchingSnapshot) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (this.hasSnapshotWithinLifetime) {
|
|
343
|
+
(0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
|
|
347
|
+
// tell the camera to update snapshot immediately.
|
|
348
|
+
// avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
389
351
|
}
|
|
390
352
|
getSnapshot() {
|
|
391
353
|
return __awaiter(this, void 0, void 0, function* () {
|
|
392
|
-
this.
|
|
393
|
-
this.
|
|
394
|
-
this.refreshSnapshot().catch((e) => {
|
|
395
|
-
(0, util_1.logError)(e.message);
|
|
396
|
-
throw e;
|
|
397
|
-
});
|
|
398
|
-
try {
|
|
399
|
-
const useLastSnapshot = yield this.refreshSnapshotInProgress;
|
|
400
|
-
this.refreshSnapshotInProgress = undefined;
|
|
401
|
-
if (useLastSnapshot && this.lastSnapshotPromise) {
|
|
402
|
-
return this.lastSnapshotPromise;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
catch (e) {
|
|
406
|
-
this.refreshSnapshotInProgress = undefined;
|
|
407
|
-
throw e;
|
|
354
|
+
if (this.lastSnapshotPromise && this.shouldUseExistingSnapshotPromise()) {
|
|
355
|
+
return this.lastSnapshotPromise;
|
|
408
356
|
}
|
|
409
|
-
this.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
357
|
+
this.checkIfSnapshotsAreBlocked();
|
|
358
|
+
this.lastSnapshotPromise = Promise.race([
|
|
359
|
+
this.getNextSnapshot({
|
|
360
|
+
afterMs: this.lastSnapshotTimestamp,
|
|
361
|
+
force: true,
|
|
362
|
+
}),
|
|
363
|
+
(0, util_1.delay)(maxSnapshotRefreshSeconds * 1000).then(() => {
|
|
364
|
+
const extraMessageForBatteryCam = this.operatingOnBattery
|
|
365
|
+
? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
|
|
366
|
+
: '';
|
|
367
|
+
throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshSeconds} seconds${extraMessageForBatteryCam}`);
|
|
368
|
+
}),
|
|
369
|
+
]);
|
|
413
370
|
try {
|
|
414
371
|
yield this.lastSnapshotPromise;
|
|
415
372
|
}
|
|
416
|
-
catch (
|
|
373
|
+
catch (e) {
|
|
417
374
|
// snapshot request failed, don't use it again
|
|
418
375
|
this.lastSnapshotPromise = undefined;
|
|
376
|
+
throw e;
|
|
419
377
|
}
|
|
378
|
+
this.fetchingSnapshot = false;
|
|
420
379
|
return this.lastSnapshotPromise;
|
|
421
380
|
});
|
|
422
381
|
}
|
|
382
|
+
getNextSnapshot({ afterMs, maxWaitMs, force, }) {
|
|
383
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
384
|
+
const response = yield this.restClient.request({
|
|
385
|
+
url: `https://app-snaps.ring.com/snapshots/next/${this.id}?extras=force`,
|
|
386
|
+
responseType: 'buffer',
|
|
387
|
+
searchParams: {
|
|
388
|
+
'after-ms': afterMs,
|
|
389
|
+
'max-wait-ms': maxWaitMs,
|
|
390
|
+
extras: force ? 'force' : undefined,
|
|
391
|
+
},
|
|
392
|
+
headers: {
|
|
393
|
+
accept: 'image/jpeg',
|
|
394
|
+
},
|
|
395
|
+
}), { responseTimestamp, timeMillis } = response, timestampAge = Math.abs(responseTimestamp - timeMillis);
|
|
396
|
+
this.lastSnapshotTimestamp = timeMillis;
|
|
397
|
+
this.lastSnapshotTimestampLocal = Date.now() - timestampAge;
|
|
398
|
+
return response;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
423
401
|
getSipOptions() {
|
|
424
402
|
return __awaiter(this, void 0, void 0, function* () {
|
|
425
403
|
const activeDings = this.onActiveDings.getValue(), existingDing = activeDings
|
package/lib/api/ring-types.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export declare enum RingCameraKind {
|
|
|
68
68
|
doorbell_portal = "doorbell_portal",
|
|
69
69
|
doorbell_scallop = "doorbell_scallop",
|
|
70
70
|
doorbell_scallop_lite = "doorbell_scallop_lite",
|
|
71
|
+
doorbell_graham_cracker = "doorbell_graham_cracker",
|
|
71
72
|
lpd_v1 = "lpd_v1",
|
|
72
73
|
lpd_v2 = "lpd_v2",
|
|
73
74
|
jbox_v1 = "jbox_v1",
|
|
@@ -687,10 +688,6 @@ export interface ActiveDing {
|
|
|
687
688
|
sip_token: string;
|
|
688
689
|
sip_ding_id: string;
|
|
689
690
|
}
|
|
690
|
-
export interface SnapshotTimestamp {
|
|
691
|
-
timestamp: number;
|
|
692
|
-
doorbot_id: number;
|
|
693
|
-
}
|
|
694
691
|
export interface AuthTokenResponse {
|
|
695
692
|
access_token: string;
|
|
696
693
|
expires_in: number;
|
package/lib/api/ring-types.js
CHANGED
|
@@ -76,6 +76,7 @@ var RingCameraKind;
|
|
|
76
76
|
RingCameraKind["doorbell_portal"] = "doorbell_portal";
|
|
77
77
|
RingCameraKind["doorbell_scallop"] = "doorbell_scallop";
|
|
78
78
|
RingCameraKind["doorbell_scallop_lite"] = "doorbell_scallop_lite";
|
|
79
|
+
RingCameraKind["doorbell_graham_cracker"] = "doorbell_graham_cracker";
|
|
79
80
|
RingCameraKind["lpd_v1"] = "lpd_v1";
|
|
80
81
|
RingCameraKind["lpd_v2"] = "lpd_v2";
|
|
81
82
|
RingCameraKind["jbox_v1"] = "jbox_v1";
|
|
@@ -94,7 +95,7 @@ var RingCameraKind;
|
|
|
94
95
|
RingCameraKind["stickup_cam_mini"] = "stickup_cam_mini";
|
|
95
96
|
})(RingCameraKind = exports.RingCameraKind || (exports.RingCameraKind = {}));
|
|
96
97
|
// RegExp taken from ring.com app
|
|
97
|
-
const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight))/);
|
|
98
|
+
const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight|doorbell_graham_cracker))/);
|
|
98
99
|
function isBatteryCameraKind(kind) {
|
|
99
100
|
return !kind.match(isWiredCameraRegExp);
|
|
100
101
|
}
|
|
@@ -108,6 +109,7 @@ exports.RingCameraModel = {
|
|
|
108
109
|
doorbell_portal: 'Door View Cam',
|
|
109
110
|
doorbell_scallop: 'Doorbell 3 Plus',
|
|
110
111
|
doorbell_scallop_lite: 'Doorbell 3',
|
|
112
|
+
doorbell_graham_cracker: 'Doorbell Wired',
|
|
111
113
|
lpd_v1: 'Doorbell Pro',
|
|
112
114
|
lpd_v2: 'Doorbell Pro',
|
|
113
115
|
jbox_v1: 'Doorbell Elite',
|
package/lib/api/sip-session.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare class SipSession extends Subscribed {
|
|
|
27
27
|
constructor(sipOptions: SipOptions, rtpOptions: RtpOptions, audioSplitter: RtpSplitter, audioRtcpSplitter: RtpSplitter, videoSplitter: RtpSplitter, videoRtcpSplitter: RtpSplitter, tlsPort: number, camera: RingCamera);
|
|
28
28
|
createSipCall(sipOptions: SipOptions): SipCall;
|
|
29
29
|
start(ffmpegOptions?: FfmpegOptions): Promise<RtpDescription>;
|
|
30
|
+
prepareTranscoder(transcodeVideoStream: boolean, ffmpegInputOptions: SpawnInput[] | undefined, remoteRtpOptions: RtpOptions, audioPort: number, videoPort: number, sdpInput: string): {
|
|
31
|
+
ffmpegInputArguments: SpawnInput[];
|
|
32
|
+
inputSdpLines: string[];
|
|
33
|
+
};
|
|
30
34
|
private startTranscoder;
|
|
31
35
|
reservePort(bufferPorts?: number): Promise<number>;
|
|
32
36
|
requestKeyFrame(): Promise<void>;
|
package/lib/api/sip-session.js
CHANGED
|
@@ -114,31 +114,17 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
const
|
|
117
|
+
prepareTranscoder(transcodeVideoStream, ffmpegInputOptions, remoteRtpOptions, audioPort, videoPort, sdpInput) {
|
|
118
|
+
const ffmpegInputArguments = [
|
|
119
119
|
'-hide_banner',
|
|
120
120
|
'-protocol_whitelist',
|
|
121
121
|
'pipe,udp,rtp,file,crypto',
|
|
122
122
|
'-f',
|
|
123
123
|
'sdp',
|
|
124
|
-
...(
|
|
124
|
+
...(ffmpegInputOptions || []),
|
|
125
125
|
'-i',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...(transcodeVideoStream
|
|
129
|
-
? ffmpegOptions.video || ['-vcodec', 'copy']
|
|
130
|
-
: []),
|
|
131
|
-
...(ffmpegOptions.output || []),
|
|
132
|
-
], ff = new camera_utils_1.FfmpegProcess({
|
|
133
|
-
ffmpegArgs,
|
|
134
|
-
ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
|
|
135
|
-
exitCallback: () => this.callEnded(true),
|
|
136
|
-
logLabel: `From Ring (${this.camera.name})`,
|
|
137
|
-
logger: {
|
|
138
|
-
error: util_1.logError,
|
|
139
|
-
info: util_1.logDebug,
|
|
140
|
-
},
|
|
141
|
-
}), inputSdpLines = [
|
|
126
|
+
sdpInput,
|
|
127
|
+
], inputSdpLines = [
|
|
142
128
|
'v=0',
|
|
143
129
|
'o=105202070 3747 461 IN IP4 127.0.0.1',
|
|
144
130
|
's=Talk',
|
|
@@ -167,8 +153,6 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
167
153
|
};
|
|
168
154
|
});
|
|
169
155
|
}
|
|
170
|
-
this.onCallEnded.subscribe(() => ff.stop());
|
|
171
|
-
ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n'));
|
|
172
156
|
this.audioSplitter.addMessageHandler(({ isRtpMessage, message }) => {
|
|
173
157
|
if ((0, rtp_utils_1.isStunMessage)(message)) {
|
|
174
158
|
return null;
|
|
@@ -177,6 +161,26 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
177
161
|
port: isRtpMessage ? audioPort : audioPort + 1,
|
|
178
162
|
};
|
|
179
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'));
|
|
180
184
|
}
|
|
181
185
|
reservePort(bufferPorts = 0) {
|
|
182
186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -99,6 +99,7 @@ class CameraSource {
|
|
|
99
99
|
(0, util_1.logDebug)(`Snapshot cached for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
|
|
100
100
|
}
|
|
101
101
|
catch (e) {
|
|
102
|
+
this.cachedSnapshot = undefined;
|
|
102
103
|
(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'}`);
|
|
103
104
|
}
|
|
104
105
|
});
|
package/package.json
CHANGED