ring-client-api 11.0.2-beta.1 → 11.0.4

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 CHANGED
@@ -2,20 +2,37 @@
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)
5
+ ### [11.0.4](https://github.com/dgreif/ring/compare/v11.0.3...v11.0.4) (2022-05-29)
6
6
 
7
7
 
8
8
  ### Bug Fixes
9
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)
10
+ * handle unanswered calls when transcoding ([6f9d3df](https://github.com/dgreif/ring/commit/6f9d3dfa079f046a6b5cfa140fd67eb210669429))
11
+ * update werift and ring edge connection strategy ([7b786f3](https://github.com/dgreif/ring/commit/7b786f3ffd9ad8eb4a8f03f5eac9ca6c67517aae))
12
12
 
13
- ### [11.0.2-beta.0](https://github.com/dgreif/ring/compare/v11.0.1...v11.0.2-beta.0) (2022-05-21)
13
+ ### [11.0.3](https://github.com/dgreif/ring/compare/v11.0.2...v11.0.3) (2022-05-28)
14
14
 
15
15
 
16
16
  ### Bug Fixes
17
17
 
18
+ * automatically activate streams and allow key frame requests ([b129f29](https://github.com/dgreif/ring/commit/b129f29d0a3774d779e9c159fe381e4e09066c0d))
19
+ * end streaming session when connection ends ([b6bf08b](https://github.com/dgreif/ring/commit/b6bf08ba01e344faf59f6b42ea0c3b5ce384e0bf))
20
+ * **homebridge:** allow ffmpeg in config schema ([#946](https://github.com/dgreif/ring/issues/946)) ([3242bbd](https://github.com/dgreif/ring/commit/3242bbd34dd6847500839396a9e10509e8216023))
21
+ * remove race condition for sending messages to ring edge connections ([a9438d3](https://github.com/dgreif/ring/commit/a9438d3dfc5e7ad8b96881b689534c84fab637f1))
22
+ * start ring edge pings as soon as session is started ([04ad614](https://github.com/dgreif/ring/commit/04ad614cb25c9aa4b11459e5fecde9ff7766a32d))
23
+ * update dependencies including ffmpeg ([f5cd553](https://github.com/dgreif/ring/commit/f5cd553506cf0ea31a309d1350eff19c48552809))
24
+ * use random client id for every ring edge connection ([3ba78e3](https://github.com/dgreif/ring/commit/3ba78e39b9da392779005d7d76828ddd2e425e6b))
25
+
26
+ ### [11.0.2](https://github.com/dgreif/ring/compare/v11.0.1...v11.0.2) (2022-05-22)
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * **api:** prevent duplicate events from `onCallEnded` ([14ee6eb](https://github.com/dgreif/ring/commit/14ee6eb90bb6ae568dcfd9e8e176c3001e422f9c)), closes [#941](https://github.com/dgreif/ring/issues/941)
18
32
  * handle pcmu audio streams for incoming audio ([06f6ff1](https://github.com/dgreif/ring/commit/06f6ff10ce7c8f2b05b1a12b8f244daa200fa9ab)), closes [#930](https://github.com/dgreif/ring/issues/930)
33
+ * handle pcmu return audio ([7b86182](https://github.com/dgreif/ring/commit/7b8618261a6ccb85b9f760ee1fcc228be00fe5a6)), closes [#930](https://github.com/dgreif/ring/issues/930)
34
+ * **homebride:** use shared engines config to enforce node 16 requirement ([b748cb0](https://github.com/dgreif/ring/commit/b748cb027db0c43ae109e1ea77c6bc83f86e4913))
35
+ * prevent hanging promises from streams that fail to connect ([dd7638e](https://github.com/dgreif/ring/commit/dd7638e32626499c0be501be6b19b5d874fa974f))
19
36
 
20
37
  ### [11.0.1](https://github.com/dgreif/ring/compare/v11.0.0...v11.0.1) (2022-05-17)
21
38
 
@@ -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;
@@ -12,12 +13,13 @@ export interface BasicPeerConnection {
12
13
  type: 'answer';
13
14
  sdp: string;
14
15
  }): Promise<void>;
15
- addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
16
+ addIceCandidate(candidate: Partial<RTCIceCandidate>): Promise<void>;
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,10 +1,12 @@
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");
7
- const debug = false, ringIceServers = [
8
+ const subscribed_1 = require("../subscribed");
9
+ const ringIceServers = [
8
10
  'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
9
11
  'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
10
12
  'stun:stun.kinesisvideo.us-west-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: [
@@ -55,6 +59,7 @@ class WeriftPeerConnection {
55
59
  },
56
60
  iceServers: ringIceServers.map((server) => ({ urls: server })),
57
61
  iceTransportPolicy: 'all',
62
+ bundlePolicy: 'disable',
58
63
  })), audioTransceiver = pc.addTransceiver(this.returnAudioTrack, {
59
64
  direction: 'sendrecv',
60
65
  }), videoTransceiver = pc.addTransceiver('video', {
@@ -67,11 +72,9 @@ class WeriftPeerConnection {
67
72
  track.onReceiveRtcp.subscribe((rtcp) => {
68
73
  this.onAudioRtcp.next(rtcp);
69
74
  });
70
- if (debug) {
71
- track.onReceiveRtp.once(() => {
72
- (0, util_1.logInfo)('received first audio packet');
73
- });
74
- }
75
+ track.onReceiveRtp.once(() => {
76
+ (0, util_1.logDebug)('received first audio packet');
77
+ });
75
78
  });
76
79
  videoTransceiver.onTrack.subscribe((track) => {
77
80
  track.onReceiveRtp.subscribe((rtp) => {
@@ -81,10 +84,13 @@ class WeriftPeerConnection {
81
84
  this.onVideoRtcp.next(rtcp);
82
85
  });
83
86
  track.onReceiveRtp.once(() => {
84
- if (debug) {
85
- (0, util_1.logInfo)('received first video packet');
86
- }
87
- setInterval(() => videoTransceiver.receiver.sendRtcpPLI(track.ssrc), 2000);
87
+ (0, util_1.logDebug)('received first video packet');
88
+ this.addSubscriptions((0, rxjs_1.merge)(this.onRequestKeyFrame, (0, rxjs_1.interval)(4000)).subscribe(() => {
89
+ videoTransceiver.receiver
90
+ .sendRtcpPLI(track.ssrc)
91
+ .catch((e) => (0, util_1.logError)(e));
92
+ }));
93
+ this.requestKeyFrame();
88
94
  });
89
95
  });
90
96
  this.pc.onIceCandidate.subscribe((iceCandidate) => {
@@ -118,8 +124,12 @@ class WeriftPeerConnection {
118
124
  addIceCandidate(candidate) {
119
125
  return this.pc.addIceCandidate(candidate);
120
126
  }
127
+ requestKeyFrame() {
128
+ this.onRequestKeyFrame.next();
129
+ }
121
130
  close() {
122
131
  this.pc.close().catch(util_1.logError);
132
+ this.unsubscribe();
123
133
  }
124
134
  }
125
135
  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': 'ring_android-aabb123',
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
- this.onCallAnswered
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);
@@ -54,17 +54,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
54
54
  body: {
55
55
  doorbot_id: camera.id,
56
56
  ice: iceCandidate.candidate,
57
- mlineindex: 0,
58
- },
59
- });
60
- // HACK: send ice candidate with both mline indexes to convince ring edge to connect both audio and video
61
- // Without this, only audio will connect unless you connect from the network of the Ring Edge router
62
- this.sendMessage({
63
- method: 'ice',
64
- body: {
65
- doorbot_id: camera.id,
66
- ice: iceCandidate.candidate,
67
- mlineindex: 1,
57
+ mlineindex: iceCandidate.sdpMLineIndex,
68
58
  },
69
59
  });
70
60
  }));
@@ -76,7 +66,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
76
66
  body: {
77
67
  doorbot_id: this.camera.id,
78
68
  stream_options: { audio_enabled: true, video_enabled: true },
79
- sdp: sdp.replace('\na=group:BUNDLE 0 1', ''),
69
+ sdp,
80
70
  },
81
71
  });
82
72
  this.onOfferSent.next();
@@ -104,6 +94,7 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
104
94
  case 'sdp':
105
95
  await this.pc.acceptAnswer(message.body);
106
96
  this.onCallAnswered.next(message.body.sdp);
97
+ this.activate();
107
98
  return;
108
99
  case 'ice':
109
100
  await this.pc.addIceCandidate({
@@ -123,22 +114,25 @@ class RingEdgeConnection extends streaming_connection_base_1.StreamingConnection
123
114
  (0, util_1.logError)(message);
124
115
  }
125
116
  sendSessionMessage(method, body = {}) {
126
- const message = {
127
- method,
128
- body: {
129
- ...body,
130
- doorbot_id: this.camera.id,
131
- session_id: this.sessionId,
132
- },
117
+ const sendSessionMessage = () => {
118
+ const message = {
119
+ method,
120
+ body: {
121
+ ...body,
122
+ doorbot_id: this.camera.id,
123
+ session_id: this.sessionId,
124
+ },
125
+ };
126
+ this.sendMessage(message);
133
127
  };
134
128
  if (this.sessionId) {
135
129
  // Send immediately if we already have a session id
136
130
  // This is needed to send `close` before closing the websocket
137
- this.sendMessage(message);
131
+ sendSessionMessage();
138
132
  }
139
133
  else {
140
134
  (0, rxjs_1.firstValueFrom)(this.onSessionId)
141
- .then(() => this.sendMessage(message))
135
+ .then(sendSessionMessage)
142
136
  .catch((e) => (0, util_1.logError)(e));
143
137
  }
144
138
  }
@@ -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
- private activated;
24
- activate(): Promise<void>;
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.activated = false;
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)((message) => {
35
- return this.handleMessage(JSON.parse(message.data));
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
- async activate() {
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
- async activateCameraSpeaker() {
63
- await (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
64
- this.sendSessionMessage('camera_options', {
65
- stealth_mode: false,
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;
@@ -23,10 +23,13 @@ export declare class StreamingSession extends Subscribed {
23
23
  private readonly returnAudioSplitter;
24
24
  constructor(camera: RingCamera, connection: RingEdgeConnection | WebrtcConnection);
25
25
  private bindToConnection;
26
- activated: boolean;
27
- activate(): Promise<void>;
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;
28
31
  cameraSpeakerActivated: boolean;
29
- activateCameraSpeaker(): Promise<void>;
32
+ activateCameraSpeaker(): void;
30
33
  reservePort(bufferPorts?: number): Promise<number>;
31
34
  get isUsingOpus(): Promise<boolean>;
32
35
  startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
@@ -37,5 +40,6 @@ export declare class StreamingSession extends Subscribed {
37
40
  private callEnded;
38
41
  stop(): void;
39
42
  sendAudioPacket(rtp: RtpPacket): void;
43
+ requestKeyFrame(): void;
40
44
  }
41
45
  export {};
@@ -28,7 +28,6 @@ class StreamingSession extends subscribed_1.Subscribed {
28
28
  this.audioSplitter = new camera_utils_1.RtpSplitter();
29
29
  this.videoSplitter = new camera_utils_1.RtpSplitter();
30
30
  this.returnAudioSplitter = new camera_utils_1.RtpSplitter();
31
- this.activated = false;
32
31
  this.cameraSpeakerActivated = false;
33
32
  this.hasEnded = false;
34
33
  this.bindToConnection(connection);
@@ -36,21 +35,21 @@ class StreamingSession extends subscribed_1.Subscribed {
36
35
  bindToConnection(connection) {
37
36
  this.addSubscriptions(connection.onAudioRtp.subscribe(this.onAudioRtp), connection.onVideoRtp.subscribe(this.onVideoRtp), connection.onCallAnswered.subscribe((sdp) => {
38
37
  this.onUsingOpus.next(sdp.toLocaleLowerCase().includes(' opus/'));
39
- }));
38
+ }), connection.onCallEnded.subscribe(() => this.callEnded()));
40
39
  }
41
- async activate() {
42
- if (this.activated || this.hasEnded) {
43
- return;
44
- }
45
- this.activated = true;
46
- await this.connection.activate();
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();
47
46
  }
48
- async activateCameraSpeaker() {
47
+ activateCameraSpeaker() {
49
48
  if (this.cameraSpeakerActivated || this.hasEnded) {
50
49
  return;
51
50
  }
52
51
  this.cameraSpeakerActivated = true;
53
- await this.connection.activateCameraSpeaker();
52
+ this.connection.activateCameraSpeaker();
54
53
  }
55
54
  async reservePort(bufferPorts = 0) {
56
55
  const ports = await (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
@@ -63,7 +62,15 @@ class StreamingSession extends subscribed_1.Subscribed {
63
62
  if (this.hasEnded) {
64
63
  return;
65
64
  }
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 = [
65
+ const videoPort = await this.reservePort(1), audioPort = await this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, ringSdp = await Promise.race([
66
+ (0, rxjs_1.firstValueFrom)(this.connection.onCallAnswered),
67
+ (0, rxjs_1.firstValueFrom)(this.onCallEnded),
68
+ ]);
69
+ if (!ringSdp) {
70
+ (0, util_1.logDebug)('Call ended before answered');
71
+ return;
72
+ }
73
+ const usingOpus = await this.isUsingOpus, ffmpegInputArguments = [
67
74
  '-hide_banner',
68
75
  '-protocol_whitelist',
69
76
  'pipe,udp,rtp,file,crypto',
@@ -104,10 +111,10 @@ class StreamingSession extends subscribed_1.Subscribed {
104
111
  }))
105
112
  .subscribe());
106
113
  }
107
- this.onCallEnded.subscribe(() => ff.stop());
114
+ this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
108
115
  ff.writeStdin(inputSdp);
109
- // Activate the stream now that ffmpeg is ready to receive
110
- await this.activate();
116
+ // Request a key frame now that ffmpeg is ready to receive
117
+ this.requestKeyFrame();
111
118
  }
112
119
  async transcodeReturnAudio(ffmpegOptions) {
113
120
  if (this.hasEnded) {
@@ -143,7 +150,7 @@ class StreamingSession extends subscribed_1.Subscribed {
143
150
  info: util_1.logDebug,
144
151
  },
145
152
  });
146
- this.onCallEnded.subscribe(() => ff.stop());
153
+ this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
147
154
  }
148
155
  callEnded() {
149
156
  if (this.hasEnded) {
@@ -166,5 +173,8 @@ class StreamingSession extends subscribed_1.Subscribed {
166
173
  }
167
174
  this.connection.sendAudioPacket(rtp);
168
175
  }
176
+ requestKeyFrame() {
177
+ this.connection.requestKeyFrame();
178
+ }
169
179
  }
170
180
  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.sendSessionMessage('stream_options', {
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.2-beta.1",
3
+ "version": "11.0.4",
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.0",
59
+ "@homebridge/camera-utils": "^2.1.1",
60
60
  "@homebridge/plugin-ui-utils": "^0.0.19",
61
- "werift": "^0.15.1",
61
+ "werift": "0.15.4",
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.6.0"
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.34",
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.25.0",
80
- "@typescript-eslint/parser": "5.25.0",
81
- "concurrently": "^7.2.0",
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.39",
85
- "eslint": "8.15.0",
84
+ "esbuild": "^0.14.42",
85
+ "eslint": "8.16.0",
86
86
  "eslint-config-prettier": "8.5.0",
87
- "eslint-plugin-jest": "26.2.2",
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.2",
97
- "typescript": "4.6.4"
96
+ "ts-jest": "28.0.3",
97
+ "typescript": "4.7.2"
98
98
  },
99
99
  "engines": {
100
100
  "node": "^16",