ring-client-api 11.0.0 → 11.0.2-beta.1
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 +22 -0
- package/lib/api/streaming/peer-connection.d.ts +3 -3
- package/lib/api/streaming/peer-connection.js +7 -1
- package/lib/api/streaming/streaming-connection-base.d.ts +1 -1
- package/lib/api/streaming/streaming-session.d.ts +8 -1
- package/lib/api/streaming/streaming-session.js +63 -7
- package/package.json +20 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.2-beta.1](https://github.com/dgreif/ring/compare/v11.0.2-beta.0...v11.0.2-beta.1) (2022-05-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **api:** prevent duplicate events from `onCallEnded` ([75696d5](https://github.com/dgreif/ring/commit/75696d510969f706248a0f83e98fb1c99fc07213)), closes [#941](https://github.com/dgreif/ring/issues/941)
|
|
11
|
+
* handle pcmu return audio ([ce29fb5](https://github.com/dgreif/ring/commit/ce29fb5638bc488c515518a0fadcaca900ebe750)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
12
|
+
|
|
13
|
+
### [11.0.2-beta.0](https://github.com/dgreif/ring/compare/v11.0.1...v11.0.2-beta.0) (2022-05-21)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* handle pcmu audio streams for incoming audio ([06f6ff1](https://github.com/dgreif/ring/commit/06f6ff10ce7c8f2b05b1a12b8f244daa200fa9ab)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
19
|
+
|
|
20
|
+
### [11.0.1](https://github.com/dgreif/ring/compare/v11.0.0...v11.0.1) (2022-05-17)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* update webrtc package ([63d003c](https://github.com/dgreif/ring/commit/63d003c1517c158663d230e6daf911c7fc765c55)), closes [#930](https://github.com/dgreif/ring/issues/930)
|
|
26
|
+
|
|
5
27
|
## [11.0.0](https://github.com/dgreif/ring/compare/v10.0.0...v11.0.0) (2022-05-08)
|
|
6
28
|
|
|
7
29
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectionState, MediaStreamTrack, RTCIceCandidate, RtcpPacket, RtpPacket } from '
|
|
1
|
+
import { ConnectionState, MediaStreamTrack, RTCIceCandidate, RtcpPacket, RtpPacket } from 'werift';
|
|
2
2
|
import { Observable, ReplaySubject, Subject } from 'rxjs';
|
|
3
3
|
export interface BasicPeerConnection {
|
|
4
4
|
createOffer(): Promise<{
|
|
@@ -27,11 +27,11 @@ export declare class WeriftPeerConnection implements BasicPeerConnection {
|
|
|
27
27
|
onConnectionState: ReplaySubject<"failed" | "closed" | "connected" | "connecting" | "new" | "disconnected">;
|
|
28
28
|
returnAudioTrack: MediaStreamTrack;
|
|
29
29
|
constructor();
|
|
30
|
-
createOffer(): Promise<import("
|
|
30
|
+
createOffer(): Promise<import("werift").RTCSessionDescription>;
|
|
31
31
|
createAnswer(offer: {
|
|
32
32
|
type: 'offer';
|
|
33
33
|
sdp: string;
|
|
34
|
-
}): Promise<import("
|
|
34
|
+
}): Promise<import("werift").RTCSessionDescription>;
|
|
35
35
|
acceptAnswer(answer: {
|
|
36
36
|
type: 'answer';
|
|
37
37
|
sdp: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WeriftPeerConnection = void 0;
|
|
4
|
-
const werift_1 = require("
|
|
4
|
+
const werift_1 = require("werift");
|
|
5
5
|
const rxjs_1 = require("rxjs");
|
|
6
6
|
const util_1 = require("../util");
|
|
7
7
|
const debug = false, ringIceServers = [
|
|
@@ -31,6 +31,12 @@ class WeriftPeerConnection {
|
|
|
31
31
|
clockRate: 48000,
|
|
32
32
|
channels: 2,
|
|
33
33
|
}),
|
|
34
|
+
new werift_1.RTCRtpCodecParameters({
|
|
35
|
+
mimeType: 'audio/PCMU',
|
|
36
|
+
clockRate: 8000,
|
|
37
|
+
channels: 1,
|
|
38
|
+
payloadType: 0,
|
|
39
|
+
}),
|
|
34
40
|
],
|
|
35
41
|
video: [
|
|
36
42
|
new werift_1.RTCRtpCodecParameters({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BasicPeerConnection } from './peer-connection';
|
|
2
2
|
import { Subscribed } from '../subscribed';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
|
-
import { RtpPacket } from '
|
|
4
|
+
import { RtpPacket } from 'werift';
|
|
5
5
|
import { ReplaySubject, Subject } from 'rxjs';
|
|
6
6
|
export interface StreamingConnectionOptions {
|
|
7
7
|
createPeerConnection?: () => BasicPeerConnection;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RtpPacket } from '
|
|
1
|
+
import { RtpPacket } from 'werift';
|
|
2
2
|
import { ReplaySubject, Subject } from 'rxjs';
|
|
3
3
|
import { RingEdgeConnection } from './ring-edge-connection';
|
|
4
4
|
import { WebrtcConnection } from './webrtc-connection';
|
|
@@ -15,10 +15,12 @@ export declare class StreamingSession extends Subscribed {
|
|
|
15
15
|
private readonly camera;
|
|
16
16
|
private connection;
|
|
17
17
|
readonly onCallEnded: ReplaySubject<void>;
|
|
18
|
+
private readonly onUsingOpus;
|
|
18
19
|
readonly onVideoRtp: Subject<RtpPacket>;
|
|
19
20
|
readonly onAudioRtp: Subject<RtpPacket>;
|
|
20
21
|
private readonly audioSplitter;
|
|
21
22
|
private readonly videoSplitter;
|
|
23
|
+
private readonly returnAudioSplitter;
|
|
22
24
|
constructor(camera: RingCamera, connection: RingEdgeConnection | WebrtcConnection);
|
|
23
25
|
private bindToConnection;
|
|
24
26
|
activated: boolean;
|
|
@@ -26,7 +28,12 @@ export declare class StreamingSession extends Subscribed {
|
|
|
26
28
|
cameraSpeakerActivated: boolean;
|
|
27
29
|
activateCameraSpeaker(): Promise<void>;
|
|
28
30
|
reservePort(bufferPorts?: number): Promise<number>;
|
|
31
|
+
get isUsingOpus(): Promise<boolean>;
|
|
29
32
|
startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
|
|
33
|
+
transcodeReturnAudio(ffmpegOptions: {
|
|
34
|
+
input: SpawnInput[];
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
private hasEnded;
|
|
30
37
|
private callEnded;
|
|
31
38
|
stop(): void;
|
|
32
39
|
sendAudioPacket(rtp: RtpPacket): void;
|
|
@@ -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");
|
|
@@ -21,26 +22,31 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
21
22
|
this.camera = camera;
|
|
22
23
|
this.connection = connection;
|
|
23
24
|
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
25
|
+
this.onUsingOpus = new rxjs_1.ReplaySubject(1);
|
|
24
26
|
this.onVideoRtp = new rxjs_1.Subject();
|
|
25
27
|
this.onAudioRtp = new rxjs_1.Subject();
|
|
26
28
|
this.audioSplitter = new camera_utils_1.RtpSplitter();
|
|
27
29
|
this.videoSplitter = new camera_utils_1.RtpSplitter();
|
|
30
|
+
this.returnAudioSplitter = new camera_utils_1.RtpSplitter();
|
|
28
31
|
this.activated = false;
|
|
29
32
|
this.cameraSpeakerActivated = false;
|
|
33
|
+
this.hasEnded = false;
|
|
30
34
|
this.bindToConnection(connection);
|
|
31
35
|
}
|
|
32
36
|
bindToConnection(connection) {
|
|
33
|
-
this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp))
|
|
37
|
+
this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp), connection.onCallAnswered.subscribe((sdp) => {
|
|
38
|
+
this.onUsingOpus.next(sdp.toLocaleLowerCase().includes(' opus/'));
|
|
39
|
+
}));
|
|
34
40
|
}
|
|
35
41
|
async activate() {
|
|
36
|
-
if (this.activated) {
|
|
42
|
+
if (this.activated || this.hasEnded) {
|
|
37
43
|
return;
|
|
38
44
|
}
|
|
39
45
|
this.activated = true;
|
|
40
46
|
await this.connection.activate();
|
|
41
47
|
}
|
|
42
48
|
async activateCameraSpeaker() {
|
|
43
|
-
if (this.cameraSpeakerActivated) {
|
|
49
|
+
if (this.cameraSpeakerActivated || this.hasEnded) {
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
46
52
|
this.cameraSpeakerActivated = true;
|
|
@@ -50,19 +56,25 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
50
56
|
const ports = await (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
|
|
51
57
|
return ports[0];
|
|
52
58
|
}
|
|
59
|
+
get isUsingOpus() {
|
|
60
|
+
return (0, rxjs_1.firstValueFrom)(this.onUsingOpus);
|
|
61
|
+
}
|
|
53
62
|
async startTranscoding(ffmpegOptions) {
|
|
54
|
-
|
|
63
|
+
if (this.hasEnded) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
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 = [
|
|
55
67
|
'-hide_banner',
|
|
56
68
|
'-protocol_whitelist',
|
|
57
69
|
'pipe,udp,rtp,file,crypto',
|
|
58
|
-
|
|
59
|
-
'libopus',
|
|
70
|
+
// Ring will answer with either opus or pcmu
|
|
71
|
+
...(usingOpus ? ['-acodec', 'libopus'] : []),
|
|
60
72
|
'-f',
|
|
61
73
|
'sdp',
|
|
62
74
|
...(ffmpegOptions.input || []),
|
|
63
75
|
'-i',
|
|
64
76
|
'pipe:',
|
|
65
|
-
],
|
|
77
|
+
], inputSdp = getCleanSdp(ringSdp, transcodeVideoStream)
|
|
66
78
|
.replace(/m=audio \d+/, `m=audio ${audioPort}`)
|
|
67
79
|
.replace(/m=video \d+/, `m=video ${videoPort}`), ff = new camera_utils_1.FfmpegProcess({
|
|
68
80
|
ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
|
|
@@ -97,17 +109,61 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
97
109
|
// Activate the stream now that ffmpeg is ready to receive
|
|
98
110
|
await this.activate();
|
|
99
111
|
}
|
|
112
|
+
async transcodeReturnAudio(ffmpegOptions) {
|
|
113
|
+
if (this.hasEnded) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const audioOutForwarder = new camera_utils_1.RtpSplitter(({ message }) => {
|
|
117
|
+
const rtp = werift_1.RtpPacket.deSerialize(message);
|
|
118
|
+
this.connection.sendAudioPacket(rtp);
|
|
119
|
+
return null;
|
|
120
|
+
}), usingOpus = await this.isUsingOpus, ff = new camera_utils_1.FfmpegProcess({
|
|
121
|
+
ffmpegArgs: [
|
|
122
|
+
'-hide_banner',
|
|
123
|
+
'-protocol_whitelist',
|
|
124
|
+
'pipe,udp,rtp,file,crypto',
|
|
125
|
+
'-re',
|
|
126
|
+
'-i',
|
|
127
|
+
...ffmpegOptions.input,
|
|
128
|
+
'-acodec',
|
|
129
|
+
...(usingOpus
|
|
130
|
+
? ['libopus', '-ac', 2, '-ar', '48k']
|
|
131
|
+
: ['pcm_mulaw', '-ac', 1, '-ar', '8k']),
|
|
132
|
+
'-flags',
|
|
133
|
+
'+global_header',
|
|
134
|
+
'-f',
|
|
135
|
+
'rtp',
|
|
136
|
+
`rtp://127.0.0.1:${await audioOutForwarder.portPromise}`,
|
|
137
|
+
],
|
|
138
|
+
ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
|
|
139
|
+
exitCallback: () => this.callEnded(),
|
|
140
|
+
logLabel: `Return Audio (${this.camera.name})`,
|
|
141
|
+
logger: {
|
|
142
|
+
error: util_1.logError,
|
|
143
|
+
info: util_1.logDebug,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
this.onCallEnded.subscribe(() => ff.stop());
|
|
147
|
+
}
|
|
100
148
|
callEnded() {
|
|
149
|
+
if (this.hasEnded) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.hasEnded = true;
|
|
101
153
|
this.unsubscribe();
|
|
102
154
|
this.onCallEnded.next();
|
|
103
155
|
this.connection.stop();
|
|
104
156
|
this.audioSplitter.close();
|
|
105
157
|
this.videoSplitter.close();
|
|
158
|
+
this.returnAudioSplitter.close();
|
|
106
159
|
}
|
|
107
160
|
stop() {
|
|
108
161
|
this.callEnded();
|
|
109
162
|
}
|
|
110
163
|
sendAudioPacket(rtp) {
|
|
164
|
+
if (this.hasEnded) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
111
167
|
this.connection.sendAudioPacket(rtp);
|
|
112
168
|
}
|
|
113
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ring-client-api",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2-beta.1",
|
|
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": {
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"lint": "eslint '**/*.ts' --fix",
|
|
16
16
|
"build": "rm -rf lib && tsc --declaration",
|
|
17
17
|
"build-quick": "rm -rf lib && esbuild ./{api,examples,homebridge}/*.ts ./api/streaming/*.ts --target=es2017 --format=cjs --outdir=lib",
|
|
18
|
-
"example": "node -r @swc-node/register ./
|
|
19
|
-
"api-example": "node -r @swc-node/register ./
|
|
20
|
-
"record-example": "node -r @swc-node/register ./
|
|
21
|
-
"return-audio-example": "node -r @swc-node/register ./
|
|
22
|
-
"stream-example": "node -r @swc-node/register ./
|
|
23
|
-
"browser-example": "node -r @swc-node/register ./
|
|
18
|
+
"example": "node -r @swc-node/register ./examples/example.ts",
|
|
19
|
+
"api-example": "node -r @swc-node/register ./examples/api-example.ts",
|
|
20
|
+
"record-example": "node -r @swc-node/register ./examples/record-example.ts",
|
|
21
|
+
"return-audio-example": "node -r @swc-node/register ./examples/return-audio-example.ts",
|
|
22
|
+
"stream-example": "node -r @swc-node/register ./examples/stream-example.ts",
|
|
23
|
+
"browser-example": "node -r @swc-node/register ./examples/browser-example.ts",
|
|
24
24
|
"auth-cli": "npm run build-quick && ./ring-auth-cli.js",
|
|
25
25
|
"device-data-cli": "npm run build-quick && ./ring-device-data-cli.js",
|
|
26
26
|
"hb": "npm run build-quick && ./build/package-homebridge.sh && homebridge -P ./release-homebridge -U ./.homebridge",
|
|
@@ -58,33 +58,33 @@
|
|
|
58
58
|
"@eneris/push-receiver": "^3.0.1",
|
|
59
59
|
"@homebridge/camera-utils": "^2.1.0",
|
|
60
60
|
"@homebridge/plugin-ui-utils": "^0.0.19",
|
|
61
|
-
"
|
|
61
|
+
"werift": "^0.15.1",
|
|
62
62
|
"@types/socket.io-client": "1.4.36",
|
|
63
63
|
"colors": "^1.4.0",
|
|
64
64
|
"debug": "^4.3.4",
|
|
65
65
|
"got": "^11.8.3",
|
|
66
66
|
"rxjs": "^7.5.5",
|
|
67
67
|
"socket.io-client": "^2.4.0",
|
|
68
|
-
"systeminformation": "^5.11.
|
|
68
|
+
"systeminformation": "^5.11.15",
|
|
69
69
|
"uuid": "^8.3.2",
|
|
70
70
|
"ws": "^8.6.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@swc-node/register": "^1.5.1",
|
|
74
74
|
"@types/debug": "4.1.7",
|
|
75
|
-
"@types/jest": "27.5.
|
|
76
|
-
"@types/node": "17.0.
|
|
75
|
+
"@types/jest": "27.5.1",
|
|
76
|
+
"@types/node": "17.0.34",
|
|
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.
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "5.25.0",
|
|
80
|
+
"@typescript-eslint/parser": "5.25.0",
|
|
81
|
+
"concurrently": "^7.2.0",
|
|
82
82
|
"conventional-github-releaser": "3.1.5",
|
|
83
|
-
"dotenv": "16.0.
|
|
84
|
-
"esbuild": "^0.14.
|
|
85
|
-
"eslint": "8.
|
|
83
|
+
"dotenv": "16.0.1",
|
|
84
|
+
"esbuild": "^0.14.39",
|
|
85
|
+
"eslint": "8.15.0",
|
|
86
86
|
"eslint-config-prettier": "8.5.0",
|
|
87
|
-
"eslint-plugin-jest": "26.
|
|
87
|
+
"eslint-plugin-jest": "26.2.2",
|
|
88
88
|
"eslint-plugin-prettier": "4.0.0",
|
|
89
89
|
"express": "4.18.1",
|
|
90
90
|
"homebridge": "1.4.1",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
"prettier": "2.6.2",
|
|
93
93
|
"reflect-metadata": "0.1.13",
|
|
94
94
|
"rimraf": "3.0.2",
|
|
95
|
-
"standard-version": "9.
|
|
96
|
-
"ts-jest": "28.0.
|
|
95
|
+
"standard-version": "9.5.0",
|
|
96
|
+
"ts-jest": "28.0.2",
|
|
97
97
|
"typescript": "4.6.4"
|
|
98
98
|
},
|
|
99
99
|
"engines": {
|