ring-client-api 10.0.0-beta.3 → 10.0.0-beta.6
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 +46 -0
- package/README.md +0 -3
- package/lib/api/api.d.ts +2 -4
- package/lib/api/api.js +64 -23
- package/lib/api/index.js +5 -1
- package/lib/api/ring-camera.d.ts +22 -16
- package/lib/api/ring-camera.js +52 -37
- package/lib/api/ring-types.d.ts +76 -26
- package/lib/api/ring-types.js +18 -7
- package/lib/api/{peer-connection.d.ts → streaming/peer-connection.d.ts} +21 -4
- package/lib/api/{peer-connection.js → streaming/peer-connection.js} +17 -6
- 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/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 +8 -1
- package/lib/test/api/ring-camera.spec.js +3 -1
- package/package.json +31 -30
- package/lib/api/live-call-ring-edge.d.ts +0 -41
- package/lib/api/live-call-ring-edge.js +0 -294
package/lib/api/ring-types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isWebSocketSupportedAsset = exports.disabledLocationModes = exports.DispatchSignalType = exports.DoorbellType = exports.ChimeModel = exports.deviceTypesWithVolume = exports.allAlarmStates = exports.RingCameraModel = exports.
|
|
3
|
+
exports.isWebSocketSupportedAsset = exports.disabledLocationModes = exports.DispatchSignalType = exports.PushNotificationAction = exports.NotificationDetectionType = exports.DoorbellType = exports.ChimeModel = exports.deviceTypesWithVolume = exports.allAlarmStates = exports.RingCameraModel = exports.RingCameraKind = exports.RingDeviceCategory = exports.RingDeviceType = void 0;
|
|
4
4
|
// eslint-disable-next-line no-shadow
|
|
5
5
|
var RingDeviceType;
|
|
6
6
|
(function (RingDeviceType) {
|
|
@@ -95,12 +95,6 @@ var RingCameraKind;
|
|
|
95
95
|
RingCameraKind["cocoa_doorbell"] = "cocoa_doorbell";
|
|
96
96
|
RingCameraKind["stickup_cam_mini"] = "stickup_cam_mini";
|
|
97
97
|
})(RingCameraKind = exports.RingCameraKind || (exports.RingCameraKind = {}));
|
|
98
|
-
// RegExp taken from ring.com app
|
|
99
|
-
const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight|doorbell_graham_cracker))/);
|
|
100
|
-
function isBatteryCameraKind(kind) {
|
|
101
|
-
return !kind.match(isWiredCameraRegExp);
|
|
102
|
-
}
|
|
103
|
-
exports.isBatteryCameraKind = isBatteryCameraKind;
|
|
104
98
|
exports.RingCameraModel = {
|
|
105
99
|
doorbot: 'Doorbell',
|
|
106
100
|
doorbell: 'Doorbell',
|
|
@@ -158,6 +152,23 @@ var DoorbellType;
|
|
|
158
152
|
DoorbellType[DoorbellType["None"] = 2] = "None";
|
|
159
153
|
})(DoorbellType = exports.DoorbellType || (exports.DoorbellType = {}));
|
|
160
154
|
// eslint-disable-next-line no-shadow
|
|
155
|
+
var NotificationDetectionType;
|
|
156
|
+
(function (NotificationDetectionType) {
|
|
157
|
+
// Note, this list may not be complete
|
|
158
|
+
NotificationDetectionType["Human"] = "human";
|
|
159
|
+
NotificationDetectionType["Loitering"] = "loitering";
|
|
160
|
+
NotificationDetectionType["Motion"] = "motion";
|
|
161
|
+
NotificationDetectionType["OtherMotion"] = "other_motion";
|
|
162
|
+
NotificationDetectionType["NotAvailable"] = "null";
|
|
163
|
+
NotificationDetectionType["StreamBroken"] = "stream_broken";
|
|
164
|
+
})(NotificationDetectionType = exports.NotificationDetectionType || (exports.NotificationDetectionType = {}));
|
|
165
|
+
// eslint-disable-next-line no-shadow
|
|
166
|
+
var PushNotificationAction;
|
|
167
|
+
(function (PushNotificationAction) {
|
|
168
|
+
PushNotificationAction["Ding"] = "com.ring.push.HANDLE_NEW_DING";
|
|
169
|
+
PushNotificationAction["Motion"] = "com.ring.push.HANDLE_NEW_motion";
|
|
170
|
+
})(PushNotificationAction = exports.PushNotificationAction || (exports.PushNotificationAction = {}));
|
|
171
|
+
// eslint-disable-next-line no-shadow
|
|
161
172
|
var DispatchSignalType;
|
|
162
173
|
(function (DispatchSignalType) {
|
|
163
174
|
DispatchSignalType["Burglar"] = "user-verified-burglar-xa";
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import { MediaStreamTrack, RTCIceCandidate,
|
|
2
|
-
import { ReplaySubject, Subject } from 'rxjs';
|
|
3
|
-
export
|
|
4
|
-
|
|
1
|
+
import { ConnectionState, MediaStreamTrack, RTCIceCandidate, RtcpPacket, RtpPacket } from '@koush/werift';
|
|
2
|
+
import { Observable, ReplaySubject, Subject } from 'rxjs';
|
|
3
|
+
export interface BasicPeerConnection {
|
|
4
|
+
createOffer(): Promise<{
|
|
5
|
+
sdp: string;
|
|
6
|
+
}>;
|
|
7
|
+
createAnswer(offer: {
|
|
8
|
+
type: 'offer';
|
|
9
|
+
sdp: string;
|
|
10
|
+
}): Promise<RTCSessionDescriptionInit>;
|
|
11
|
+
acceptAnswer(answer: {
|
|
12
|
+
type: 'answer';
|
|
13
|
+
sdp: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
16
|
+
onIceCandidate: Observable<RTCIceCandidate>;
|
|
17
|
+
onConnectionState: Observable<ConnectionState>;
|
|
18
|
+
close(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
21
|
+
private pc;
|
|
5
22
|
onAudioRtp: Subject<RtpPacket>;
|
|
6
23
|
onAudioRtcp: Subject<RtcpPacket>;
|
|
7
24
|
onVideoRtp: Subject<RtpPacket>;
|
|
@@ -9,12 +9,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.WeriftPeerConnection = void 0;
|
|
13
13
|
const werift_1 = require("@koush/werift");
|
|
14
14
|
const rxjs_1 = require("rxjs");
|
|
15
|
-
const util_1 = require("
|
|
16
|
-
const debug = false
|
|
17
|
-
|
|
15
|
+
const util_1 = require("../util");
|
|
16
|
+
const debug = false, ringIceServers = [
|
|
17
|
+
'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
|
|
18
|
+
'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
|
|
19
|
+
'stun:stun.kinesisvideo.us-west-2.amazonaws.com:443',
|
|
20
|
+
'stun:stun.l.google.com:19302',
|
|
21
|
+
'stun:stun1.l.google.com:19302',
|
|
22
|
+
'stun:stun2.l.google.com:19302',
|
|
23
|
+
'stun:stun3.l.google.com:19302',
|
|
24
|
+
'stun:stun4.l.google.com:19302',
|
|
25
|
+
];
|
|
26
|
+
class WeriftPeerConnection {
|
|
18
27
|
constructor() {
|
|
19
28
|
this.onAudioRtp = new rxjs_1.Subject();
|
|
20
29
|
this.onAudioRtcp = new rxjs_1.Subject();
|
|
@@ -43,10 +52,12 @@ class PeerConnection {
|
|
|
43
52
|
{ type: 'nack', parameter: 'pli' },
|
|
44
53
|
{ type: 'goog-remb' },
|
|
45
54
|
],
|
|
46
|
-
parameters: 'packetization-mode=1;profile-level-id=
|
|
55
|
+
parameters: 'packetization-mode=1;profile-level-id=640029;level-asymmetry-allowed=1',
|
|
47
56
|
}),
|
|
48
57
|
],
|
|
49
58
|
},
|
|
59
|
+
iceServers: ringIceServers.map((server) => ({ urls: server })),
|
|
60
|
+
iceTransportPolicy: 'all',
|
|
50
61
|
})), audioTransceiver = pc.addTransceiver(this.returnAudioTrack, {
|
|
51
62
|
direction: 'sendrecv',
|
|
52
63
|
}), videoTransceiver = pc.addTransceiver('video', {
|
|
@@ -120,4 +131,4 @@ class PeerConnection {
|
|
|
120
131
|
this.pc.close().catch(util_1.logError);
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
|
-
exports.
|
|
134
|
+
exports.WeriftPeerConnection = WeriftPeerConnection;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { RingCamera } from '../ring-camera';
|
|
2
|
+
import { StreamingConnectionBase, StreamingConnectionOptions } from './streaming-connection-base';
|
|
3
|
+
interface SessionBody {
|
|
4
|
+
doorbot_id: number;
|
|
5
|
+
session_id: string;
|
|
6
|
+
}
|
|
7
|
+
interface AnswerMessage {
|
|
8
|
+
method: 'sdp';
|
|
9
|
+
body: {
|
|
10
|
+
sdp: string;
|
|
11
|
+
type: 'answer';
|
|
12
|
+
} & SessionBody;
|
|
13
|
+
}
|
|
14
|
+
interface IceCandidateMessage {
|
|
15
|
+
method: 'ice';
|
|
16
|
+
body: {
|
|
17
|
+
ice: string;
|
|
18
|
+
mlineindex: number;
|
|
19
|
+
} & SessionBody;
|
|
20
|
+
}
|
|
21
|
+
interface SessionCreatedMessage {
|
|
22
|
+
method: 'session_created';
|
|
23
|
+
body: SessionBody;
|
|
24
|
+
}
|
|
25
|
+
interface SessionStartedMessage {
|
|
26
|
+
method: 'session_started';
|
|
27
|
+
body: SessionBody;
|
|
28
|
+
}
|
|
29
|
+
interface PongMessage {
|
|
30
|
+
method: 'pong';
|
|
31
|
+
body: SessionBody;
|
|
32
|
+
}
|
|
33
|
+
interface NotificationMessage {
|
|
34
|
+
method: 'notification';
|
|
35
|
+
body: {
|
|
36
|
+
is_ok: boolean;
|
|
37
|
+
text: string;
|
|
38
|
+
} & SessionBody;
|
|
39
|
+
}
|
|
40
|
+
declare enum CloseReasonCode {
|
|
41
|
+
NormalClose = 0,
|
|
42
|
+
AuthenticationFailed = 5,
|
|
43
|
+
Timeout = 6
|
|
44
|
+
}
|
|
45
|
+
interface CloseMessage {
|
|
46
|
+
method: 'close';
|
|
47
|
+
body: {
|
|
48
|
+
reason: {
|
|
49
|
+
code: CloseReasonCode;
|
|
50
|
+
text: string;
|
|
51
|
+
};
|
|
52
|
+
} & SessionBody;
|
|
53
|
+
}
|
|
54
|
+
declare type IncomingMessage = AnswerMessage | IceCandidateMessage | SessionCreatedMessage | SessionStartedMessage | PongMessage | CloseMessage | NotificationMessage;
|
|
55
|
+
export declare class RingEdgeConnection extends StreamingConnectionBase {
|
|
56
|
+
private camera;
|
|
57
|
+
private readonly onSessionId;
|
|
58
|
+
private readonly onOfferSent;
|
|
59
|
+
constructor(authToken: string, camera: RingCamera, options: StreamingConnectionOptions);
|
|
60
|
+
private initiateCall;
|
|
61
|
+
private sessionId;
|
|
62
|
+
protected handleMessage(message: IncomingMessage): Promise<void>;
|
|
63
|
+
protected sendSessionMessage(method: string, body?: Record<any, any>): void;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
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.RingEdgeConnection = void 0;
|
|
13
|
+
const ws_1 = require("ws");
|
|
14
|
+
const rxjs_1 = require("rxjs");
|
|
15
|
+
const util_1 = require("../util");
|
|
16
|
+
const operators_1 = require("rxjs/operators");
|
|
17
|
+
const streaming_connection_base_1 = require("./streaming-connection-base");
|
|
18
|
+
// eslint-disable-next-line no-shadow
|
|
19
|
+
var CloseReasonCode;
|
|
20
|
+
(function (CloseReasonCode) {
|
|
21
|
+
CloseReasonCode[CloseReasonCode["NormalClose"] = 0] = "NormalClose";
|
|
22
|
+
// reason: { code: 5, text: '[rsl-apps/webrtc-liveview-server/Session.cpp:429] [Auth] [0xd540]: [rsl-apps/session-manager/Manager.cpp:227] [AppAuth] Unauthorized: invalid or expired token' }
|
|
23
|
+
// reason: { code: 5, text: 'Authentication failed: -1' }
|
|
24
|
+
// reason: { code: 5, text: 'Sessions with the provided ID not found' }
|
|
25
|
+
CloseReasonCode[CloseReasonCode["AuthenticationFailed"] = 5] = "AuthenticationFailed";
|
|
26
|
+
// reason: { code: 6, text: 'Timeout waiting for ping' }
|
|
27
|
+
CloseReasonCode[CloseReasonCode["Timeout"] = 6] = "Timeout";
|
|
28
|
+
})(CloseReasonCode || (CloseReasonCode = {}));
|
|
29
|
+
class RingEdgeConnection extends streaming_connection_base_1.StreamingConnectionBase {
|
|
30
|
+
constructor(authToken, camera, options) {
|
|
31
|
+
super(new ws_1.WebSocket('wss://api.prod.signalling.ring.devices.a2z.com:443/ws', {
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${authToken}`,
|
|
34
|
+
'X-Sig-API-Version': '4.0',
|
|
35
|
+
'X-Sig-Client-ID': 'ring_android-aabb123',
|
|
36
|
+
'X-Sig-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',
|
|
37
|
+
'X-Sig-Auth-Type': 'ring_oauth',
|
|
38
|
+
},
|
|
39
|
+
}), options);
|
|
40
|
+
this.camera = camera;
|
|
41
|
+
this.onSessionId = new rxjs_1.ReplaySubject(1);
|
|
42
|
+
this.onOfferSent = new rxjs_1.ReplaySubject(1);
|
|
43
|
+
this.sessionId = null;
|
|
44
|
+
this.addSubscriptions(this.onWsOpen.subscribe(() => {
|
|
45
|
+
(0, util_1.logDebug)(`WebSocket connected for ${camera.name} (Ring Edge)`);
|
|
46
|
+
this.initiateCall().catch((e) => {
|
|
47
|
+
(0, util_1.logError)(e);
|
|
48
|
+
this.callEnded();
|
|
49
|
+
});
|
|
50
|
+
}),
|
|
51
|
+
// The ring-edge session needs a ping every 5 seconds to keep the connection alive
|
|
52
|
+
this.onCallAnswered
|
|
53
|
+
.pipe((0, operators_1.switchMap)(() => (0, rxjs_1.interval)(5000)))
|
|
54
|
+
.subscribe(() => {
|
|
55
|
+
if (!this.sessionId) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.sendSessionMessage('ping');
|
|
59
|
+
}), this.pc.onIceCandidate.subscribe((iceCandidate) => __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
yield (0, rxjs_1.firstValueFrom)(this.onOfferSent);
|
|
61
|
+
this.sendMessage({
|
|
62
|
+
method: 'ice',
|
|
63
|
+
body: {
|
|
64
|
+
doorbot_id: camera.id,
|
|
65
|
+
ice: iceCandidate.candidate,
|
|
66
|
+
mlineindex: 0,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
// HACK: send ice candidate with both mline indexes to convince ring edge to connect both audio and video
|
|
70
|
+
// Without this, only audio will connect unless you connect from the network of the Ring Edge router
|
|
71
|
+
this.sendMessage({
|
|
72
|
+
method: 'ice',
|
|
73
|
+
body: {
|
|
74
|
+
doorbot_id: camera.id,
|
|
75
|
+
ice: iceCandidate.candidate,
|
|
76
|
+
mlineindex: 1,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
})));
|
|
80
|
+
}
|
|
81
|
+
initiateCall() {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
const { sdp } = yield this.pc.createOffer();
|
|
84
|
+
this.sendMessage({
|
|
85
|
+
method: 'live_view',
|
|
86
|
+
body: {
|
|
87
|
+
doorbot_id: this.camera.id,
|
|
88
|
+
stream_options: { audio_enabled: true, video_enabled: true },
|
|
89
|
+
sdp: sdp.replace('\na=group:BUNDLE 0 1', ''),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
this.onOfferSent.next();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
handleMessage(message) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
if (message.body.doorbot_id !== this.camera.id) {
|
|
98
|
+
// ignore messages for other cameras
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (['session_created', 'session_started'].includes(message.method) &&
|
|
102
|
+
'session_id' in message.body &&
|
|
103
|
+
!this.sessionId) {
|
|
104
|
+
this.sessionId = message.body.session_id;
|
|
105
|
+
this.onSessionId.next(this.sessionId);
|
|
106
|
+
}
|
|
107
|
+
if (message.body.session_id && message.body.session_id !== this.sessionId) {
|
|
108
|
+
// ignore messages for other sessions
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
switch (message.method) {
|
|
112
|
+
case 'session_created':
|
|
113
|
+
case 'session_started':
|
|
114
|
+
// session already stored above
|
|
115
|
+
return;
|
|
116
|
+
case 'sdp':
|
|
117
|
+
yield this.pc.acceptAnswer(message.body);
|
|
118
|
+
this.onCallAnswered.next(message.body.sdp);
|
|
119
|
+
return;
|
|
120
|
+
case 'ice':
|
|
121
|
+
yield this.pc.addIceCandidate({
|
|
122
|
+
candidate: message.body.ice,
|
|
123
|
+
sdpMLineIndex: message.body.mlineindex,
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
case 'pong':
|
|
127
|
+
return;
|
|
128
|
+
case 'close':
|
|
129
|
+
(0, util_1.logError)('Video stream closed');
|
|
130
|
+
(0, util_1.logError)(message.body);
|
|
131
|
+
this.callEnded();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
(0, util_1.logError)('UNKNOWN MESSAGE');
|
|
135
|
+
(0, util_1.logError)(message);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
sendSessionMessage(method, body = {}) {
|
|
139
|
+
const message = {
|
|
140
|
+
method,
|
|
141
|
+
body: Object.assign(Object.assign({}, body), { doorbot_id: this.camera.id, session_id: this.sessionId }),
|
|
142
|
+
};
|
|
143
|
+
if (this.sessionId) {
|
|
144
|
+
// Send immediately if we already have a session id
|
|
145
|
+
// This is needed to send `close` before closing the websocket
|
|
146
|
+
this.sendMessage(message);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
(0, rxjs_1.firstValueFrom)(this.onSessionId)
|
|
150
|
+
.then(() => this.sendMessage(message))
|
|
151
|
+
.catch((e) => (0, util_1.logError)(e));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.RingEdgeConnection = RingEdgeConnection;
|
|
@@ -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;
|