homebridge-nest-accfactory 0.2.11 → 0.3.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +14 -7
  3. package/config.schema.json +118 -0
  4. package/dist/HomeKitDevice.js +194 -77
  5. package/dist/HomeKitHistory.js +1 -1
  6. package/dist/config.js +207 -0
  7. package/dist/devices.js +113 -0
  8. package/dist/index.js +2 -1
  9. package/dist/nexustalk.js +19 -21
  10. package/dist/{camera.js → plugins/camera.js} +212 -239
  11. package/dist/{doorbell.js → plugins/doorbell.js} +32 -30
  12. package/dist/plugins/floodlight.js +91 -0
  13. package/dist/plugins/heatlink.js +17 -0
  14. package/dist/{protect.js → plugins/protect.js} +24 -41
  15. package/dist/{tempsensor.js → plugins/tempsensor.js} +13 -17
  16. package/dist/{thermostat.js → plugins/thermostat.js} +424 -381
  17. package/dist/{weather.js → plugins/weather.js} +26 -60
  18. package/dist/protobuf/nest/services/apigateway.proto +31 -1
  19. package/dist/protobuf/nest/trait/firmware.proto +207 -89
  20. package/dist/protobuf/nest/trait/hvac.proto +1052 -312
  21. package/dist/protobuf/nest/trait/located.proto +51 -8
  22. package/dist/protobuf/nest/trait/network.proto +366 -36
  23. package/dist/protobuf/nest/trait/occupancy.proto +145 -17
  24. package/dist/protobuf/nest/trait/product/protect.proto +57 -43
  25. package/dist/protobuf/nest/trait/resourcedirectory.proto +8 -0
  26. package/dist/protobuf/nest/trait/sensor.proto +7 -1
  27. package/dist/protobuf/nest/trait/service.proto +3 -1
  28. package/dist/protobuf/nest/trait/structure.proto +60 -14
  29. package/dist/protobuf/nest/trait/ui.proto +41 -1
  30. package/dist/protobuf/nest/trait/user.proto +6 -1
  31. package/dist/protobuf/nest/trait/voiceassistant.proto +2 -1
  32. package/dist/protobuf/nestlabs/eventingapi/v1.proto +20 -1
  33. package/dist/protobuf/root.proto +1 -0
  34. package/dist/protobuf/wdl.proto +18 -2
  35. package/dist/protobuf/weave/common.proto +2 -1
  36. package/dist/protobuf/weave/trait/heartbeat.proto +41 -1
  37. package/dist/protobuf/weave/trait/power.proto +1 -0
  38. package/dist/protobuf/weave/trait/security.proto +10 -1
  39. package/dist/streamer.js +68 -72
  40. package/dist/system.js +1208 -1245
  41. package/dist/webrtc.js +28 -23
  42. package/package.json +12 -12
  43. package/dist/floodlight.js +0 -97
@@ -171,8 +171,9 @@ message WdlCommandOptions {
171
171
  bool extendable = 5;
172
172
  uint32 reservedTagMin = 6;
173
173
  uint32 reservedTagMax = 7;
174
- string pb_extends = 8;
174
+ string extends = 8;
175
175
  string completionEvent = 11;
176
+ WdlPermission permission = 20;
176
177
  }
177
178
 
178
179
  message WdlEventOptions {
@@ -181,7 +182,7 @@ message WdlEventOptions {
181
182
  bool extendable = 5;
182
183
  uint32 reservedTagMin = 6;
183
184
  uint32 reservedTagMax = 7;
184
- string pb_extends = 8;
185
+ string extends = 8;
185
186
  EventImportance eventImportance = 9;
186
187
  }
187
188
 
@@ -373,6 +374,8 @@ message WdlTraitOptions {
373
374
  uint32 reservedCommandTagMax = 11;
374
375
  uint32 reservedEventTagMin = 12;
375
376
  uint32 reservedEventTagMax = 13;
377
+ WdlPermission permissionRead = 20;
378
+ WdlPermission permissionUpdate = 21;
376
379
  }
377
380
 
378
381
  message WdlTypespaceOptions {
@@ -440,6 +443,19 @@ message WdlServiceOptions {
440
443
  WdlResourceOptions resource = 3;
441
444
  }
442
445
 
446
+ message WdlFileOptions {
447
+ }
448
+
449
+ message WdlPermission {
450
+ string permission = 1;
451
+ repeated Override permissionOverrides = 2;
452
+
453
+ message Override {
454
+ string resourceType = 1;
455
+ string permission = 2;
456
+ }
457
+ }
458
+
443
459
  message CaspianOptions {
444
460
  bool ignore = 1;
445
461
  }
@@ -71,6 +71,7 @@ enum QuantityType {
71
71
  PPM = 48;
72
72
  PIRMEASUREMENT = 256;
73
73
  NORMALIZED = 257;
74
+ ANGLE = 259;
74
75
  }
75
76
 
76
77
  message ResourceId {
@@ -141,4 +142,4 @@ message TimeOfDay {
141
142
  uint32 hour = 5;
142
143
  uint32 minute = 6;
143
144
  uint32 second = 7;
144
- }
145
+ }
@@ -17,6 +17,15 @@ message LivenessTrait {
17
17
  google.protobuf.BoolValue commandRequestUnresponsiveness = 8;
18
18
  google.protobuf.Timestamp commandRequestUnresponsivenessTimeStatusChanged = 9;
19
19
  google.protobuf.StringValue publisherName = 10;
20
+ google.protobuf.BoolValue tunnelDisconnected = 11;
21
+ google.protobuf.Timestamp tunnelDisconnectedTimeStatusChanged = 12;
22
+ google.protobuf.Timestamp lastContactedTime = 13;
23
+ google.protobuf.Timestamp lastWdmHeartbeatTime = 14;
24
+ google.protobuf.Timestamp tunnelClosedTime = 15;
25
+ repeated DeviceFrontendType frontend = 16;
26
+ google.protobuf.BoolValue disconnected = 17;
27
+ google.protobuf.Timestamp disconnectedTimeStatusChanged = 18;
28
+ google.protobuf.Timestamp connectionClosedTime = 19;
20
29
 
21
30
  enum LivenessDeviceStatus {
22
31
  LIVENESS_DEVICE_STATUS_UNSPECIFIED = 0;
@@ -28,11 +37,42 @@ message LivenessTrait {
28
37
  LIVENESS_DEVICE_STATUS_SCHEDULED_DOWN = 6;
29
38
  }
30
39
 
40
+ enum DeviceFrontendType {
41
+ DEVICE_FRONTEND_TYPE_UNSPECIFIED = 0;
42
+ DEVICE_FRONTEND_TYPE_LYCRA = 1;
43
+ DEVICE_FRONTEND_TYPE_WEAVE_FE_1 = 2;
44
+ }
45
+
31
46
  message LivenessChangeEvent {
32
47
  LivenessDeviceStatus status = 1;
33
48
  LivenessDeviceStatus heartbeatStatus = 2;
34
49
  google.protobuf.BoolValue notifyRequestUnresponsiveness = 3;
35
50
  google.protobuf.BoolValue commandRequestUnresponsiveness = 4;
36
51
  LivenessDeviceStatus prevStatus = 5;
52
+ google.protobuf.BoolValue tunnelDisconnected = 6;
53
+ google.protobuf.Timestamp lastContactedTime = 7;
54
+ google.protobuf.Timestamp lastWdmHeartbeatTime = 8;
55
+ google.protobuf.Timestamp tunnelClosedTime = 9;
56
+ google.protobuf.Timestamp timeStatusChanged = 10;
57
+ google.protobuf.Timestamp timePrevStatusChanged = 11;
58
+ repeated DeviceFrontendType frontend = 12;
59
+ google.protobuf.BoolValue disconnected = 13;
60
+ google.protobuf.Timestamp disconnectedTimeStatusChanged = 14;
61
+ google.protobuf.Timestamp connectionClosedTime = 15;
62
+ google.protobuf.StringValue traitLabel = 16;
63
+ }
64
+
65
+ message LivenessConnectedSignalEvent {
66
+ string connectionId = 1;
67
+ DeviceFrontendType frontend = 2;
68
+ google.protobuf.Timestamp occurrenceTime = 3;
69
+ google.protobuf.StringValue connectionTag = 4;
70
+ }
71
+
72
+ message LivenessDisconnectedSignalEvent {
73
+ string connectionId = 1;
74
+ DeviceFrontendType frontend = 2;
75
+ google.protobuf.Timestamp occurrenceTime = 3;
76
+ google.protobuf.StringValue connectionTag = 4;
37
77
  }
38
- }
78
+ }
@@ -45,6 +45,7 @@ message PowerSourceCapabilitiesTrait {
45
45
  enum PowerSourceType {
46
46
  POWER_SOURCE_TYPE_UNSPECIFIED = 0;
47
47
  POWER_SOURCE_TYPE_BATTERY = 1;
48
+ POWER_SOURCE_TYPE_RECHARGEABLE_BATTERY = 2;
48
49
  }
49
50
 
50
51
  enum PowerSourceCurrentType {
@@ -249,12 +249,21 @@ message TamperTrait {
249
249
  TAMPER_STATE_UNKNOWN = 3;
250
250
  }
251
251
 
252
+ enum TamperStateChangeReason {
253
+ TAMPER_STATE_CHANGE_REASON_UNSPECIFIED = 0;
254
+ TAMPER_STATE_CHANGE_REASON_CLEAR_SECURE = 1;
255
+ TAMPER_STATE_CHANGE_REASON_CLEAR_DISARM = 2;
256
+ TAMPER_STATE_CHANGE_REASON_CLEAR_SNOOZE = 3;
257
+ }
258
+
252
259
  message ResetTamperRequest {
253
260
  }
254
261
 
255
262
  message TamperStateChangeEvent {
256
263
  TamperState tamperState = 1;
257
264
  TamperState priorTamperState = 2;
265
+ TamperStateChangeReason reason = 3;
266
+ google.protobuf.Timestamp tamperStateChangeTime = 4;
258
267
  }
259
268
  }
260
269
 
@@ -340,4 +349,4 @@ message UserPincodesCapabilitiesTrait {
340
349
  uint32 minPincodeLength = 1;
341
350
  uint32 maxPincodeLength = 2;
342
351
  uint32 maxPincodesSupported = 3;
343
- }
352
+ }
package/dist/streamer.js CHANGED
@@ -17,7 +17,7 @@
17
17
  //
18
18
  // blankAudio - Buffer containing a blank audio segment for the type of audio being used
19
19
  //
20
- // Code version 2025/03/25
20
+ // Code version 2025.06.12
21
21
  // Mark Hulskamp
22
22
  'use strict';
23
23
 
@@ -29,16 +29,28 @@ import path from 'node:path';
29
29
  import { fileURLToPath } from 'node:url';
30
30
 
31
31
  // Define constants
32
- const CAMERAOFFLINEH264FILE = 'Nest_camera_offline.h264'; // Camera offline H264 frame file
33
- const CAMERAOFFH264FILE = 'Nest_camera_off.h264'; // Camera off H264 frame file
34
- const CAMERATRANSFERJPGFILE = 'Nest_camera_transfer.jpg'; // Camera transferring H264 frame file
32
+ const CAMERARESOURCE = {
33
+ offline: 'Nest_camera_offline.h264',
34
+ off: 'Nest_camera_off.h264',
35
+ transfer: 'Nest_camera_transfer.h264',
36
+ };
35
37
  const TALKBACKAUDIOTIMEOUT = 1000;
36
38
  const H264NALSTARTCODE = Buffer.from([0x00, 0x00, 0x00, 0x01]);
37
-
39
+ const MAXBUFFERAGE = 5000; // Keep last 5s of media in buffer
40
+ const STREAMFRAMEINTERVAL = 1000 / 30; // 30fps approx
41
+ const RESOURCEPATH = './res';
38
42
  const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
43
+ const LOGLEVELS = {
44
+ info: 'info',
45
+ success: 'success',
46
+ warn: 'warn',
47
+ error: 'error',
48
+ debug: 'debug',
49
+ };
39
50
 
40
51
  // Streamer object
41
52
  export default class Streamer {
53
+ log = undefined; // Logging function object
42
54
  videoEnabled = undefined; // Video stream on camera enabled or not
43
55
  audioEnabled = undefined; // Audio from camera enabled or not
44
56
  online = undefined; // Camera online or not
@@ -60,13 +72,7 @@ export default class Streamer {
60
72
 
61
73
  constructor(deviceData, options) {
62
74
  // Setup logger object if passed as option
63
- if (
64
- typeof options?.log?.info === 'function' &&
65
- typeof options?.log?.success === 'function' &&
66
- typeof options?.log?.warn === 'function' &&
67
- typeof options?.log?.error === 'function' &&
68
- typeof options?.log?.debug === 'function'
69
- ) {
75
+ if (Object.keys(LOGLEVELS).every((fn) => typeof options?.log?.[fn] === 'function')) {
70
76
  this.log = options.log;
71
77
  }
72
78
 
@@ -77,39 +83,30 @@ export default class Streamer {
77
83
  this.audioEnabled = deviceData?.audio_enabled === true;
78
84
  this.uuid = deviceData?.nest_google_uuid;
79
85
 
80
- // Setup location for *.h264 frame files. This can be overriden by a passed in option
81
- let resourcePath = path.resolve(__dirname + '/res'); // Default location for *.h264 files
82
- if (
83
- typeof options?.resourcePath === 'string' &&
84
- options.resourcePath !== '' &&
85
- fs.existsSync(path.resolve(options.resourcePath)) === true
86
- ) {
87
- resourcePath = path.resolve(options.resourcePath);
88
- }
89
-
90
- // load buffer for camera offline in .h264 frame
91
- if (fs.existsSync(path.resolve(resourcePath + '/' + CAMERAOFFLINEH264FILE)) === true) {
92
- this.#cameraOfflineFrame = fs.readFileSync(path.resolve(resourcePath + '/' + CAMERAOFFLINEH264FILE));
93
- }
94
-
95
- // load buffer for camera stream off in .h264 frame
96
- if (fs.existsSync(path.resolve(resourcePath + '/' + CAMERAOFFH264FILE)) === true) {
97
- this.#cameraVideoOffFrame = fs.readFileSync(path.resolve(resourcePath + '/' + CAMERAOFFH264FILE));
98
- }
86
+ // Load support video frame files as required
87
+ const loadFrameIfExists = (filename, label) => {
88
+ let buffer = undefined;
89
+ let file = path.resolve(__dirname, RESOURCEPATH, filename);
90
+ if (fs.existsSync(file) === true) {
91
+ buffer = fs.readFileSync(file);
92
+ } else {
93
+ this.log?.warn?.('Failed to load %s video resource for "%s"', label, deviceData.description);
94
+ }
95
+ return buffer;
96
+ };
99
97
 
100
- // load buffer for camera transferring in .h264 frame
101
- if (fs.existsSync(path.resolve(resourcePath + '/' + CAMERATRANSFERJPGFILE)) === true) {
102
- this.#cameraTransferringFrame = fs.readFileSync(path.resolve(resourcePath + '/' + CAMERATRANSFERJPGFILE));
103
- }
98
+ this.#cameraOfflineFrame = loadFrameIfExists(CAMERARESOURCE.offline, 'offline');
99
+ this.#cameraVideoOffFrame = loadFrameIfExists(CAMERARESOURCE.off, 'video off');
100
+ this.#cameraTransferringFrame = loadFrameIfExists(CAMERARESOURCE.transfer, 'transferring');
104
101
 
105
102
  // Start a non-blocking loop for output to the various streams which connect to our streamer object
106
103
  // This process will also handle the rolling-buffer size we require
107
104
  // Record streams will always start from the beginning of the buffer (tail)
108
105
  // Live streams will always start from the end of the buffer (head)
109
- let lastTimeVideo = Date.now();
106
+ let lastVideoFrameTime = Date.now();
110
107
  this.#outputTimer = setInterval(() => {
111
108
  let dateNow = Date.now();
112
- let outputVideoFrame = dateNow > lastTimeVideo + 90000 / 30; // 30fps
109
+ let outputVideoFrame = dateNow - lastVideoFrameTime >= STREAMFRAMEINTERVAL;
113
110
  Object.values(this.#outputs).forEach((output) => {
114
111
  // Monitor for camera going offline, video enabled/disabled or being transferred between Nest/Google Home
115
112
  // We'll insert the appropriate video frame into the stream
@@ -117,26 +114,28 @@ export default class Streamer {
117
114
  // Camera is offline so feed in our custom h264 frame and AAC silence
118
115
  output.buffer.push({ time: dateNow, type: 'video', data: this.#cameraOfflineFrame });
119
116
  output.buffer.push({ time: dateNow, type: 'audio', data: this.blankAudio });
120
- lastTimeVideo = dateNow;
117
+ lastVideoFrameTime = dateNow;
121
118
  }
122
119
  if (this.online === true && this.videoEnabled === false && this.#cameraVideoOffFrame !== undefined && outputVideoFrame === true) {
123
120
  // Camera video is turned off so feed in our custom h264 frame and AAC silence
124
121
  output.buffer.push({ time: dateNow, type: 'video', data: this.#cameraVideoOffFrame });
125
122
  output.buffer.push({ time: dateNow, type: 'audio', data: this.blankAudio });
126
- lastTimeVideo = dateNow;
123
+ lastVideoFrameTime = dateNow;
127
124
  }
128
125
  if (this.migrating === true && this.#cameraTransferringFrame !== undefined && outputVideoFrame === true) {
129
126
  // Camera video is turned off so feed in our custom h264 frame and AAC silence
130
127
  output.buffer.push({ time: dateNow, type: 'video', data: this.#cameraTransferringFrame });
131
128
  output.buffer.push({ time: dateNow, type: 'audio', data: this.blankAudio });
132
- lastTimeVideo = dateNow;
129
+ lastVideoFrameTime = dateNow;
133
130
  }
134
131
 
135
132
  // Keep our 'main' rolling buffer under a certain size
136
133
  // Live/record buffers will always reduce in length in the next section
137
- // <---- maybe make this time based x time since first packet in buffer?
138
- if (output.type === 'buffer' && output.buffer.length > 1250) {
139
- output.buffer.shift();
134
+ if (output.type === 'buffer') {
135
+ let cutoffTime = dateNow - MAXBUFFERAGE;
136
+ while (output.buffer.length > 0 && output.buffer[0].time < cutoffTime) {
137
+ output.buffer.shift();
138
+ }
140
139
  }
141
140
 
142
141
  // Output the packet data to any 'live' or 'recording' streams
@@ -151,7 +150,7 @@ export default class Streamer {
151
150
  }
152
151
  }
153
152
  });
154
- }, 0);
153
+ }, 10); // Every 10ms, rather than "next tick"
155
154
  }
156
155
 
157
156
  // Class functions
@@ -163,7 +162,7 @@ export default class Streamer {
163
162
  if (this.#outputs?.buffer === undefined) {
164
163
  // No active buffer session, start connection to streamer
165
164
  if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
166
- this?.log?.debug && this.log.debug('Started buffering for uuid "%s"', this.uuid);
165
+ this?.log?.debug?.('Started buffering for uuid "%s"', this.uuid);
167
166
  this.connect();
168
167
  }
169
168
 
@@ -209,10 +208,8 @@ export default class Streamer {
209
208
  });
210
209
  }
211
210
 
212
- if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
213
- // We do not have an active connection, so startup connection
214
- this.connect();
215
- }
211
+ // We do not have an active connection, so startup connection
212
+ this.#doConnect();
216
213
 
217
214
  // Add video/audio streams for our output loop to handle outputting
218
215
  this.#outputs[sessionID] = {
@@ -224,13 +221,12 @@ export default class Streamer {
224
221
  };
225
222
 
226
223
  // finally, we've started live stream
227
- this?.log?.debug &&
228
- this.log.debug(
229
- 'Started live stream from uuid "%s" %s "%s"',
230
- this.uuid,
231
- talkbackStream !== null && typeof talkbackStream === 'object' ? 'with two-way audio and sesssion id of' : 'and sesssion id of',
232
- sessionID,
233
- );
224
+ this?.log?.debug?.(
225
+ 'Started live stream from uuid "%s" %s "%s"',
226
+ this.uuid,
227
+ talkbackStream !== null && typeof talkbackStream === 'object' ? 'with two-way audio and session id of' : 'and session id of',
228
+ sessionID,
229
+ );
234
230
  }
235
231
 
236
232
  startRecordStream(sessionID, videoStream, audioStream) {
@@ -247,10 +243,8 @@ export default class Streamer {
247
243
  });
248
244
  }
249
245
 
250
- if (this.connected === undefined && typeof this.connect === 'function' && this.online === true && this.videoEnabled === true) {
251
- // We do not have an active connection, so startup connection
252
- this.connect();
253
- }
246
+ // We do not have an active connection, so startup connection
247
+ this.#doConnect();
254
248
 
255
249
  // Add video/audio streams for our output loop to handle outputting
256
250
  this.#outputs[sessionID] = {
@@ -262,13 +256,13 @@ export default class Streamer {
262
256
  };
263
257
 
264
258
  // Finally we've started the recording stream
265
- this?.log?.debug && this.log.debug('Started recording stream from uuid "%s" with sesison id of "%s"', this.uuid, sessionID);
259
+ this?.log?.debug?.('Started recording stream from uuid "%s" with session id of "%s"', this.uuid, sessionID);
266
260
  }
267
261
 
268
262
  stopRecordStream(sessionID) {
269
263
  // Request to stop a recording stream
270
264
  if (this.#outputs?.[sessionID] !== undefined) {
271
- this?.log?.debug && this.log.debug('Stopped recording stream from uuid "%s"', this.uuid);
265
+ this?.log?.debug?.('Stopped recording stream from uuid "%s"', this.uuid);
272
266
  delete this.#outputs[sessionID];
273
267
  }
274
268
 
@@ -281,7 +275,7 @@ export default class Streamer {
281
275
  stopLiveStream(sessionID) {
282
276
  // Request to stop an active live stream
283
277
  if (this.#outputs?.[sessionID] !== undefined) {
284
- this?.log?.debug && this.log.debug('Stopped live stream from uuid "%s"', this.uuid);
278
+ this?.log?.debug?.('Stopped live stream from uuid "%s"', this.uuid);
285
279
  delete this.#outputs[sessionID];
286
280
  }
287
281
 
@@ -293,7 +287,7 @@ export default class Streamer {
293
287
 
294
288
  stopBuffering() {
295
289
  if (this.#outputs?.buffer !== undefined) {
296
- this?.log?.debug && this.log.debug('Stopped buffering from uuid "%s"', this.uuid);
290
+ this?.log?.debug?.('Stopped buffering from uuid "%s"', this.uuid);
297
291
  delete this.#outputs.buffer;
298
292
  }
299
293
 
@@ -305,7 +299,7 @@ export default class Streamer {
305
299
 
306
300
  stopEverything() {
307
301
  if (this.haveOutputs() === true) {
308
- this?.log?.debug && this.log.debug('Stopped buffering, live and recording from uuid "%s"', this.uuid);
302
+ this?.log?.debug?.('Stopped buffering, live and recording from uuid "%s"', this.uuid);
309
303
  this.#outputs = {}; // No more outputs
310
304
  if (typeof this.close === 'function') {
311
305
  this.close();
@@ -328,9 +322,7 @@ export default class Streamer {
328
322
  if (typeof this.close === 'function') {
329
323
  this.close();
330
324
  }
331
- if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
332
- this.connect();
333
- }
325
+ this.#doConnect();
334
326
  }
335
327
  }
336
328
 
@@ -339,7 +331,7 @@ export default class Streamer {
339
331
  this.videoEnabled !== deviceData.streaming_enabled ||
340
332
  this.audioEnabled !== deviceData?.audio_enabled
341
333
  ) {
342
- // Online status or streaming status has changed has changed
334
+ // Online status or streaming status has changed
343
335
  this.online = deviceData?.online === true;
344
336
  this.videoEnabled = deviceData?.streaming_enabled === true;
345
337
  this.audioEnabled = deviceData?.audio_enabled === true;
@@ -349,9 +341,7 @@ export default class Streamer {
349
341
  if ((this.online === false || this.videoEnabled === false || this.audioEnabled === false) && typeof this.close === 'function') {
350
342
  this.close(); // as offline or streaming not enabled, close streamer
351
343
  }
352
- if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
353
- this.connect(); // Connect for stream
354
- }
344
+ this.#doConnect();
355
345
  }
356
346
  }
357
347
  }
@@ -378,4 +368,10 @@ export default class Streamer {
378
368
  haveOutputs() {
379
369
  return Object.keys(this.#outputs).length > 0;
380
370
  }
371
+
372
+ #doConnect() {
373
+ if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
374
+ this.connect();
375
+ }
376
+ }
381
377
  }