ring-client-api 10.0.0-beta.4 → 10.0.0-beta.7
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 +43 -0
- package/lib/api/api.js +3 -5
- package/lib/api/index.js +5 -1
- package/lib/api/rest-client.d.ts +5 -0
- package/lib/api/rest-client.js +7 -6
- package/lib/api/ring-camera.d.ts +16 -5
- package/lib/api/ring-camera.js +43 -9
- package/lib/api/ring-types.d.ts +1 -0
- package/lib/api/ring-types.js +2 -0
- package/lib/api/{peer-connection.d.ts → streaming/peer-connection.d.ts} +21 -4
- package/lib/api/{peer-connection.js → streaming/peer-connection.js} +4 -4
- package/lib/api/streaming/ring-edge-connection.d.ts +65 -0
- package/lib/api/streaming/ring-edge-connection.js +155 -0
- package/lib/api/streaming/simple-webrtc-session.d.ts +11 -0
- package/lib/api/streaming/simple-webrtc-session.js +63 -0
- package/lib/api/streaming/streaming-connection-base.d.ts +30 -0
- package/lib/api/streaming/streaming-connection-base.js +111 -0
- package/lib/api/{live-call.d.ts → streaming/streaming-session.d.ts} +15 -18
- package/lib/api/{live-call.js → streaming/streaming-session.js} +32 -94
- package/lib/api/streaming/webrtc-connection.d.ts +23 -0
- package/lib/api/streaming/webrtc-connection.js +60 -0
- package/lib/api/util.js +7 -0
- package/lib/test/api/ring-camera.spec.d.ts +1 -0
- package/lib/test/api/ring-camera.spec.js +25 -0
- package/package.json +40 -37
- package/lib/api/live-call-ring-edge.d.ts +0 -41
- package/lib/api/live-call-ring-edge.js +0 -294
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SimpleWebRtcSession = void 0;
|
|
13
|
+
const util_1 = require("../util");
|
|
14
|
+
function liveViewUrl(path) {
|
|
15
|
+
return `https://api.ring.com/integrations/v1/liveview/${path}`;
|
|
16
|
+
}
|
|
17
|
+
class SimpleWebRtcSession {
|
|
18
|
+
constructor(camera, restClient) {
|
|
19
|
+
this.camera = camera;
|
|
20
|
+
this.restClient = restClient;
|
|
21
|
+
this.sessionId = (0, util_1.generateUuid)();
|
|
22
|
+
}
|
|
23
|
+
start(sdp) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const response = yield this.restClient.request({
|
|
26
|
+
method: 'POST',
|
|
27
|
+
url: liveViewUrl('start'),
|
|
28
|
+
json: {
|
|
29
|
+
session_id: this.sessionId,
|
|
30
|
+
device_id: this.camera.id,
|
|
31
|
+
sdp,
|
|
32
|
+
protocol: 'webrtc',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
return response.sdp;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
end() {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const resp = yield this.restClient.request({
|
|
41
|
+
method: 'POST',
|
|
42
|
+
url: liveViewUrl('end'),
|
|
43
|
+
json: {
|
|
44
|
+
session_id: this.sessionId,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return resp;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
activateCameraSpeaker() {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
yield this.restClient.request({
|
|
53
|
+
method: 'PATCH',
|
|
54
|
+
url: liveViewUrl('options'),
|
|
55
|
+
json: {
|
|
56
|
+
session_id: this.sessionId,
|
|
57
|
+
actions: ['turn_off_stealth_mode'],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.SimpleWebRtcSession = SimpleWebRtcSession;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BasicPeerConnection } from './peer-connection';
|
|
2
|
+
import { Subscribed } from '../subscribed';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
4
|
+
import { RtpPacket } from '@koush/werift';
|
|
5
|
+
import { ReplaySubject, Subject } from 'rxjs';
|
|
6
|
+
export interface StreamingConnectionOptions {
|
|
7
|
+
createPeerConnection?: () => BasicPeerConnection;
|
|
8
|
+
}
|
|
9
|
+
export declare abstract class StreamingConnectionBase extends Subscribed {
|
|
10
|
+
protected ws: WebSocket;
|
|
11
|
+
protected options: StreamingConnectionOptions;
|
|
12
|
+
readonly onCallAnswered: ReplaySubject<string>;
|
|
13
|
+
readonly onCallEnded: ReplaySubject<void>;
|
|
14
|
+
readonly onWsOpen: import("rxjs").Observable<unknown>;
|
|
15
|
+
protected readonly pc: BasicPeerConnection;
|
|
16
|
+
readonly onAudioRtp: Subject<RtpPacket>;
|
|
17
|
+
readonly onVideoRtp: Subject<RtpPacket>;
|
|
18
|
+
constructor(ws: WebSocket, options?: StreamingConnectionOptions);
|
|
19
|
+
protected abstract handleMessage(message: {
|
|
20
|
+
method: string;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
protected abstract sendSessionMessage(method: string, body?: Record<any, any>): void;
|
|
23
|
+
private activated;
|
|
24
|
+
activate(): Promise<void>;
|
|
25
|
+
activateCameraSpeaker(): Promise<void>;
|
|
26
|
+
protected sendMessage(message: Record<any, any>): void;
|
|
27
|
+
sendAudioPacket(rtp: RtpPacket): void;
|
|
28
|
+
protected callEnded(): void;
|
|
29
|
+
stop(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.StreamingConnectionBase = void 0;
|
|
13
|
+
const peer_connection_1 = require("./peer-connection");
|
|
14
|
+
const subscribed_1 = require("../subscribed");
|
|
15
|
+
const rxjs_1 = require("rxjs");
|
|
16
|
+
const operators_1 = require("rxjs/operators");
|
|
17
|
+
const util_1 = require("../util");
|
|
18
|
+
class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
19
|
+
constructor(ws, options = {}) {
|
|
20
|
+
super();
|
|
21
|
+
this.ws = ws;
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.onCallAnswered = new rxjs_1.ReplaySubject(1);
|
|
24
|
+
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
25
|
+
this.activated = false;
|
|
26
|
+
if (options.createPeerConnection) {
|
|
27
|
+
// we were passed a custom peer connection factory
|
|
28
|
+
this.pc = options.createPeerConnection();
|
|
29
|
+
// passing rtp packets is not supported for custom peer connections
|
|
30
|
+
this.onAudioRtp = new rxjs_1.Subject();
|
|
31
|
+
this.onVideoRtp = new rxjs_1.Subject();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// no custom peer connection factory, use the werift and pass along rtp packets
|
|
35
|
+
const pc = new peer_connection_1.WeriftPeerConnection();
|
|
36
|
+
this.pc = pc;
|
|
37
|
+
this.onAudioRtp = pc.onAudioRtp;
|
|
38
|
+
this.onVideoRtp = pc.onVideoRtp;
|
|
39
|
+
}
|
|
40
|
+
this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
|
|
41
|
+
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');
|
|
42
|
+
this.addSubscriptions(onMessage
|
|
43
|
+
.pipe((0, operators_1.concatMap)((message) => {
|
|
44
|
+
return this.handleMessage(JSON.parse(message.data));
|
|
45
|
+
}))
|
|
46
|
+
.subscribe(), onError.subscribe((e) => {
|
|
47
|
+
(0, util_1.logError)(e);
|
|
48
|
+
this.callEnded();
|
|
49
|
+
}), onClose.subscribe(() => {
|
|
50
|
+
this.callEnded();
|
|
51
|
+
}), this.pc.onConnectionState.subscribe((state) => {
|
|
52
|
+
if (state === 'failed') {
|
|
53
|
+
(0, util_1.logError)('Stream connection failed');
|
|
54
|
+
this.callEnded();
|
|
55
|
+
}
|
|
56
|
+
if (state === 'closed') {
|
|
57
|
+
(0, util_1.logDebug)('Stream connection closed');
|
|
58
|
+
this.callEnded();
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
activate() {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
if (this.activated) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.activated = true;
|
|
68
|
+
yield (0, rxjs_1.firstValueFrom)(this.pc.onConnectionState.pipe((0, operators_1.filter)((state) => state === 'connected')));
|
|
69
|
+
(0, util_1.logInfo)('Activating Session');
|
|
70
|
+
this.sendSessionMessage('activate_session');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
activateCameraSpeaker() {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
|
|
76
|
+
this.sendSessionMessage('camera_options', {
|
|
77
|
+
stealth_mode: false,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
sendMessage(message) {
|
|
82
|
+
this.ws.send(JSON.stringify(message));
|
|
83
|
+
}
|
|
84
|
+
sendAudioPacket(rtp) {
|
|
85
|
+
if (this.pc instanceof peer_connection_1.WeriftPeerConnection) {
|
|
86
|
+
this.pc.returnAudioTrack.writeRtp(rtp);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new Error('Cannot send audio packets to a custom peer connection implementation');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
callEnded() {
|
|
93
|
+
try {
|
|
94
|
+
this.sendMessage({
|
|
95
|
+
reason: { code: 0, text: '' },
|
|
96
|
+
method: 'close',
|
|
97
|
+
});
|
|
98
|
+
this.ws.close();
|
|
99
|
+
}
|
|
100
|
+
catch (_) {
|
|
101
|
+
// ignore any errors since we are stopping the call
|
|
102
|
+
}
|
|
103
|
+
this.unsubscribe();
|
|
104
|
+
this.onCallEnded.next();
|
|
105
|
+
this.pc.close();
|
|
106
|
+
}
|
|
107
|
+
stop() {
|
|
108
|
+
this.callEnded();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.StreamingConnectionBase = StreamingConnectionBase;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ReplaySubject } from 'rxjs';
|
|
2
|
-
import { RingCamera } from './ring-camera';
|
|
3
|
-
import { Subscribed } from './subscribed';
|
|
4
1
|
import { RtpPacket } from '@koush/werift';
|
|
2
|
+
import { ReplaySubject, Subject } from 'rxjs';
|
|
3
|
+
import { RingEdgeConnection } from './ring-edge-connection';
|
|
4
|
+
import { WebrtcConnection } from './webrtc-connection';
|
|
5
|
+
import { RingCamera } from '../ring-camera';
|
|
6
|
+
import { Subscribed } from '../subscribed';
|
|
5
7
|
declare type SpawnInput = string | number;
|
|
6
8
|
export interface FfmpegOptions {
|
|
7
9
|
input?: SpawnInput[];
|
|
@@ -9,27 +11,22 @@ export interface FfmpegOptions {
|
|
|
9
11
|
audio?: SpawnInput[];
|
|
10
12
|
output: SpawnInput[];
|
|
11
13
|
}
|
|
12
|
-
export declare class
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
private readonly ws;
|
|
16
|
-
private readonly onWsOpen;
|
|
17
|
-
private readonly onMessage;
|
|
18
|
-
private readonly pc;
|
|
19
|
-
readonly onCallAnswered: ReplaySubject<string>;
|
|
14
|
+
export declare class StreamingSession extends Subscribed {
|
|
15
|
+
private readonly camera;
|
|
16
|
+
private connection;
|
|
20
17
|
readonly onCallEnded: ReplaySubject<void>;
|
|
18
|
+
readonly onVideoRtp: Subject<RtpPacket>;
|
|
19
|
+
readonly onAudioRtp: Subject<RtpPacket>;
|
|
21
20
|
private readonly audioSplitter;
|
|
22
21
|
private readonly videoSplitter;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
constructor(sessionId: string, camera: RingCamera);
|
|
26
|
-
private handleMessage;
|
|
27
|
-
reservePort(bufferPorts?: number): Promise<number>;
|
|
28
|
-
startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
|
|
22
|
+
constructor(camera: RingCamera, connection: RingEdgeConnection | WebrtcConnection);
|
|
23
|
+
private bindToConnection;
|
|
29
24
|
activated: boolean;
|
|
30
25
|
activate(): Promise<void>;
|
|
26
|
+
cameraSpeakerActivated: boolean;
|
|
31
27
|
activateCameraSpeaker(): Promise<void>;
|
|
32
|
-
|
|
28
|
+
reservePort(bufferPorts?: number): Promise<number>;
|
|
29
|
+
startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
|
|
33
30
|
private callEnded;
|
|
34
31
|
stop(): void;
|
|
35
32
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
@@ -9,15 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
13
|
-
const
|
|
12
|
+
exports.StreamingSession = void 0;
|
|
13
|
+
const camera_utils_1 = require("@homebridge/camera-utils");
|
|
14
14
|
const rxjs_1 = require("rxjs");
|
|
15
|
-
const
|
|
16
|
-
const util_1 = require("
|
|
15
|
+
const ffmpeg_1 = require("../ffmpeg");
|
|
16
|
+
const util_1 = require("../util");
|
|
17
17
|
const operators_1 = require("rxjs/operators");
|
|
18
|
-
const subscribed_1 = require("
|
|
19
|
-
const camera_utils_1 = require("@homebridge/camera-utils");
|
|
20
|
-
const ffmpeg_1 = require("./ffmpeg");
|
|
18
|
+
const subscribed_1 = require("../subscribed");
|
|
21
19
|
function getCleanSdp(sdp, includeVideo) {
|
|
22
20
|
return sdp
|
|
23
21
|
.split('\nm=')
|
|
@@ -26,62 +24,39 @@ function getCleanSdp(sdp, includeVideo) {
|
|
|
26
24
|
.filter((section) => includeVideo || !section.startsWith('m=video'))
|
|
27
25
|
.join('\n');
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return JSON.parse(text);
|
|
32
|
-
}
|
|
33
|
-
class LiveCall extends subscribed_1.Subscribed {
|
|
34
|
-
constructor(sessionId, camera) {
|
|
27
|
+
class StreamingSession extends subscribed_1.Subscribed {
|
|
28
|
+
constructor(camera, connection) {
|
|
35
29
|
super();
|
|
36
|
-
this.sessionId = sessionId;
|
|
37
30
|
this.camera = camera;
|
|
38
|
-
this.
|
|
31
|
+
this.connection = connection;
|
|
39
32
|
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
33
|
+
this.onVideoRtp = new rxjs_1.Subject();
|
|
34
|
+
this.onAudioRtp = new rxjs_1.Subject();
|
|
40
35
|
this.audioSplitter = new camera_utils_1.RtpSplitter();
|
|
41
36
|
this.videoSplitter = new camera_utils_1.RtpSplitter();
|
|
42
37
|
this.activated = false;
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
this.cameraSpeakerActivated = false;
|
|
39
|
+
this.bindToConnection(connection);
|
|
40
|
+
}
|
|
41
|
+
bindToConnection(connection) {
|
|
42
|
+
this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp));
|
|
43
|
+
}
|
|
44
|
+
activate() {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
if (this.activated) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.activated = true;
|
|
50
|
+
yield this.connection.activate();
|
|
51
51
|
});
|
|
52
|
-
this.onAudioRtp = this.pc.onAudioRtp;
|
|
53
|
-
this.onVideoRtp = this.pc.onVideoRtp;
|
|
54
|
-
this.onMessage = (0, rxjs_1.fromEvent)(this.ws, 'message');
|
|
55
|
-
this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
|
|
56
|
-
const onError = (0, rxjs_1.fromEvent)(this.ws, 'error'), onClose = (0, rxjs_1.fromEvent)(this.ws, 'close');
|
|
57
|
-
this.addSubscriptions(this.onMessage
|
|
58
|
-
.pipe((0, operators_1.concatMap)((message) => {
|
|
59
|
-
return this.handleMessage(JSON.parse(message.data));
|
|
60
|
-
}))
|
|
61
|
-
.subscribe(), this.onWsOpen.subscribe(() => {
|
|
62
|
-
(0, util_1.logDebug)(`WebSocket connected for ${this.camera.name}`);
|
|
63
|
-
}), onError.subscribe((e) => {
|
|
64
|
-
(0, util_1.logError)(e);
|
|
65
|
-
this.callEnded();
|
|
66
|
-
}), onClose.subscribe(() => {
|
|
67
|
-
this.callEnded();
|
|
68
|
-
}));
|
|
69
52
|
}
|
|
70
|
-
|
|
53
|
+
activateCameraSpeaker() {
|
|
71
54
|
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const answer = yield this.pc.createAnswer(message);
|
|
75
|
-
this.sendMessage(Object.assign({ method: 'sdp' }, answer));
|
|
76
|
-
this.onCallAnswered.next(message.sdp);
|
|
77
|
-
return;
|
|
78
|
-
case 'ice':
|
|
79
|
-
yield this.pc.addIceCandidate({
|
|
80
|
-
candidate: message.ice,
|
|
81
|
-
sdpMLineIndex: message.mlineindex,
|
|
82
|
-
});
|
|
83
|
-
return;
|
|
55
|
+
if (this.cameraSpeakerActivated) {
|
|
56
|
+
return;
|
|
84
57
|
}
|
|
58
|
+
this.cameraSpeakerActivated = true;
|
|
59
|
+
yield this.connection.activateCameraSpeaker();
|
|
85
60
|
});
|
|
86
61
|
}
|
|
87
62
|
reservePort(bufferPorts = 0) {
|
|
@@ -103,7 +78,7 @@ class LiveCall extends subscribed_1.Subscribed {
|
|
|
103
78
|
...(ffmpegOptions.input || []),
|
|
104
79
|
'-i',
|
|
105
80
|
'pipe:',
|
|
106
|
-
], ringSdp = yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered), inputSdp = getCleanSdp(ringSdp, transcodeVideoStream)
|
|
81
|
+
], ringSdp = yield (0, rxjs_1.firstValueFrom)(this.connection.onCallAnswered), inputSdp = getCleanSdp(ringSdp, transcodeVideoStream)
|
|
107
82
|
.replace(/m=audio \d+/, `m=audio ${audioPort}`)
|
|
108
83
|
.replace(/m=video \d+/, `m=video ${videoPort}`), ff = new camera_utils_1.FfmpegProcess({
|
|
109
84
|
ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
|
|
@@ -139,47 +114,10 @@ class LiveCall extends subscribed_1.Subscribed {
|
|
|
139
114
|
yield this.activate();
|
|
140
115
|
});
|
|
141
116
|
}
|
|
142
|
-
activate() {
|
|
143
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
-
if (this.activated) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
this.activated = true;
|
|
148
|
-
yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
|
|
149
|
-
this.sendMessage({ method: 'activate_session' });
|
|
150
|
-
this.sendMessage({
|
|
151
|
-
video_enabled: true,
|
|
152
|
-
audio_enabled: true,
|
|
153
|
-
method: 'stream_options',
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
activateCameraSpeaker() {
|
|
158
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
-
yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
|
|
160
|
-
this.sendMessage({
|
|
161
|
-
stealth_mode: false,
|
|
162
|
-
method: 'camera_options',
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
sendMessage(message) {
|
|
167
|
-
this.ws.send(JSON.stringify(message));
|
|
168
|
-
}
|
|
169
117
|
callEnded() {
|
|
170
|
-
try {
|
|
171
|
-
this.sendMessage({
|
|
172
|
-
reason: { code: 0, text: '' },
|
|
173
|
-
method: 'close',
|
|
174
|
-
});
|
|
175
|
-
this.ws.close();
|
|
176
|
-
}
|
|
177
|
-
catch (_) {
|
|
178
|
-
// ignore any errors since we are stopping the call
|
|
179
|
-
}
|
|
180
118
|
this.unsubscribe();
|
|
181
119
|
this.onCallEnded.next();
|
|
182
|
-
this.
|
|
120
|
+
this.connection.stop();
|
|
183
121
|
this.audioSplitter.close();
|
|
184
122
|
this.videoSplitter.close();
|
|
185
123
|
}
|
|
@@ -187,7 +125,7 @@ class LiveCall extends subscribed_1.Subscribed {
|
|
|
187
125
|
this.callEnded();
|
|
188
126
|
}
|
|
189
127
|
sendAudioPacket(rtp) {
|
|
190
|
-
this.
|
|
128
|
+
this.connection.sendAudioPacket(rtp);
|
|
191
129
|
}
|
|
192
130
|
}
|
|
193
|
-
exports.
|
|
131
|
+
exports.StreamingSession = StreamingSession;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RingCamera } from '../ring-camera';
|
|
2
|
+
import { StreamingConnectionBase, StreamingConnectionOptions } from './streaming-connection-base';
|
|
3
|
+
interface InitializationMessage {
|
|
4
|
+
method: 'initialization';
|
|
5
|
+
text: 'Done';
|
|
6
|
+
}
|
|
7
|
+
interface OfferMessage {
|
|
8
|
+
method: 'sdp';
|
|
9
|
+
sdp: string;
|
|
10
|
+
type: 'offer';
|
|
11
|
+
}
|
|
12
|
+
interface IceCandidateMessage {
|
|
13
|
+
method: 'ice';
|
|
14
|
+
ice: string;
|
|
15
|
+
mlineindex: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class WebrtcConnection extends StreamingConnectionBase {
|
|
18
|
+
private sessionId;
|
|
19
|
+
constructor(sessionId: string, camera: RingCamera, options: StreamingConnectionOptions);
|
|
20
|
+
protected handleMessage(message: InitializationMessage | OfferMessage | IceCandidateMessage): Promise<void>;
|
|
21
|
+
protected sendSessionMessage(method: string, body?: Record<any, any>): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WebrtcConnection = void 0;
|
|
13
|
+
const ws_1 = require("ws");
|
|
14
|
+
const streaming_connection_base_1 = require("./streaming-connection-base");
|
|
15
|
+
const util_1 = require("../util");
|
|
16
|
+
function parseLiveCallSession(sessionId) {
|
|
17
|
+
const encodedSession = sessionId.split('.')[1], buff = Buffer.from(encodedSession, 'base64'), text = buff.toString('ascii');
|
|
18
|
+
return JSON.parse(text);
|
|
19
|
+
}
|
|
20
|
+
class WebrtcConnection extends streaming_connection_base_1.StreamingConnectionBase {
|
|
21
|
+
constructor(sessionId, camera, options) {
|
|
22
|
+
const liveCallSession = parseLiveCallSession(sessionId);
|
|
23
|
+
super(new ws_1.WebSocket(`wss://${liveCallSession.rms_fqdn}:${liveCallSession.webrtc_port}/`, {
|
|
24
|
+
headers: {
|
|
25
|
+
API_VERSION: '3.1',
|
|
26
|
+
API_TOKEN: sessionId,
|
|
27
|
+
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
|
+
},
|
|
29
|
+
}), options);
|
|
30
|
+
this.sessionId = sessionId;
|
|
31
|
+
this.addSubscriptions(this.onWsOpen.subscribe(() => {
|
|
32
|
+
(0, util_1.logDebug)(`WebSocket connected for ${camera.name}`);
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
handleMessage(message) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
switch (message.method) {
|
|
38
|
+
case 'sdp':
|
|
39
|
+
const answer = yield this.pc.createAnswer(message);
|
|
40
|
+
this.sendSessionMessage('sdp', answer);
|
|
41
|
+
this.onCallAnswered.next(message.sdp);
|
|
42
|
+
this.sendSessionMessage('stream_options', {
|
|
43
|
+
audio_enabled: true,
|
|
44
|
+
video_enabled: true,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
case 'ice':
|
|
48
|
+
yield this.pc.addIceCandidate({
|
|
49
|
+
candidate: message.ice,
|
|
50
|
+
sdpMLineIndex: message.mlineindex,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
sendSessionMessage(method, body = {}) {
|
|
57
|
+
this.sendMessage(Object.assign(Object.assign({}, body), { method }));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.WebrtcConnection = WebrtcConnection;
|
package/lib/api/util.js
CHANGED
|
@@ -18,6 +18,7 @@ const colors_1 = require("colors");
|
|
|
18
18
|
const readline_1 = require("readline");
|
|
19
19
|
const uuid_1 = require("uuid");
|
|
20
20
|
const systeminformation_1 = require("systeminformation");
|
|
21
|
+
const logger_1 = __importDefault(require("@eneris/push-receiver/dist/utils/logger"));
|
|
21
22
|
const debugLogger = (0, debug_1.default)('ring'), uuidNamespace = 'e53ffdc0-e91d-4ce1-bec2-df939d94739d';
|
|
22
23
|
let logger = {
|
|
23
24
|
logInfo(message) {
|
|
@@ -120,3 +121,9 @@ function randomString(length) {
|
|
|
120
121
|
return uuid.replace(/-/g, '').substring(0, length).toLowerCase();
|
|
121
122
|
}
|
|
122
123
|
exports.randomString = randomString;
|
|
124
|
+
// Override push receiver logging to avoid ECONNRESET errors leaking
|
|
125
|
+
function logPushReceiver(...args) {
|
|
126
|
+
logDebug('[Push Receiver]');
|
|
127
|
+
logDebug(args[0]);
|
|
128
|
+
}
|
|
129
|
+
logger_1.default.error = logPushReceiver;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const api_1 = require("../../api");
|
|
4
|
+
describe('Ring Camera', () => {
|
|
5
|
+
describe('battery level', () => {
|
|
6
|
+
it('should handle string battery life', () => {
|
|
7
|
+
expect((0, api_1.getBatteryLevel)({
|
|
8
|
+
battery_life: '49',
|
|
9
|
+
})).toEqual(49);
|
|
10
|
+
});
|
|
11
|
+
it('should handle null battery life', () => {
|
|
12
|
+
expect((0, api_1.getBatteryLevel)({ battery_life: null })).toEqual(null);
|
|
13
|
+
});
|
|
14
|
+
it('should handle right battery only', () => {
|
|
15
|
+
expect((0, api_1.getBatteryLevel)({ battery_life: null, battery_life_2: 24 })).toEqual(24);
|
|
16
|
+
});
|
|
17
|
+
it('should handle left battery only', () => {
|
|
18
|
+
expect((0, api_1.getBatteryLevel)({ battery_life: 76, battery_life_2: null })).toEqual(76);
|
|
19
|
+
});
|
|
20
|
+
it('should handle dual batteries', () => {
|
|
21
|
+
expect((0, api_1.getBatteryLevel)({ battery_life: '92', battery_life_2: 84 })).toEqual(84);
|
|
22
|
+
expect((0, api_1.getBatteryLevel)({ battery_life: '92', battery_life_2: 100 })).toEqual(92);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|