ring-client-api 11.0.2-beta.0 → 11.0.3
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 +18 -1
- package/lib/api/streaming/peer-connection.d.ts +5 -1
- package/lib/api/streaming/peer-connection.js +15 -2
- package/lib/api/streaming/ring-edge-connection.js +21 -17
- package/lib/api/streaming/streaming-connection-base.d.ts +7 -3
- package/lib/api/streaming/streaming-connection-base.js +35 -13
- package/lib/api/streaming/streaming-session.d.ts +13 -3
- package/lib/api/streaming/streaming-session.js +69 -14
- package/lib/api/streaming/webrtc-connection.js +1 -4
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,29 @@
|
|
|
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
|
-
### [11.0.
|
|
5
|
+
### [11.0.3](https://github.com/dgreif/ring/compare/v11.0.2...v11.0.3) (2022-05-28)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
### Bug Fixes
|
|
9
9
|
|
|
10
|
+
* automatically activate streams and allow key frame requests ([b129f29](https://github.com/dgreif/ring/commit/b129f29d0a3774d779e9c159fe381e4e09066c0d))
|
|
11
|
+
* end streaming session when connection ends ([b6bf08b](https://github.com/dgreif/ring/commit/b6bf08ba01e344faf59f6b42ea0c3b5ce384e0bf))
|
|
12
|
+
* **homebridge:** allow ffmpeg in config schema ([#946](https://github.com/dgreif/ring/issues/946)) ([3242bbd](https://github.com/dgreif/ring/commit/3242bbd34dd6847500839396a9e10509e8216023))
|
|
13
|
+
* remove race condition for sending messages to ring edge connections ([a9438d3](https://github.com/dgreif/ring/commit/a9438d3dfc5e7ad8b96881b689534c84fab637f1))
|
|
14
|
+
* start ring edge pings as soon as session is started ([04ad614](https://github.com/dgreif/ring/commit/04ad614cb25c9aa4b11459e5fecde9ff7766a32d))
|
|
15
|
+
* update dependencies including ffmpeg ([f5cd553](https://github.com/dgreif/ring/commit/f5cd553506cf0ea31a309d1350eff19c48552809))
|
|
16
|
+
* use random client id for every ring edge connection ([3ba78e3](https://github.com/dgreif/ring/commit/3ba78e39b9da392779005d7d76828ddd2e425e6b))
|
|
17
|
+
|
|
18
|
+
### [11.0.2](https://github.com/dgreif/ring/compare/v11.0.1...v11.0.2) (2022-05-22)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **api:** prevent duplicate events from `onCallEnded` ([14ee6eb](https://github.com/dgreif/ring/commit/14ee6eb90bb6ae568dcfd9e8e176c3001e422f9c)), closes [#941](https://github.com/dgreif/ring/issues/941)
|
|
10
24
|
* handle pcmu audio streams for incoming audio ([06f6ff1](https://github.com/dgreif/ring/commit/06f6ff10ce7c8f2b05b1a12b8f244daa200fa9ab)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
25
|
+
* handle pcmu return audio ([7b86182](https://github.com/dgreif/ring/commit/7b8618261a6ccb85b9f760ee1fcc228be00fe5a6)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
26
|
+
* **homebride:** use shared engines config to enforce node 16 requirement ([b748cb0](https://github.com/dgreif/ring/commit/b748cb027db0c43ae109e1ea77c6bc83f86e4913))
|
|
27
|
+
* prevent hanging promises from streams that fail to connect ([dd7638e](https://github.com/dgreif/ring/commit/dd7638e32626499c0be501be6b19b5d874fa974f))
|
|
11
28
|
|
|
12
29
|
### [11.0.1](https://github.com/dgreif/ring/compare/v11.0.0...v11.0.1) (2022-05-17)
|
|
13
30
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConnectionState, MediaStreamTrack, RTCIceCandidate, RtcpPacket, RtpPacket } from 'werift';
|
|
2
2
|
import { Observable, ReplaySubject, Subject } from 'rxjs';
|
|
3
|
+
import { Subscribed } from '../subscribed';
|
|
3
4
|
export interface BasicPeerConnection {
|
|
4
5
|
createOffer(): Promise<{
|
|
5
6
|
sdp: string;
|
|
@@ -16,8 +17,9 @@ export interface BasicPeerConnection {
|
|
|
16
17
|
onIceCandidate: Observable<RTCIceCandidate>;
|
|
17
18
|
onConnectionState: Observable<ConnectionState>;
|
|
18
19
|
close(): void;
|
|
20
|
+
requestKeyFrame?: () => void;
|
|
19
21
|
}
|
|
20
|
-
export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
22
|
+
export declare class WeriftPeerConnection extends Subscribed implements BasicPeerConnection {
|
|
21
23
|
private pc;
|
|
22
24
|
onAudioRtp: Subject<RtpPacket>;
|
|
23
25
|
onAudioRtcp: Subject<RtcpPacket>;
|
|
@@ -26,6 +28,7 @@ export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
|
26
28
|
onIceCandidate: Subject<RTCIceCandidate>;
|
|
27
29
|
onConnectionState: ReplaySubject<"failed" | "closed" | "connected" | "connecting" | "new" | "disconnected">;
|
|
28
30
|
returnAudioTrack: MediaStreamTrack;
|
|
31
|
+
private onRequestKeyFrame;
|
|
29
32
|
constructor();
|
|
30
33
|
createOffer(): Promise<import("werift").RTCSessionDescription>;
|
|
31
34
|
createAnswer(offer: {
|
|
@@ -37,5 +40,6 @@ export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
|
37
40
|
sdp: string;
|
|
38
41
|
}): Promise<void>;
|
|
39
42
|
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
43
|
+
requestKeyFrame(): void;
|
|
40
44
|
close(): void;
|
|
41
45
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WeriftPeerConnection = void 0;
|
|
4
|
+
/* eslint-disable brace-style */
|
|
4
5
|
const werift_1 = require("werift");
|
|
5
6
|
const rxjs_1 = require("rxjs");
|
|
6
7
|
const util_1 = require("../util");
|
|
8
|
+
const subscribed_1 = require("../subscribed");
|
|
7
9
|
const debug = false, ringIceServers = [
|
|
8
10
|
'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
|
|
9
11
|
'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
|
|
@@ -14,8 +16,9 @@ const debug = false, ringIceServers = [
|
|
|
14
16
|
'stun:stun3.l.google.com:19302',
|
|
15
17
|
'stun:stun4.l.google.com:19302',
|
|
16
18
|
];
|
|
17
|
-
class WeriftPeerConnection {
|
|
19
|
+
class WeriftPeerConnection extends subscribed_1.Subscribed {
|
|
18
20
|
constructor() {
|
|
21
|
+
super();
|
|
19
22
|
this.onAudioRtp = new rxjs_1.Subject();
|
|
20
23
|
this.onAudioRtcp = new rxjs_1.Subject();
|
|
21
24
|
this.onVideoRtp = new rxjs_1.Subject();
|
|
@@ -23,6 +26,7 @@ class WeriftPeerConnection {
|
|
|
23
26
|
this.onIceCandidate = new rxjs_1.Subject();
|
|
24
27
|
this.onConnectionState = new rxjs_1.ReplaySubject(1);
|
|
25
28
|
this.returnAudioTrack = new werift_1.MediaStreamTrack({ kind: 'audio' });
|
|
29
|
+
this.onRequestKeyFrame = new rxjs_1.Subject();
|
|
26
30
|
const pc = (this.pc = new werift_1.RTCPeerConnection({
|
|
27
31
|
codecs: {
|
|
28
32
|
audio: [
|
|
@@ -84,7 +88,12 @@ class WeriftPeerConnection {
|
|
|
84
88
|
if (debug) {
|
|
85
89
|
(0, util_1.logInfo)('received first video packet');
|
|
86
90
|
}
|
|
87
|
-
|
|
91
|
+
this.addSubscriptions((0, rxjs_1.merge)(this.onRequestKeyFrame, (0, rxjs_1.interval)(4000)).subscribe(() => {
|
|
92
|
+
videoTransceiver.receiver
|
|
93
|
+
.sendRtcpPLI(track.ssrc)
|
|
94
|
+
.catch((e) => (0, util_1.logError)(e));
|
|
95
|
+
}));
|
|
96
|
+
this.requestKeyFrame();
|
|
88
97
|
});
|
|
89
98
|
});
|
|
90
99
|
this.pc.onIceCandidate.subscribe((iceCandidate) => {
|
|
@@ -118,8 +127,12 @@ class WeriftPeerConnection {
|
|
|
118
127
|
addIceCandidate(candidate) {
|
|
119
128
|
return this.pc.addIceCandidate(candidate);
|
|
120
129
|
}
|
|
130
|
+
requestKeyFrame() {
|
|
131
|
+
this.onRequestKeyFrame.next();
|
|
132
|
+
}
|
|
121
133
|
close() {
|
|
122
134
|
this.pc.close().catch(util_1.logError);
|
|
135
|
+
this.unsubscribe();
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
138
|
exports.WeriftPeerConnection = WeriftPeerConnection;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.RingEdgeConnection = void 0;
|
|
4
7
|
const ws_1 = require("ws");
|
|
5
8
|
const rxjs_1 = require("rxjs");
|
|
6
9
|
const util_1 = require("../util");
|
|
7
|
-
const operators_1 = require("rxjs/operators");
|
|
8
10
|
const streaming_connection_base_1 = require("./streaming-connection-base");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
12
|
// eslint-disable-next-line no-shadow
|
|
10
13
|
var CloseReasonCode;
|
|
11
14
|
(function (CloseReasonCode) {
|
|
@@ -23,7 +26,9 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
23
26
|
headers: {
|
|
24
27
|
Authorization: `Bearer ${authToken}`,
|
|
25
28
|
'X-Sig-API-Version': '4.0',
|
|
26
|
-
'X-Sig-Client-ID':
|
|
29
|
+
'X-Sig-Client-ID': `ring_android-${crypto_1.default
|
|
30
|
+
.randomBytes(4)
|
|
31
|
+
.toString('hex')}`,
|
|
27
32
|
'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',
|
|
28
33
|
'X-Sig-Auth-Type': 'ring_oauth',
|
|
29
34
|
},
|
|
@@ -40,12 +45,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
40
45
|
});
|
|
41
46
|
}),
|
|
42
47
|
// The ring-edge session needs a ping every 5 seconds to keep the connection alive
|
|
43
|
-
|
|
44
|
-
.pipe((0, operators_1.switchMap)(() => (0, rxjs_1.interval)(5000)))
|
|
45
|
-
.subscribe(() => {
|
|
46
|
-
if (!this.sessionId) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
48
|
+
(0, rxjs_1.interval)(5000).subscribe(() => {
|
|
49
49
|
this.sendSessionMessage('ping');
|
|
50
50
|
}), this.pc.onIceCandidate.subscribe(async (iceCandidate) => {
|
|
51
51
|
await (0, rxjs_1.firstValueFrom)(this.onOfferSent);
|
|
@@ -104,6 +104,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
104
104
|
case 'sdp':
|
|
105
105
|
await this.pc.acceptAnswer(message.body);
|
|
106
106
|
this.onCallAnswered.next(message.body.sdp);
|
|
107
|
+
this.activate();
|
|
107
108
|
return;
|
|
108
109
|
case 'ice':
|
|
109
110
|
await this.pc.addIceCandidate({
|
|
@@ -123,22 +124,25 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
|
|
|
123
124
|
(0, util_1.logError)(message);
|
|
124
125
|
}
|
|
125
126
|
sendSessionMessage(method, body = {}) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
const sendSessionMessage = () => {
|
|
128
|
+
const message = {
|
|
129
|
+
method,
|
|
130
|
+
body: {
|
|
131
|
+
...body,
|
|
132
|
+
doorbot_id: this.camera.id,
|
|
133
|
+
session_id: this.sessionId,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
this.sendMessage(message);
|
|
133
137
|
};
|
|
134
138
|
if (this.sessionId) {
|
|
135
139
|
// Send immediately if we already have a session id
|
|
136
140
|
// This is needed to send `close` before closing the websocket
|
|
137
|
-
|
|
141
|
+
sendSessionMessage();
|
|
138
142
|
}
|
|
139
143
|
else {
|
|
140
144
|
(0, rxjs_1.firstValueFrom)(this.onSessionId)
|
|
141
|
-
.then(
|
|
145
|
+
.then(sendSessionMessage)
|
|
142
146
|
.catch((e) => (0, util_1.logError)(e));
|
|
143
147
|
}
|
|
144
148
|
}
|
|
@@ -11,6 +11,9 @@ export declare abstract class StreamingConnectionBase extends Subscribed {
|
|
|
11
11
|
protected options: StreamingConnectionOptions;
|
|
12
12
|
readonly onCallAnswered: ReplaySubject<string>;
|
|
13
13
|
readonly onCallEnded: ReplaySubject<void>;
|
|
14
|
+
readonly onMessage: ReplaySubject<{
|
|
15
|
+
method: string;
|
|
16
|
+
}>;
|
|
14
17
|
readonly onWsOpen: import("rxjs").Observable<unknown>;
|
|
15
18
|
protected readonly pc: BasicPeerConnection;
|
|
16
19
|
readonly onAudioRtp: Subject<RtpPacket>;
|
|
@@ -20,11 +23,12 @@ export declare abstract class StreamingConnectionBase extends Subscribed {
|
|
|
20
23
|
method: string;
|
|
21
24
|
}): Promise<void>;
|
|
22
25
|
protected abstract sendSessionMessage(method: string, body?: Record<any, any>): void;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
activateCameraSpeaker(): Promise<void>;
|
|
26
|
+
protected activate(): void;
|
|
27
|
+
activateCameraSpeaker(): void;
|
|
26
28
|
protected sendMessage(message: Record<any, any>): void;
|
|
27
29
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
30
|
+
private hasEnded;
|
|
28
31
|
protected callEnded(): void;
|
|
29
32
|
stop(): void;
|
|
33
|
+
requestKeyFrame(): void;
|
|
30
34
|
}
|
|
@@ -13,7 +13,8 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
13
13
|
this.options = options;
|
|
14
14
|
this.onCallAnswered = new rxjs_1.ReplaySubject(1);
|
|
15
15
|
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
16
|
-
this.
|
|
16
|
+
this.onMessage = new rxjs_1.ReplaySubject();
|
|
17
|
+
this.hasEnded = false;
|
|
17
18
|
if (options.createPeerConnection) {
|
|
18
19
|
// we were passed a custom peer connection factory
|
|
19
20
|
this.pc = options.createPeerConnection();
|
|
@@ -31,8 +32,10 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
31
32
|
this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
|
|
32
33
|
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');
|
|
33
34
|
this.addSubscriptions(onMessage
|
|
34
|
-
.pipe((0, operators_1.concatMap)((
|
|
35
|
-
|
|
35
|
+
.pipe((0, operators_1.concatMap)((event) => {
|
|
36
|
+
const message = JSON.parse(event.data);
|
|
37
|
+
this.onMessage.next(message);
|
|
38
|
+
return this.handleMessage(message);
|
|
36
39
|
}))
|
|
37
40
|
.subscribe(), onError.subscribe((e) => {
|
|
38
41
|
(0, util_1.logError)(e);
|
|
@@ -50,25 +53,37 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
50
53
|
}
|
|
51
54
|
}));
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
if (this.activated) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
this.activated = true;
|
|
58
|
-
await (0, rxjs_1.firstValueFrom)(this.pc.onConnectionState.pipe((0, operators_1.filter)((state) => state === 'connected')));
|
|
56
|
+
activate() {
|
|
59
57
|
(0, util_1.logInfo)('Activating Session');
|
|
58
|
+
// the activate_session message is required to keep the stream alive longer than 70 seconds
|
|
60
59
|
this.sendSessionMessage('activate_session');
|
|
60
|
+
this.sendSessionMessage('stream_options', {
|
|
61
|
+
audio_enabled: true,
|
|
62
|
+
video_enabled: true,
|
|
63
|
+
});
|
|
61
64
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
activateCameraSpeaker() {
|
|
66
|
+
// Fire and forget this call so that callers don't get hung up waiting for answer (which might not happen)
|
|
67
|
+
(0, rxjs_1.firstValueFrom)(this.onCallAnswered)
|
|
68
|
+
.then(() => {
|
|
69
|
+
this.sendSessionMessage('camera_options', {
|
|
70
|
+
stealth_mode: false,
|
|
71
|
+
});
|
|
72
|
+
})
|
|
73
|
+
.catch((e) => {
|
|
74
|
+
(0, util_1.logError)(e);
|
|
66
75
|
});
|
|
67
76
|
}
|
|
68
77
|
sendMessage(message) {
|
|
78
|
+
if (this.hasEnded) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
69
81
|
this.ws.send(JSON.stringify(message));
|
|
70
82
|
}
|
|
71
83
|
sendAudioPacket(rtp) {
|
|
84
|
+
if (this.hasEnded) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
72
87
|
if (this.pc instanceof peer_connection_1.WeriftPeerConnection) {
|
|
73
88
|
this.pc.returnAudioTrack.writeRtp(rtp);
|
|
74
89
|
}
|
|
@@ -77,6 +92,9 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
callEnded() {
|
|
95
|
+
if (this.hasEnded) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
80
98
|
try {
|
|
81
99
|
this.sendMessage({
|
|
82
100
|
reason: { code: 0, text: '' },
|
|
@@ -87,6 +105,7 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
87
105
|
catch (_) {
|
|
88
106
|
// ignore any errors since we are stopping the call
|
|
89
107
|
}
|
|
108
|
+
this.hasEnded = true;
|
|
90
109
|
this.unsubscribe();
|
|
91
110
|
this.onCallEnded.next();
|
|
92
111
|
this.pc.close();
|
|
@@ -94,5 +113,8 @@ class StreamingConnectionBase extends subscribed_1.Subscribed {
|
|
|
94
113
|
stop() {
|
|
95
114
|
this.callEnded();
|
|
96
115
|
}
|
|
116
|
+
requestKeyFrame() {
|
|
117
|
+
this.pc.requestKeyFrame?.();
|
|
118
|
+
}
|
|
97
119
|
}
|
|
98
120
|
exports.StreamingConnectionBase = StreamingConnectionBase;
|
|
@@ -20,16 +20,26 @@ export declare class StreamingSession extends Subscribed {
|
|
|
20
20
|
readonly onAudioRtp: Subject<RtpPacket>;
|
|
21
21
|
private readonly audioSplitter;
|
|
22
22
|
private readonly videoSplitter;
|
|
23
|
+
private readonly returnAudioSplitter;
|
|
23
24
|
constructor(camera: RingCamera, connection: RingEdgeConnection | WebrtcConnection);
|
|
24
25
|
private bindToConnection;
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated
|
|
28
|
+
* activate will be removed in the future. Please use requestKeyFrame if you want to explicitly request an initial key frame
|
|
29
|
+
*/
|
|
30
|
+
activate(): void;
|
|
27
31
|
cameraSpeakerActivated: boolean;
|
|
28
|
-
activateCameraSpeaker():
|
|
32
|
+
activateCameraSpeaker(): void;
|
|
29
33
|
reservePort(bufferPorts?: number): Promise<number>;
|
|
34
|
+
get isUsingOpus(): Promise<boolean>;
|
|
30
35
|
startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
|
|
36
|
+
transcodeReturnAudio(ffmpegOptions: {
|
|
37
|
+
input: SpawnInput[];
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
private hasEnded;
|
|
31
40
|
private callEnded;
|
|
32
41
|
stop(): void;
|
|
33
42
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
43
|
+
requestKeyFrame(): void;
|
|
34
44
|
}
|
|
35
45
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StreamingSession = void 0;
|
|
4
|
+
const werift_1 = require("werift");
|
|
4
5
|
const camera_utils_1 = require("@homebridge/camera-utils");
|
|
5
6
|
const rxjs_1 = require("rxjs");
|
|
6
7
|
const ffmpeg_1 = require("../ffmpeg");
|
|
@@ -26,35 +27,42 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
26
27
|
this.onAudioRtp = new rxjs_1.Subject();
|
|
27
28
|
this.audioSplitter = new camera_utils_1.RtpSplitter();
|
|
28
29
|
this.videoSplitter = new camera_utils_1.RtpSplitter();
|
|
29
|
-
this.
|
|
30
|
+
this.returnAudioSplitter = new camera_utils_1.RtpSplitter();
|
|
30
31
|
this.cameraSpeakerActivated = false;
|
|
32
|
+
this.hasEnded = false;
|
|
31
33
|
this.bindToConnection(connection);
|
|
32
34
|
}
|
|
33
35
|
bindToConnection(connection) {
|
|
34
36
|
this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp), connection.onCallAnswered.subscribe((sdp) => {
|
|
35
37
|
this.onUsingOpus.next(sdp.toLocaleLowerCase().includes(' opus/'));
|
|
36
|
-
}));
|
|
38
|
+
}), connection.onCallEnded.subscribe(() => this.callEnded()));
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated
|
|
42
|
+
* activate will be removed in the future. Please use requestKeyFrame if you want to explicitly request an initial key frame
|
|
43
|
+
*/
|
|
44
|
+
activate() {
|
|
45
|
+
this.requestKeyFrame();
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
if (this.cameraSpeakerActivated) {
|
|
47
|
+
activateCameraSpeaker() {
|
|
48
|
+
if (this.cameraSpeakerActivated || this.hasEnded) {
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
this.cameraSpeakerActivated = true;
|
|
50
|
-
|
|
52
|
+
this.connection.activateCameraSpeaker();
|
|
51
53
|
}
|
|
52
54
|
async reservePort(bufferPorts = 0) {
|
|
53
55
|
const ports = await (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
|
|
54
56
|
return ports[0];
|
|
55
57
|
}
|
|
58
|
+
get isUsingOpus() {
|
|
59
|
+
return (0, rxjs_1.firstValueFrom)(this.onUsingOpus);
|
|
60
|
+
}
|
|
56
61
|
async startTranscoding(ffmpegOptions) {
|
|
57
|
-
|
|
62
|
+
if (this.hasEnded) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const videoPort = await this.reservePort(1), audioPort = await this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, ringSdp = await (0, rxjs_1.firstValueFrom)(this.connection.onCallAnswered), usingOpus = await this.isUsingOpus, ffmpegInputArguments = [
|
|
58
66
|
'-hide_banner',
|
|
59
67
|
'-protocol_whitelist',
|
|
60
68
|
'pipe,udp,rtp,file,crypto',
|
|
@@ -97,21 +105,68 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
97
105
|
}
|
|
98
106
|
this.onCallEnded.subscribe(() => ff.stop());
|
|
99
107
|
ff.writeStdin(inputSdp);
|
|
100
|
-
//
|
|
101
|
-
|
|
108
|
+
// Request a key frame now that ffmpeg is ready to receive
|
|
109
|
+
this.requestKeyFrame();
|
|
110
|
+
}
|
|
111
|
+
async transcodeReturnAudio(ffmpegOptions) {
|
|
112
|
+
if (this.hasEnded) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const audioOutForwarder = new camera_utils_1.RtpSplitter(({ message }) => {
|
|
116
|
+
const rtp = werift_1.RtpPacket.deSerialize(message);
|
|
117
|
+
this.connection.sendAudioPacket(rtp);
|
|
118
|
+
return null;
|
|
119
|
+
}), usingOpus = await this.isUsingOpus, ff = new camera_utils_1.FfmpegProcess({
|
|
120
|
+
ffmpegArgs: [
|
|
121
|
+
'-hide_banner',
|
|
122
|
+
'-protocol_whitelist',
|
|
123
|
+
'pipe,udp,rtp,file,crypto',
|
|
124
|
+
'-re',
|
|
125
|
+
'-i',
|
|
126
|
+
...ffmpegOptions.input,
|
|
127
|
+
'-acodec',
|
|
128
|
+
...(usingOpus
|
|
129
|
+
? ['libopus', '-ac', 2, '-ar', '48k']
|
|
130
|
+
: ['pcm_mulaw', '-ac', 1, '-ar', '8k']),
|
|
131
|
+
'-flags',
|
|
132
|
+
'+global_header',
|
|
133
|
+
'-f',
|
|
134
|
+
'rtp',
|
|
135
|
+
`rtp://127.0.0.1:${await audioOutForwarder.portPromise}`,
|
|
136
|
+
],
|
|
137
|
+
ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
|
|
138
|
+
exitCallback: () => this.callEnded(),
|
|
139
|
+
logLabel: `Return Audio (${this.camera.name})`,
|
|
140
|
+
logger: {
|
|
141
|
+
error: util_1.logError,
|
|
142
|
+
info: util_1.logDebug,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
this.onCallEnded.subscribe(() => ff.stop());
|
|
102
146
|
}
|
|
103
147
|
callEnded() {
|
|
148
|
+
if (this.hasEnded) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.hasEnded = true;
|
|
104
152
|
this.unsubscribe();
|
|
105
153
|
this.onCallEnded.next();
|
|
106
154
|
this.connection.stop();
|
|
107
155
|
this.audioSplitter.close();
|
|
108
156
|
this.videoSplitter.close();
|
|
157
|
+
this.returnAudioSplitter.close();
|
|
109
158
|
}
|
|
110
159
|
stop() {
|
|
111
160
|
this.callEnded();
|
|
112
161
|
}
|
|
113
162
|
sendAudioPacket(rtp) {
|
|
163
|
+
if (this.hasEnded) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
114
166
|
this.connection.sendAudioPacket(rtp);
|
|
115
167
|
}
|
|
168
|
+
requestKeyFrame() {
|
|
169
|
+
this.connection.requestKeyFrame();
|
|
170
|
+
}
|
|
116
171
|
}
|
|
117
172
|
exports.StreamingSession = StreamingSession;
|
|
@@ -29,10 +29,7 @@ class WebrtcConnection extends streaming_connection_base_1.StreamingConnectionBa
|
|
|
29
29
|
const answer = await this.pc.createAnswer(message);
|
|
30
30
|
this.sendSessionMessage('sdp', answer);
|
|
31
31
|
this.onCallAnswered.next(message.sdp);
|
|
32
|
-
this.
|
|
33
|
-
audio_enabled: true,
|
|
34
|
-
video_enabled: true,
|
|
35
|
-
});
|
|
32
|
+
this.activate();
|
|
36
33
|
return;
|
|
37
34
|
case 'ice':
|
|
38
35
|
await this.pc.addIceCandidate({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ring-client-api",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.3",
|
|
4
4
|
"description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
5
|
"main": "lib/api/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@eneris/push-receiver": "^3.0.1",
|
|
59
|
-
"@homebridge/camera-utils": "^2.1.
|
|
59
|
+
"@homebridge/camera-utils": "^2.1.1",
|
|
60
60
|
"@homebridge/plugin-ui-utils": "^0.0.19",
|
|
61
|
-
"werift": "^0.15.
|
|
61
|
+
"werift": "^0.15.3",
|
|
62
62
|
"@types/socket.io-client": "1.4.36",
|
|
63
63
|
"colors": "^1.4.0",
|
|
64
64
|
"debug": "^4.3.4",
|
|
@@ -67,24 +67,24 @@
|
|
|
67
67
|
"socket.io-client": "^2.4.0",
|
|
68
68
|
"systeminformation": "^5.11.15",
|
|
69
69
|
"uuid": "^8.3.2",
|
|
70
|
-
"ws": "^8.
|
|
70
|
+
"ws": "^8.7.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@swc-node/register": "^1.5.1",
|
|
74
74
|
"@types/debug": "4.1.7",
|
|
75
75
|
"@types/jest": "27.5.1",
|
|
76
|
-
"@types/node": "17.0.
|
|
76
|
+
"@types/node": "17.0.36",
|
|
77
77
|
"@types/uuid": "8.3.4",
|
|
78
78
|
"@types/ws": "^8.5.3",
|
|
79
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
80
|
-
"@typescript-eslint/parser": "5.
|
|
81
|
-
"concurrently": "^7.2.
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "5.26.0",
|
|
80
|
+
"@typescript-eslint/parser": "5.26.0",
|
|
81
|
+
"concurrently": "^7.2.1",
|
|
82
82
|
"conventional-github-releaser": "3.1.5",
|
|
83
83
|
"dotenv": "16.0.1",
|
|
84
|
-
"esbuild": "^0.14.
|
|
85
|
-
"eslint": "8.
|
|
84
|
+
"esbuild": "^0.14.41",
|
|
85
|
+
"eslint": "8.16.0",
|
|
86
86
|
"eslint-config-prettier": "8.5.0",
|
|
87
|
-
"eslint-plugin-jest": "26.
|
|
87
|
+
"eslint-plugin-jest": "26.4.2",
|
|
88
88
|
"eslint-plugin-prettier": "4.0.0",
|
|
89
89
|
"express": "4.18.1",
|
|
90
90
|
"homebridge": "1.4.1",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"reflect-metadata": "0.1.13",
|
|
94
94
|
"rimraf": "3.0.2",
|
|
95
95
|
"standard-version": "9.5.0",
|
|
96
|
-
"ts-jest": "28.0.
|
|
97
|
-
"typescript": "4.
|
|
96
|
+
"ts-jest": "28.0.3",
|
|
97
|
+
"typescript": "4.7.2"
|
|
98
98
|
},
|
|
99
99
|
"engines": {
|
|
100
100
|
"node": "^16",
|