homebridge-nest-accfactory 0.0.4-a
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 +27 -0
- package/LICENSE +176 -0
- package/README.md +121 -0
- package/config.schema.json +107 -0
- package/dist/HomeKitDevice.js +441 -0
- package/dist/HomeKitHistory.js +2835 -0
- package/dist/camera.js +1276 -0
- package/dist/doorbell.js +122 -0
- package/dist/index.js +35 -0
- package/dist/nexustalk.js +741 -0
- package/dist/protect.js +240 -0
- package/dist/protobuf/google/rpc/status.proto +91 -0
- package/dist/protobuf/google/rpc/stream_body.proto +26 -0
- package/dist/protobuf/google/trait/product/camera.proto +53 -0
- package/dist/protobuf/googlehome/foyer.proto +208 -0
- package/dist/protobuf/nest/messages.proto +8 -0
- package/dist/protobuf/nest/services/apigateway.proto +107 -0
- package/dist/protobuf/nest/trait/audio.proto +7 -0
- package/dist/protobuf/nest/trait/cellular.proto +313 -0
- package/dist/protobuf/nest/trait/debug.proto +37 -0
- package/dist/protobuf/nest/trait/detector.proto +41 -0
- package/dist/protobuf/nest/trait/diagnostics.proto +87 -0
- package/dist/protobuf/nest/trait/firmware.proto +221 -0
- package/dist/protobuf/nest/trait/guest.proto +105 -0
- package/dist/protobuf/nest/trait/history.proto +345 -0
- package/dist/protobuf/nest/trait/humanlibrary.proto +19 -0
- package/dist/protobuf/nest/trait/hvac.proto +1353 -0
- package/dist/protobuf/nest/trait/input.proto +29 -0
- package/dist/protobuf/nest/trait/lighting.proto +61 -0
- package/dist/protobuf/nest/trait/located.proto +193 -0
- package/dist/protobuf/nest/trait/media.proto +68 -0
- package/dist/protobuf/nest/trait/network.proto +352 -0
- package/dist/protobuf/nest/trait/occupancy.proto +373 -0
- package/dist/protobuf/nest/trait/olive.proto +15 -0
- package/dist/protobuf/nest/trait/pairing.proto +85 -0
- package/dist/protobuf/nest/trait/product/camera.proto +283 -0
- package/dist/protobuf/nest/trait/product/detect.proto +67 -0
- package/dist/protobuf/nest/trait/product/doorbell.proto +18 -0
- package/dist/protobuf/nest/trait/product/guard.proto +59 -0
- package/dist/protobuf/nest/trait/product/protect.proto +344 -0
- package/dist/protobuf/nest/trait/promonitoring.proto +14 -0
- package/dist/protobuf/nest/trait/resourcedirectory.proto +32 -0
- package/dist/protobuf/nest/trait/safety.proto +119 -0
- package/dist/protobuf/nest/trait/security.proto +516 -0
- package/dist/protobuf/nest/trait/selftest.proto +78 -0
- package/dist/protobuf/nest/trait/sensor.proto +291 -0
- package/dist/protobuf/nest/trait/service.proto +46 -0
- package/dist/protobuf/nest/trait/structure.proto +85 -0
- package/dist/protobuf/nest/trait/system.proto +51 -0
- package/dist/protobuf/nest/trait/test.proto +15 -0
- package/dist/protobuf/nest/trait/ui.proto +65 -0
- package/dist/protobuf/nest/trait/user.proto +98 -0
- package/dist/protobuf/nest/trait/voiceassistant.proto +30 -0
- package/dist/protobuf/nestlabs/eventingapi/v1.proto +83 -0
- package/dist/protobuf/nestlabs/gateway/v1.proto +273 -0
- package/dist/protobuf/nestlabs/gateway/v2.proto +96 -0
- package/dist/protobuf/nestlabs/history/v1.proto +73 -0
- package/dist/protobuf/root.proto +64 -0
- package/dist/protobuf/wdl-event-importance.proto +11 -0
- package/dist/protobuf/wdl.proto +450 -0
- package/dist/protobuf/weave/common.proto +144 -0
- package/dist/protobuf/weave/trait/audio.proto +12 -0
- package/dist/protobuf/weave/trait/auth.proto +22 -0
- package/dist/protobuf/weave/trait/description.proto +32 -0
- package/dist/protobuf/weave/trait/heartbeat.proto +38 -0
- package/dist/protobuf/weave/trait/locale.proto +20 -0
- package/dist/protobuf/weave/trait/network.proto +24 -0
- package/dist/protobuf/weave/trait/pairing.proto +8 -0
- package/dist/protobuf/weave/trait/peerdevices.proto +18 -0
- package/dist/protobuf/weave/trait/power.proto +86 -0
- package/dist/protobuf/weave/trait/schedule.proto +76 -0
- package/dist/protobuf/weave/trait/security.proto +343 -0
- package/dist/protobuf/weave/trait/telemetry/tunnel.proto +37 -0
- package/dist/protobuf/weave/trait/time.proto +16 -0
- package/dist/res/Nest_camera_connecting.h264 +0 -0
- package/dist/res/Nest_camera_connecting.jpg +0 -0
- package/dist/res/Nest_camera_off.h264 +0 -0
- package/dist/res/Nest_camera_off.jpg +0 -0
- package/dist/res/Nest_camera_offline.h264 +0 -0
- package/dist/res/Nest_camera_offline.jpg +0 -0
- package/dist/res/Nest_camera_transfer.jpg +0 -0
- package/dist/streamer.js +344 -0
- package/dist/system.js +3112 -0
- package/dist/tempsensor.js +99 -0
- package/dist/thermostat.js +1026 -0
- package/dist/weather.js +205 -0
- package/dist/webrtc.js +55 -0
- package/package.json +66 -0
package/dist/streamer.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// streamer
|
|
2
|
+
// Part of homebridge-nest-accfactory
|
|
3
|
+
//
|
|
4
|
+
// This is the base class for all Camera/Doorbell streaming
|
|
5
|
+
//
|
|
6
|
+
// Buffers a single audio/video stream which allows multiple HomeKit devices to connect to the single stream
|
|
7
|
+
// for live viewing and/or recording
|
|
8
|
+
//
|
|
9
|
+
// The following functions should be overriden in your class which extends this
|
|
10
|
+
//
|
|
11
|
+
// streamer.connect(host)
|
|
12
|
+
// streamer.close(stopStreamFirst)
|
|
13
|
+
// streamer.talkingAudio(talkingData)
|
|
14
|
+
// streamer.update(deviceData) <- call super after
|
|
15
|
+
//
|
|
16
|
+
// Code version 6/9/2024
|
|
17
|
+
// Mark Hulskamp
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
// Define nodejs module requirements
|
|
21
|
+
import { Buffer } from 'node:buffer';
|
|
22
|
+
import { setInterval, setTimeout, clearTimeout } from 'node:timers';
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
// Define constants
|
|
28
|
+
const CAMERAOFFLINEH264FILE = 'Nest_camera_offline.h264'; // Camera offline H264 frame file
|
|
29
|
+
const CAMERAOFFH264FILE = 'Nest_camera_off.h264'; // Camera off H264 frame file
|
|
30
|
+
|
|
31
|
+
const H264NALStartcode = Buffer.from([0x00, 0x00, 0x00, 0x01]);
|
|
32
|
+
const AACAudioSilence = Buffer.from([0x21, 0x10, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
|
33
|
+
|
|
34
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
|
|
35
|
+
|
|
36
|
+
// Streamer object
|
|
37
|
+
export default class Streamer {
|
|
38
|
+
cameraOfflineFrame = undefined;
|
|
39
|
+
cameraVideoOffFrame = undefined;
|
|
40
|
+
videoEnabled = undefined;
|
|
41
|
+
audioEnabled = undefined;
|
|
42
|
+
online = undefined;
|
|
43
|
+
host = ''; // Host to connect to or connected too
|
|
44
|
+
socket = null; // TCP socket object
|
|
45
|
+
|
|
46
|
+
// Internal data only for this class
|
|
47
|
+
#outputTimer = undefined; // Timer for non-blocking loop to stream output data
|
|
48
|
+
#outputs = {}; // Output streams ie: buffer, live, record
|
|
49
|
+
|
|
50
|
+
constructor(deviceData, options) {
|
|
51
|
+
// Setup logger object if passed as option
|
|
52
|
+
if (
|
|
53
|
+
typeof options?.log?.info === 'function' &&
|
|
54
|
+
typeof options?.log?.success === 'function' &&
|
|
55
|
+
typeof options?.log?.warn === 'function' &&
|
|
56
|
+
typeof options?.log?.error === 'function' &&
|
|
57
|
+
typeof options?.log?.debug === 'function'
|
|
58
|
+
) {
|
|
59
|
+
this.log = options.log;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Store data we need from the device data passed it
|
|
63
|
+
this.online = deviceData?.online === true;
|
|
64
|
+
this.videoEnabled = deviceData?.streaming_enabled === true;
|
|
65
|
+
this.audioEnabled = deviceData?.audio_enabled === true;
|
|
66
|
+
|
|
67
|
+
// Setup location for *.h264 frame files. This can be overriden by a passed in option
|
|
68
|
+
let resourcePath = path.resolve(__dirname + '/res'); // Default location for *.h264 files
|
|
69
|
+
if (
|
|
70
|
+
typeof options?.resourcePath === 'string' &&
|
|
71
|
+
options.resourcePath !== '' &&
|
|
72
|
+
fs.existsSync(path.resolve(options.resourcePath)) === true
|
|
73
|
+
) {
|
|
74
|
+
resourcePath = path.resolve(options.resourcePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// load buffer for camera offline image in .h264 frame
|
|
78
|
+
if (fs.existsSync(path.resolve(resourcePath + '/' + CAMERAOFFLINEH264FILE)) === true) {
|
|
79
|
+
this.cameraOfflineFrame = fs.readFileSync(path.resolve(resourcePath + '/' + CAMERAOFFLINEH264FILE));
|
|
80
|
+
// remove any H264 NALU from beginning of any video data. We do this as they are added later when output by our ffmpeg router
|
|
81
|
+
if (this.cameraOfflineFrame.indexOf(H264NALStartcode) === 0) {
|
|
82
|
+
this.cameraOfflineFrame = this.cameraOfflineFrame.subarray(H264NALStartcode.length);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// load buffer for camera stream off image in .h264 frame
|
|
87
|
+
if (fs.existsSync(path.resolve(resourcePath + '/' + CAMERAOFFH264FILE)) === true) {
|
|
88
|
+
this.cameraVideoOffFrame = fs.readFileSync(path.resolve(resourcePath + '/' + CAMERAOFFH264FILE));
|
|
89
|
+
// remove any H264 NALU from beginning of any video data. We do this as they are added later when output by our ffmpeg router
|
|
90
|
+
if (this.cameraVideoOffFrame.indexOf(H264NALStartcode) === 0) {
|
|
91
|
+
this.cameraVideoOffFrame = this.cameraVideoOffFrame.subarray(H264NALStartcode.length);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start a non-blocking loop for output to the various streams which connect to our streamer object
|
|
96
|
+
// This process will also handle the rolling-buffer size we require
|
|
97
|
+
// Record streams will always start from the beginning of the buffer (tail)
|
|
98
|
+
// Live streams will always start from the end of the buffer (head)
|
|
99
|
+
let lastTimeVideo = Date.now();
|
|
100
|
+
this.#outputTimer = setInterval(() => {
|
|
101
|
+
let dateNow = Date.now();
|
|
102
|
+
let outputVideoFrame = dateNow > lastTimeVideo + 90000 / 30; // 30 or 15 fps?
|
|
103
|
+
Object.values(this.#outputs).forEach((output) => {
|
|
104
|
+
// Monitor for camera going offline and/or video enabled/disabled
|
|
105
|
+
// We'll insert the appropriate video frame into the stream
|
|
106
|
+
if (this.online === false && this.cameraOfflineFrame !== undefined && outputVideoFrame === true) {
|
|
107
|
+
// Camera is offline so feed in our custom h264 frame and AAC silence
|
|
108
|
+
output.buffer.push({ type: 'video', time: dateNow, data: this.cameraOfflineFrame });
|
|
109
|
+
output.buffer.push({ type: 'audio', time: dateNow, data: AACAudioSilence });
|
|
110
|
+
lastTimeVideo = dateNow;
|
|
111
|
+
}
|
|
112
|
+
if (this.online === true && this.videoEnabled === false && this.cameraVideoOffFrame !== undefined && outputVideoFrame === true) {
|
|
113
|
+
// Camera video is turned off so feed in our custom h264 frame and AAC silence
|
|
114
|
+
output.buffer.push({ type: 'video', time: dateNow, data: this.cameraVideoOffFrame });
|
|
115
|
+
output.buffer.push({ type: 'audio', time: dateNow, data: AACAudioSilence });
|
|
116
|
+
lastTimeVideo = dateNow;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Keep our 'main' rolling buffer under a certain size
|
|
120
|
+
// Live/record buffers will always reduce in length in the next section
|
|
121
|
+
// <---- maybe make this time based x time since first packet in buffer?
|
|
122
|
+
if (output.type === 'buffer' && output.buffer.length > 1250) {
|
|
123
|
+
output.buffer.shift();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Output the packet data to any streams 'live' or 'recording' streams
|
|
127
|
+
if (output.type === 'live' || output.type === 'record') {
|
|
128
|
+
let packet = output.buffer.shift();
|
|
129
|
+
if (packet?.type === 'video' && typeof output?.video?.write === 'function') {
|
|
130
|
+
// H264 NAL Units '0001' are required to be added to beginning of any video data we output
|
|
131
|
+
// If this is missing, add on beginning of data packet
|
|
132
|
+
if (packet.data.indexOf(H264NALStartcode) !== 0) {
|
|
133
|
+
packet.data = Buffer.concat([H264NALStartcode, packet.data]);
|
|
134
|
+
}
|
|
135
|
+
output.video.write(packet.data);
|
|
136
|
+
}
|
|
137
|
+
if (packet?.type === 'audio' && typeof output?.audio?.write === 'function') {
|
|
138
|
+
output.audio.write(packet.data);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}, 0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Class functions
|
|
146
|
+
isBuffering() {
|
|
147
|
+
return this.#outputs?.buffer !== undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
startBuffering() {
|
|
151
|
+
if (this.#outputs?.buffer === undefined) {
|
|
152
|
+
// No active buffer session, start connection to streamer
|
|
153
|
+
if (this.socket === null && typeof this.host === 'string' && this.host !== '') {
|
|
154
|
+
if (typeof this.connect === 'function') {
|
|
155
|
+
this.connect(this.host);
|
|
156
|
+
this?.log?.debug && this.log.debug('Started buffering from "%s"', this.host);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.#outputs.buffer = {
|
|
161
|
+
type: 'buffer',
|
|
162
|
+
buffer: [],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
startLiveStream(sessionID, videoStream, audioStream, talkbackStream) {
|
|
168
|
+
// Setup error catching for video/audio/talkback streams
|
|
169
|
+
if (videoStream !== null && typeof videoStream === 'object') {
|
|
170
|
+
videoStream.on('error', () => {
|
|
171
|
+
// EPIPE errors??
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (audioStream !== null && typeof audioStream === 'object') {
|
|
176
|
+
audioStream.on('error', () => {
|
|
177
|
+
// EPIPE errors??
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (talkbackStream !== null && typeof talkbackStream === 'object') {
|
|
182
|
+
let talkbackTimeout = undefined;
|
|
183
|
+
|
|
184
|
+
talkbackStream.on('error', () => {
|
|
185
|
+
// EPIPE errors??
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
talkbackStream.on('data', (data) => {
|
|
189
|
+
// Received audio data to send onto camera/doorbell for output
|
|
190
|
+
if (typeof this.talkingAudio === 'function') {
|
|
191
|
+
this.talkingAudio(data);
|
|
192
|
+
|
|
193
|
+
talkbackTimeout = clearTimeout(talkbackTimeout);
|
|
194
|
+
talkbackTimeout = setTimeout(() => {
|
|
195
|
+
// no audio received in 500ms, so mark end of stream
|
|
196
|
+
this.talkingAudio(Buffer.alloc(0));
|
|
197
|
+
}, 500);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.socket === null && typeof this.host === 'string' && this.host !== '') {
|
|
203
|
+
// We do not have an active socket connection, so startup connection to host
|
|
204
|
+
if (typeof this.connect === 'function') {
|
|
205
|
+
this.connect(this.host);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Add video/audio streams for our output loop to handle outputting to
|
|
210
|
+
this.#outputs[sessionID] = {
|
|
211
|
+
type: 'live',
|
|
212
|
+
video: videoStream,
|
|
213
|
+
audio: audioStream,
|
|
214
|
+
talk: talkbackStream,
|
|
215
|
+
buffer: [],
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// finally, we've started live stream
|
|
219
|
+
this?.log?.debug &&
|
|
220
|
+
this.log.debug(
|
|
221
|
+
'Started live stream from "%s" %s and sesssion id of "%s"',
|
|
222
|
+
this.host,
|
|
223
|
+
talkbackStream !== null && typeof talkbackStream === 'object' ? 'with two-way audio' : '',
|
|
224
|
+
sessionID,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
startRecordStream(sessionID, videoStream, audioStream) {
|
|
229
|
+
// Setup error catching for video/audio streams
|
|
230
|
+
if (videoStream !== null && typeof videoStream === 'object') {
|
|
231
|
+
videoStream.on('error', () => {
|
|
232
|
+
// EPIPE errors??
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (audioStream !== null && typeof audioStream === 'object') {
|
|
237
|
+
audioStream.on('error', () => {
|
|
238
|
+
// EPIPE errors??
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this.socket === null && typeof this.host === 'string' && this.host !== '') {
|
|
243
|
+
// We do not have an active socket connection, so startup connection to host
|
|
244
|
+
if (typeof this.connect === 'function') {
|
|
245
|
+
this.connect(this.host);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Add video/audio streams for our output loop to handle outputting to
|
|
250
|
+
this.#outputs[sessionID] = {
|
|
251
|
+
type: 'record',
|
|
252
|
+
video: videoStream,
|
|
253
|
+
audio: audioStream,
|
|
254
|
+
// eslint-disable-next-line no-undef
|
|
255
|
+
buffer: this.#outputs?.buffer?.buffer !== undefined ? structuredClone(this.#outputs.buffer.buffer) : [],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Finally we've started the recording stream
|
|
259
|
+
this?.log?.debug && this.log.debug('Started recording stream from "%s" with sesison id of "%s"', this.host, sessionID);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
stopRecordStream(sessionID) {
|
|
263
|
+
// Request to stop a recording stream
|
|
264
|
+
if (typeof this.#outputs[sessionID] === 'object') {
|
|
265
|
+
this?.log?.debug && this.log.debug('Stopped recording stream from "%s"', this.host);
|
|
266
|
+
delete this.#outputs[sessionID];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// If we have no more output streams active, we'll close the connection to host
|
|
270
|
+
if (Object.keys(this.#outputs).length === 0 && typeof this.close === 'function') {
|
|
271
|
+
this.close(true);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
stopLiveStream(sessionID) {
|
|
276
|
+
// Request to stop an active live stream
|
|
277
|
+
if (typeof this.#outputs[sessionID] === 'object') {
|
|
278
|
+
this?.log?.debug && this.log.debug('Stopped live stream from "%s"', this.host);
|
|
279
|
+
delete this.#outputs[sessionID];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// If we have no more output streams active, we'll close the connection to host
|
|
283
|
+
if (Object.keys(this.#outputs).length === 0 && typeof this.close === 'function') {
|
|
284
|
+
this.close(true);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
stopBuffering() {
|
|
289
|
+
if (this.#outputs?.buffer !== undefined) {
|
|
290
|
+
this?.log?.debug && this.log.debug('Stopped buffering from "%s"', this.host);
|
|
291
|
+
delete this.#outputs.buffer;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// If we have no more output streams active, we'll close the connection to host
|
|
295
|
+
if (Object.keys(this.#outputs).length === 0 && typeof this.close === 'function') {
|
|
296
|
+
this.close(true);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
update(deviceData) {
|
|
301
|
+
if (typeof deviceData !== 'object') {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.online = deviceData?.online === true;
|
|
306
|
+
this.videoEnabled = deviceData?.streaming_enabled === true;
|
|
307
|
+
this.audioEnabled = deviceData?.audio_enabled === true;
|
|
308
|
+
|
|
309
|
+
if (this.host !== deviceData.streaming_host) {
|
|
310
|
+
this.host = deviceData.streaming_host;
|
|
311
|
+
this?.log?.debug && this.log.debug('New streaming host has requested a new host "%s" for connection', this.host);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (this.online !== deviceData.online || this.videoEnabled !== deviceData.streaming_enabled) {
|
|
315
|
+
// Online status or streaming status has changed has changed
|
|
316
|
+
this.online = deviceData?.online === true;
|
|
317
|
+
this.videoEnabled = deviceData?.streaming_enabled === true;
|
|
318
|
+
if ((this.online === false || this.videoEnabled === false) && typeof this.close === 'function') {
|
|
319
|
+
this.close(true); // as offline or streaming not enabled, close socket
|
|
320
|
+
}
|
|
321
|
+
if (this.online === true && this.videoEnabled === true && typeof this.connect === 'function') {
|
|
322
|
+
this.connect(this.host); // Connect to host for stream
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
addToOutput(type, time, data) {
|
|
328
|
+
if (typeof type !== 'string' || type === '' || typeof time !== 'number' || time === 0) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
Object.values(this.#outputs).forEach((output) => {
|
|
333
|
+
output.buffer.push({
|
|
334
|
+
type: type,
|
|
335
|
+
time: time,
|
|
336
|
+
data: data,
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
haveOutputs() {
|
|
342
|
+
return Object.keys(this.#outputs).length > 0;
|
|
343
|
+
}
|
|
344
|
+
}
|