homebridge-nest-accfactory 0.3.1 → 0.3.2
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/README.md +31 -25
- package/config.schema.json +46 -22
- package/dist/HomeKitDevice.js +495 -262
- package/dist/HomeKitHistory.js +357 -341
- package/dist/config.js +67 -85
- package/dist/consts.js +160 -0
- package/dist/devices.js +35 -48
- package/dist/ffmpeg.js +297 -0
- package/dist/index.js +3 -3
- package/dist/nexustalk.js +157 -124
- package/dist/plugins/camera.js +1153 -924
- package/dist/plugins/doorbell.js +26 -32
- package/dist/plugins/floodlight.js +11 -24
- package/dist/plugins/heatlink.js +411 -5
- package/dist/plugins/lock.js +309 -0
- package/dist/plugins/protect.js +239 -70
- package/dist/plugins/tempsensor.js +158 -34
- package/dist/plugins/thermostat.js +890 -454
- package/dist/plugins/weather.js +128 -33
- package/dist/protobuf/nest/services/apigateway.proto +1 -1
- package/dist/protobuf/nestlabs/gateway/v2.proto +1 -1
- package/dist/protobuf/root.proto +1 -0
- package/dist/rtpmuxer.js +186 -0
- package/dist/streamer.js +486 -244
- package/dist/system.js +1739 -2852
- package/dist/utils.js +327 -0
- package/dist/webrtc.js +354 -225
- package/package.json +19 -16
package/dist/nexustalk.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Credit to https://github.com/Brandawg93/homebridge-nest-cam for the work on the Nest Camera comms code on which this is based
|
|
7
7
|
//
|
|
8
|
-
// Code version 2025.
|
|
8
|
+
// Code version 2025.07.30
|
|
9
9
|
// Mark Hulskamp
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
@@ -19,15 +19,14 @@ import fs from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import tls from 'tls';
|
|
21
21
|
import crypto from 'crypto';
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
23
22
|
|
|
24
23
|
// Define our modules
|
|
25
24
|
import Streamer from './streamer.js';
|
|
26
25
|
|
|
27
26
|
// Define constants
|
|
27
|
+
import { USER_AGENT, __dirname } from './consts.js';
|
|
28
|
+
|
|
28
29
|
const PING_INTERVAL = 15000; // Ping interval to nexus server while stream active
|
|
29
|
-
const USER_AGENT = 'Nest/5.78.0 (iOScom.nestlabs.jasper.release) os=18.0'; // User Agent string
|
|
30
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
|
|
31
30
|
|
|
32
31
|
const PACKET_TYPE = {
|
|
33
32
|
PING: 1,
|
|
@@ -63,11 +62,9 @@ const AAC_MONO_48000_BLANK = Buffer.from([
|
|
|
63
62
|
|
|
64
63
|
// nexusTalk object
|
|
65
64
|
export default class NexusTalk extends Streamer {
|
|
65
|
+
streaming_host = undefined; // Main nexustalk streaming host
|
|
66
66
|
token = undefined;
|
|
67
|
-
|
|
68
|
-
pingTimer = undefined; // Timer object for ping interval
|
|
69
|
-
stalledTimer = undefined; // Timer object for no received data
|
|
70
|
-
host = ''; // Host to connect to or connected too
|
|
67
|
+
useGoogleAuth = false; // Nest vs google auth
|
|
71
68
|
blankAudio = AAC_MONO_48000_BLANK;
|
|
72
69
|
video = {}; // Video stream details once connected
|
|
73
70
|
audio = {}; // Audio stream details once connected
|
|
@@ -79,11 +76,23 @@ export default class NexusTalk extends Streamer {
|
|
|
79
76
|
#messages = []; // Incoming messages
|
|
80
77
|
#authorised = false; // Have we been authorised
|
|
81
78
|
#id = undefined; // Session ID
|
|
79
|
+
#host = undefined; // Current host connected to
|
|
80
|
+
#pingTimer = undefined; // Timer object for ping interval
|
|
81
|
+
#stalledTimer = undefined; // Timer object for no received data
|
|
82
|
+
|
|
83
|
+
// Codecs being used for video, audio and talking
|
|
84
|
+
get codecs() {
|
|
85
|
+
return {
|
|
86
|
+
video: Streamer.CODEC_TYPE.H264, // Video codec
|
|
87
|
+
audio: Streamer.CODEC_TYPE.AAC, // Audio codec
|
|
88
|
+
talkback: Streamer.CODEC_TYPE.SPEEX, // Talkback codec
|
|
89
|
+
};
|
|
90
|
+
}
|
|
82
91
|
|
|
83
|
-
constructor(deviceData, options) {
|
|
84
|
-
super(deviceData, options);
|
|
92
|
+
constructor(uuid, deviceData, options) {
|
|
93
|
+
super(uuid, deviceData, options);
|
|
85
94
|
|
|
86
|
-
if (fs.existsSync(path.
|
|
95
|
+
if (fs.existsSync(path.join(__dirname, 'protobuf/nest/nexustalk.proto')) === true) {
|
|
87
96
|
protobuf.util.Long = null;
|
|
88
97
|
protobuf.configure();
|
|
89
98
|
this.#protobufNexusTalk = protobuf.loadSync(path.resolve(__dirname + '/protobuf/nest/nexustalk.proto'));
|
|
@@ -91,85 +100,86 @@ export default class NexusTalk extends Streamer {
|
|
|
91
100
|
|
|
92
101
|
// Store data we need from the device data passed it
|
|
93
102
|
this.token = deviceData?.apiAccess?.token;
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
// Set our streamer codec types
|
|
98
|
-
this.codecs = {
|
|
99
|
-
video: 'h264',
|
|
100
|
-
audio: 'aac',
|
|
101
|
-
talk: 'speex',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// If specified option to start buffering, kick off
|
|
105
|
-
if (options?.buffer === true) {
|
|
106
|
-
this.startBuffering();
|
|
107
|
-
}
|
|
103
|
+
this.streaming_host = deviceData?.streaming_host; // Host we'll connect to
|
|
104
|
+
this.useGoogleAuth = typeof deviceData?.apiAccess?.oauth2 === 'string' && deviceData?.apiAccess?.oauth2 !== '';
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
// Class functions
|
|
111
|
-
connect(host) {
|
|
108
|
+
async connect(host) {
|
|
112
109
|
// Clear any timers we have running
|
|
113
|
-
clearInterval(this
|
|
114
|
-
clearTimeout(this
|
|
115
|
-
this
|
|
116
|
-
this
|
|
110
|
+
clearInterval(this.#pingTimer);
|
|
111
|
+
clearTimeout(this.#stalledTimer);
|
|
112
|
+
this.#pingTimer = undefined;
|
|
113
|
+
this.#stalledTimer = undefined;
|
|
117
114
|
this.#id = undefined; // No session ID yet
|
|
118
115
|
|
|
119
116
|
if (this.online === true && this.videoEnabled === true) {
|
|
120
117
|
if (typeof host === 'undefined' || host === null) {
|
|
121
118
|
// No host parameter passed in, so we'll set this to our internally stored host
|
|
122
|
-
host = this.
|
|
119
|
+
host = this.streaming_host;
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
this.connected = false; // Starting connection
|
|
126
123
|
this?.log?.debug?.('Connection started to "%s"', host);
|
|
124
|
+
this.#host = host; // Update internal host name since we’re about to connect
|
|
125
|
+
|
|
126
|
+
// Wrap tls.connect() in a Promise so we can await the TLS handshake
|
|
127
|
+
try {
|
|
128
|
+
await new Promise((resolve, reject) => {
|
|
129
|
+
this.#socket = tls.connect({ host: host, port: 1443 }, () => {
|
|
130
|
+
// Opened connection to Nexus server, so now need to authenticate ourselves
|
|
131
|
+
this?.log?.debug?.('Connection established to "%s"', host);
|
|
132
|
+
|
|
133
|
+
this.#socket.setKeepAlive(true); // Keep socket connection alive
|
|
134
|
+
this.connected = true;
|
|
135
|
+
this.#Authenticate(false); // Send authentication request
|
|
136
|
+
resolve(); // Allow await connect() to continue
|
|
137
|
+
});
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.connected = true;
|
|
135
|
-
this.#Authenticate(false);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
this.#socket.on('error', () => {});
|
|
139
|
+
this.#socket.on('error', (err) => {
|
|
140
|
+
// TLS error (could be refused, timeout, etc.)
|
|
141
|
+
this?.log?.warn?.('TLS error on connect to "%s": %s', host, err?.message || err);
|
|
142
|
+
this.connected = undefined;
|
|
143
|
+
reject(err);
|
|
144
|
+
});
|
|
139
145
|
|
|
140
|
-
|
|
146
|
+
this.#socket.on('end', () => {});
|
|
141
147
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
this.#socket.on('data', (data) => {
|
|
149
|
+
this.#handleNexusData(data);
|
|
150
|
+
});
|
|
145
151
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
this.#socket.on('close', (hadError) => {
|
|
153
|
+
this?.log?.debug?.('Connection closed to "%s"', host);
|
|
154
|
+
|
|
155
|
+
clearInterval(this.#pingTimer);
|
|
156
|
+
clearTimeout(this.#stalledTimer);
|
|
157
|
+
this.#pingTimer = undefined;
|
|
158
|
+
this.#stalledTimer = undefined;
|
|
159
|
+
this.#authorised = false; // Since connection closed, we can't be authorised anymore
|
|
160
|
+
this.#socket = undefined; // Clear socket object
|
|
161
|
+
this.connected = undefined;
|
|
162
|
+
this.#id = undefined; // Not an active session anymore
|
|
163
|
+
|
|
164
|
+
if (hadError === true && (this.isStreaming() === true || this.isBuffering() === true)) {
|
|
165
|
+
// We still have either active buffering occurring or output streams running
|
|
166
|
+
// so attempt to restart connection to existing host
|
|
167
|
+
this.connect(host);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this?.log?.error?.('Failed to connect to "%s": %s', host, String(error));
|
|
173
|
+
}
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
close(stopStreamFirst) {
|
|
177
|
+
async close(stopStreamFirst) {
|
|
168
178
|
// Close an authenicated socket stream gracefully
|
|
169
179
|
if (this.#socket !== undefined) {
|
|
170
180
|
if (stopStreamFirst === true) {
|
|
171
181
|
// Send a notifcation to nexus we're finished playback
|
|
172
|
-
this.#stopNexusData();
|
|
182
|
+
await this.#stopNexusData();
|
|
173
183
|
}
|
|
174
184
|
this.#socket.destroy();
|
|
175
185
|
}
|
|
@@ -181,15 +191,17 @@ export default class NexusTalk extends Streamer {
|
|
|
181
191
|
this.#messages = [];
|
|
182
192
|
this.video = {};
|
|
183
193
|
this.audio = {};
|
|
194
|
+
this.#host = undefined; // No longer connected to this host
|
|
184
195
|
}
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
async onUpdate(deviceData) {
|
|
187
198
|
if (typeof deviceData !== 'object') {
|
|
188
199
|
return;
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
if (deviceData.apiAccess.token !== this.token) {
|
|
202
|
+
if (deviceData?.apiAccess?.token !== undefined && deviceData.apiAccess.token !== this.token) {
|
|
192
203
|
// access token has changed so re-authorise
|
|
204
|
+
this?.log?.debug?.('Access token has changed for uuid "%s". Updating token', this.nest_google_uuid);
|
|
193
205
|
this.token = deviceData.apiAccess.token;
|
|
194
206
|
|
|
195
207
|
if (this.#socket !== undefined) {
|
|
@@ -197,30 +209,38 @@ export default class NexusTalk extends Streamer {
|
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
|
|
200
|
-
if (this.
|
|
201
|
-
this.
|
|
202
|
-
this?.log?.debug?.('New host has been requested for connection. Host requested is "%s"', this.host);
|
|
203
|
-
}
|
|
212
|
+
if (deviceData?.streaming_host !== undefined && this.streaming_host !== deviceData.streaming_host) {
|
|
213
|
+
this.streaming_host = deviceData.streaming_host;
|
|
204
214
|
|
|
205
|
-
|
|
206
|
-
|
|
215
|
+
if (this.isStreaming() === true || this.isBuffering() === true) {
|
|
216
|
+
this?.log?.debug?.('New host has been requested for connection. Host requested is "%s"', this.streaming_host);
|
|
217
|
+
|
|
218
|
+
// Setup listener for socket close event. Once socket is closed, we'll perform the redirect
|
|
219
|
+
this.#socket?.on?.('close', () => {
|
|
220
|
+
this.connect(this.streaming_host); // Connect to new host
|
|
221
|
+
});
|
|
222
|
+
this.close(true); // Close existing socket
|
|
223
|
+
}
|
|
224
|
+
}
|
|
207
225
|
}
|
|
208
226
|
|
|
209
|
-
|
|
227
|
+
sendTalkback(talkingBuffer) {
|
|
228
|
+
if (Buffer.isBuffer(talkingBuffer) === false || this.#protobufNexusTalk === undefined || this.#id === undefined) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
210
232
|
// Encode audio packet for sending to camera
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.#sendMessage(PACKET_TYPE.AUDIO_PAYLOAD, encodedData);
|
|
223
|
-
}
|
|
233
|
+
let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.AudioPayload');
|
|
234
|
+
if (TraitMap !== null) {
|
|
235
|
+
let encodedData = TraitMap.encode(
|
|
236
|
+
TraitMap.fromObject({
|
|
237
|
+
payload: talkingBuffer,
|
|
238
|
+
sessionId: this.#id,
|
|
239
|
+
codec: Streamer.CODEC_TYPE.SPEEX,
|
|
240
|
+
sampleRate: 16000,
|
|
241
|
+
}),
|
|
242
|
+
).finish();
|
|
243
|
+
this.#sendMessage(PACKET_TYPE.AUDIO_PAYLOAD, encodedData);
|
|
224
244
|
}
|
|
225
245
|
}
|
|
226
246
|
|
|
@@ -300,15 +320,13 @@ export default class NexusTalk extends Streamer {
|
|
|
300
320
|
let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.AuthoriseRequest');
|
|
301
321
|
if (TraitMap !== null) {
|
|
302
322
|
authoriseRequest = TraitMap.encode(
|
|
303
|
-
TraitMap.fromObject(
|
|
304
|
-
this.tokenType === 'nest' ? { sessionToken: this.token } : this.tokenType === 'google' ? { oliveToken: this.token } : {},
|
|
305
|
-
),
|
|
323
|
+
TraitMap.fromObject(this.useGoogleAuth === true ? { oliveToken: this.token } : { sessionToken: this.token }),
|
|
306
324
|
).finish();
|
|
307
325
|
}
|
|
308
326
|
|
|
309
327
|
if (reauthorise === true && authoriseRequest !== null) {
|
|
310
328
|
// Request to re-authorise only
|
|
311
|
-
this?.log?.debug?.('Re-authentication requested to "%s"', this
|
|
329
|
+
this?.log?.debug?.('Re-authentication requested to "%s"', this.#host);
|
|
312
330
|
this.#sendMessage(PACKET_TYPE.AUTHORIZE_REQUEST, authoriseRequest);
|
|
313
331
|
}
|
|
314
332
|
|
|
@@ -316,12 +334,12 @@ export default class NexusTalk extends Streamer {
|
|
|
316
334
|
// This isn't a re-authorise request, so perform 'Hello' packet
|
|
317
335
|
let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.Hello');
|
|
318
336
|
if (TraitMap !== null) {
|
|
319
|
-
this?.log?.debug?.('Performing authentication to "%s"', this
|
|
337
|
+
this?.log?.debug?.('Performing authentication to "%s"', this.#host);
|
|
320
338
|
|
|
321
339
|
let encodedData = TraitMap.encode(
|
|
322
340
|
TraitMap.fromObject({
|
|
323
341
|
protocolVersion: 'VERSION_3',
|
|
324
|
-
uuid: this.
|
|
342
|
+
uuid: this.nest_google_uuid.split(/[._]+/)[1],
|
|
325
343
|
requireConnectedCamera: false,
|
|
326
344
|
USER_AGENT: USER_AGENT,
|
|
327
345
|
deviceId: crypto.randomUUID(),
|
|
@@ -350,13 +368,12 @@ export default class NexusTalk extends Streamer {
|
|
|
350
368
|
return;
|
|
351
369
|
}
|
|
352
370
|
|
|
353
|
-
this?.log?.debug?.('Redirect requested from "%s" to "%s"', this
|
|
371
|
+
this?.log?.debug?.('Redirect requested from "%s" to "%s"', this.#host, redirectToHost);
|
|
354
372
|
|
|
355
373
|
// Setup listener for socket close event. Once socket is closed, we'll perform the redirect
|
|
356
|
-
this.#socket
|
|
357
|
-
this
|
|
358
|
-
|
|
359
|
-
});
|
|
374
|
+
this.#socket?.on?.('close', () => {
|
|
375
|
+
this.connect(redirectToHost); // Connect to new host
|
|
376
|
+
});
|
|
360
377
|
this.close(true); // Close existing socket
|
|
361
378
|
}
|
|
362
379
|
|
|
@@ -368,18 +385,17 @@ export default class NexusTalk extends Streamer {
|
|
|
368
385
|
if (stream.codecType === this.codecs.video.toUpperCase()) {
|
|
369
386
|
this.video = {
|
|
370
387
|
id: stream.channelId,
|
|
371
|
-
|
|
388
|
+
baseTimestamp: stream.startTimestamp,
|
|
389
|
+
baseTime: Date.now(),
|
|
372
390
|
sampleRate: stream.sampleRate,
|
|
373
|
-
timeStamp: 0,
|
|
374
391
|
};
|
|
375
392
|
}
|
|
376
393
|
if (stream.codecType === this.codecs.audio.toUpperCase()) {
|
|
377
394
|
this.audio = {
|
|
378
395
|
id: stream.channelId,
|
|
379
|
-
|
|
396
|
+
baseTimestamp: stream.startTimestamp,
|
|
397
|
+
baseTime: Date.now(),
|
|
380
398
|
sampleRate: stream.sampleRate,
|
|
381
|
-
timeStamp: 0,
|
|
382
|
-
talking: false,
|
|
383
399
|
};
|
|
384
400
|
}
|
|
385
401
|
});
|
|
@@ -389,11 +405,26 @@ export default class NexusTalk extends Streamer {
|
|
|
389
405
|
this.#packets = [];
|
|
390
406
|
this.#messages = [];
|
|
391
407
|
|
|
392
|
-
this?.log?.debug?.('Playback started from "%s" with session ID "%s"', this
|
|
408
|
+
this?.log?.debug?.('Playback started from "%s" with session ID "%s"', this.#host, this.#id);
|
|
393
409
|
}
|
|
394
410
|
}
|
|
395
411
|
|
|
396
412
|
#handlePlaybackPacket(payload) {
|
|
413
|
+
const calculateTimestamp = (delta, stream) => {
|
|
414
|
+
if (
|
|
415
|
+
typeof delta !== 'number' ||
|
|
416
|
+
typeof stream?.sampleRate !== 'number' ||
|
|
417
|
+
stream?.baseTime === undefined ||
|
|
418
|
+
stream?.baseTimestamp === undefined
|
|
419
|
+
) {
|
|
420
|
+
return Date.now();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let deltaTicks = stream.baseTimestamp + delta - stream.baseTimestamp;
|
|
424
|
+
let deltaMs = (deltaTicks / stream.sampleRate) * 1000;
|
|
425
|
+
return stream.baseTime + deltaMs;
|
|
426
|
+
};
|
|
427
|
+
|
|
397
428
|
// Decode playback packet
|
|
398
429
|
if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
|
|
399
430
|
let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.PlaybackPacket').decode(payload).toJSON();
|
|
@@ -401,30 +432,33 @@ export default class NexusTalk extends Streamer {
|
|
|
401
432
|
// Setup up a timeout to monitor for no packets recieved in a certain period
|
|
402
433
|
// If its trigger, we'll attempt to restart the stream and/or connection
|
|
403
434
|
// <-- testing to see how often this occurs first
|
|
404
|
-
clearTimeout(this
|
|
405
|
-
this
|
|
435
|
+
clearTimeout(this.#stalledTimer);
|
|
436
|
+
this.#stalledTimer = setTimeout(() => {
|
|
406
437
|
this?.log?.debug?.(
|
|
407
438
|
'We have not received any data from nexus in the past "%s" seconds for uuid "%s". Attempting restart',
|
|
408
439
|
10,
|
|
409
|
-
this.
|
|
440
|
+
this.nest_google_uuid,
|
|
410
441
|
);
|
|
411
442
|
|
|
412
443
|
// Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
|
|
413
|
-
this.#socket
|
|
414
|
-
this
|
|
415
|
-
|
|
416
|
-
});
|
|
444
|
+
this.#socket?.on?.('close', () => {
|
|
445
|
+
this.connect(); // try reconnection
|
|
446
|
+
});
|
|
417
447
|
this.close(false); // Close existing socket
|
|
418
448
|
}, 10000);
|
|
419
449
|
|
|
450
|
+
// Timestamps are rolling — incremented from startTime using timestampDelta per packet
|
|
451
|
+
|
|
420
452
|
// Handle video packet
|
|
421
453
|
if (decodedMessage?.channelId !== undefined && decodedMessage.channelId === this.video?.id) {
|
|
422
|
-
|
|
454
|
+
let ts = calculateTimestamp(decodedMessage.timestampDelta, this.video);
|
|
455
|
+
this.add(Streamer.PACKET_TYPE.VIDEO, Buffer.from(decodedMessage.payload, 'base64'), ts);
|
|
423
456
|
}
|
|
424
457
|
|
|
425
458
|
// Handle audio packet
|
|
426
459
|
if (decodedMessage?.channelId !== undefined && decodedMessage.channelId === this.audio?.id) {
|
|
427
|
-
|
|
460
|
+
let ts = calculateTimestamp(decodedMessage.timestampDelta, this.audio);
|
|
461
|
+
this.add(Streamer.PACKET_TYPE.AUDIO, Buffer.from(decodedMessage.payload, 'base64'), ts);
|
|
428
462
|
}
|
|
429
463
|
}
|
|
430
464
|
}
|
|
@@ -436,18 +470,17 @@ export default class NexusTalk extends Streamer {
|
|
|
436
470
|
|
|
437
471
|
if (this.#id !== undefined && decodedMessage.reason === 'USER_ENDED_SESSION') {
|
|
438
472
|
// Normal playback ended ie: when we stopped playback
|
|
439
|
-
this?.log?.debug?.('Playback ended on "%s"', this
|
|
473
|
+
this?.log?.debug?.('Playback ended on "%s"', this.#host);
|
|
440
474
|
}
|
|
441
475
|
|
|
442
476
|
if (decodedMessage.reason !== 'USER_ENDED_SESSION') {
|
|
443
477
|
// Error during playback, so we'll attempt to restart by reconnection to host
|
|
444
|
-
this?.log?.debug?.('Playback ended on "%s" with error "%s". Attempting reconnection', this
|
|
478
|
+
this?.log?.debug?.('Playback ended on "%s" with error "%s". Attempting reconnection', this.#host, decodedMessage.reason);
|
|
445
479
|
|
|
446
480
|
// Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
|
|
447
|
-
this.#socket
|
|
448
|
-
this
|
|
449
|
-
|
|
450
|
-
});
|
|
481
|
+
this.#socket?.on?.('close', () => {
|
|
482
|
+
this.connect(); // try reconnection to existing host
|
|
483
|
+
});
|
|
451
484
|
this.close(false); // Close existing socket
|
|
452
485
|
}
|
|
453
486
|
}
|
|
@@ -472,7 +505,7 @@ export default class NexusTalk extends Streamer {
|
|
|
472
505
|
if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
|
|
473
506
|
//let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackBegin').decode(payload).toJSON();
|
|
474
507
|
this.audio.talking = true;
|
|
475
|
-
this?.log?.debug?.('Talking started on uuid "%s"', this.
|
|
508
|
+
this?.log?.debug?.('Talking started on uuid "%s"', this.nest_google_uuid);
|
|
476
509
|
}
|
|
477
510
|
}
|
|
478
511
|
|
|
@@ -481,7 +514,7 @@ export default class NexusTalk extends Streamer {
|
|
|
481
514
|
if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
|
|
482
515
|
//let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackEnd').decode(payload).toJSON();
|
|
483
516
|
this.audio.talking = false;
|
|
484
|
-
this?.log?.debug?.('Talking ended on uuid "%s"', this.
|
|
517
|
+
this?.log?.debug?.('Talking ended on uuid "%s"', this.nest_google_uuid);
|
|
485
518
|
}
|
|
486
519
|
}
|
|
487
520
|
|
|
@@ -521,8 +554,8 @@ export default class NexusTalk extends Streamer {
|
|
|
521
554
|
}
|
|
522
555
|
|
|
523
556
|
// Periodically send PING message to keep stream alive
|
|
524
|
-
clearInterval(this
|
|
525
|
-
this
|
|
557
|
+
clearInterval(this.#pingTimer);
|
|
558
|
+
this.#pingTimer = setInterval(() => {
|
|
526
559
|
this.#sendMessage(PACKET_TYPE.PING, Buffer.alloc(0));
|
|
527
560
|
}, PING_INTERVAL);
|
|
528
561
|
|