@mindexec/cli 0.2.394 → 0.2.395
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/package.json +1 -1
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/remote-hub.js +364 -13
- package/scripts/remote-agent-ws-smoke.mjs +69 -2
- package/scripts/remote-hub-smoke.mjs +39 -0
- package/server.js +11 -1
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/remote-hub.js
CHANGED
|
@@ -20,7 +20,73 @@ const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT = 4;
|
|
|
20
20
|
const RECENT_LIVE_FRAME_CACHE_LIMIT = 80;
|
|
21
21
|
const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 3 * 1024 * 1024;
|
|
22
22
|
const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 24 * 1024 * 1024;
|
|
23
|
-
const REMOTE_PROTOCOL_VERSION =
|
|
23
|
+
const REMOTE_PROTOCOL_VERSION = 2;
|
|
24
|
+
const REMOTE_AGENT_PROTOCOL = 'mindexec.remote.agent';
|
|
25
|
+
const REMOTE_FRAME_PROTOCOL = 'mindexec.remote.frame.v2';
|
|
26
|
+
const REMOTE_FRAME_PROTOCOL_VERSION = 2;
|
|
27
|
+
const REMOTE_FRAME_HANDSHAKE = 'smartview-request-open-frame';
|
|
28
|
+
const DEFAULT_REMOTE_FRAME_MODE = 'remote-fast';
|
|
29
|
+
const REMOTE_FRAME_MODE_PROFILES = Object.freeze({
|
|
30
|
+
thumbnail: Object.freeze({
|
|
31
|
+
mode: 'thumbnail',
|
|
32
|
+
label: 'Thumbnail PNG/JPEG Binary',
|
|
33
|
+
transport: 'ws-binary',
|
|
34
|
+
encoding: 'image',
|
|
35
|
+
codec: 'image',
|
|
36
|
+
compression: 'image/*',
|
|
37
|
+
profile: 'thumbnail-binary-v1',
|
|
38
|
+
modeVersion: 1,
|
|
39
|
+
implemented: true
|
|
40
|
+
}),
|
|
41
|
+
'remote-fast': Object.freeze({
|
|
42
|
+
mode: 'remote-fast',
|
|
43
|
+
label: 'RemoteFast JPEG Binary',
|
|
44
|
+
transport: 'ws-binary',
|
|
45
|
+
encoding: 'image',
|
|
46
|
+
codec: 'jpeg',
|
|
47
|
+
compression: 'image/jpeg',
|
|
48
|
+
profile: 'jpeg-binary-v1',
|
|
49
|
+
modeVersion: 1,
|
|
50
|
+
implemented: true
|
|
51
|
+
}),
|
|
52
|
+
'remote-quality': Object.freeze({
|
|
53
|
+
mode: 'remote-quality',
|
|
54
|
+
label: 'RemoteQuality JPEG Binary',
|
|
55
|
+
transport: 'ws-binary',
|
|
56
|
+
encoding: 'image',
|
|
57
|
+
codec: 'jpeg',
|
|
58
|
+
compression: 'image/jpeg',
|
|
59
|
+
profile: 'jpeg-quality-binary-v1',
|
|
60
|
+
modeVersion: 1,
|
|
61
|
+
implemented: true
|
|
62
|
+
}),
|
|
63
|
+
'video-codec': Object.freeze({
|
|
64
|
+
mode: 'video-codec',
|
|
65
|
+
label: 'Video Codec Stream',
|
|
66
|
+
transport: 'ws-binary',
|
|
67
|
+
encoding: 'video',
|
|
68
|
+
codec: 'video',
|
|
69
|
+
compression: 'video/*',
|
|
70
|
+
profile: 'future-video-codec',
|
|
71
|
+
modeVersion: 0,
|
|
72
|
+
implemented: false
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
const REMOTE_FRAME_MODE_ALIASES = new Map([
|
|
76
|
+
['fast', 'remote-fast'],
|
|
77
|
+
['remotefast', 'remote-fast'],
|
|
78
|
+
['remote_fast', 'remote-fast'],
|
|
79
|
+
['remote-fast', 'remote-fast'],
|
|
80
|
+
['quality', 'remote-quality'],
|
|
81
|
+
['remotequality', 'remote-quality'],
|
|
82
|
+
['remote_quality', 'remote-quality'],
|
|
83
|
+
['remote-quality', 'remote-quality'],
|
|
84
|
+
['thumb', 'thumbnail'],
|
|
85
|
+
['thumbnail', 'thumbnail'],
|
|
86
|
+
['video', 'video-codec'],
|
|
87
|
+
['codec', 'video-codec'],
|
|
88
|
+
['video-codec', 'video-codec']
|
|
89
|
+
]);
|
|
24
90
|
const MAX_SYNTHETIC_DEVICES = 1000;
|
|
25
91
|
const DEFAULT_HOST_TARGET_LEASE_MS = 30000;
|
|
26
92
|
const DUPLICATE_DEVICE_ACTIVE_REJECT_MS = 15000;
|
|
@@ -58,6 +124,115 @@ function safeString(value, maxLength = 200) {
|
|
|
58
124
|
return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
|
|
59
125
|
}
|
|
60
126
|
|
|
127
|
+
function normalizeRemoteFrameMode(value, fallback = DEFAULT_REMOTE_FRAME_MODE) {
|
|
128
|
+
const requested = safeString(value, 80).toLowerCase();
|
|
129
|
+
const fallbackMode = REMOTE_FRAME_MODE_PROFILES[fallback] ? fallback : DEFAULT_REMOTE_FRAME_MODE;
|
|
130
|
+
if (!requested) {
|
|
131
|
+
return fallbackMode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const normalized = REMOTE_FRAME_MODE_ALIASES.get(requested) || requested;
|
|
135
|
+
const profile = REMOTE_FRAME_MODE_PROFILES[normalized];
|
|
136
|
+
if (!profile || profile.implemented !== true) {
|
|
137
|
+
return fallbackMode;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function serializeRemoteFrameModeProfile(profile) {
|
|
144
|
+
return {
|
|
145
|
+
mode: profile.mode,
|
|
146
|
+
label: profile.label,
|
|
147
|
+
transport: profile.transport,
|
|
148
|
+
encoding: profile.encoding,
|
|
149
|
+
codec: profile.codec,
|
|
150
|
+
compression: profile.compression,
|
|
151
|
+
profile: profile.profile,
|
|
152
|
+
modeVersion: profile.modeVersion,
|
|
153
|
+
implemented: profile.implemented === true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getRemoteFrameModeProfile(value, fallback = DEFAULT_REMOTE_FRAME_MODE) {
|
|
158
|
+
const mode = normalizeRemoteFrameMode(value, fallback);
|
|
159
|
+
return serializeRemoteFrameModeProfile(REMOTE_FRAME_MODE_PROFILES[mode]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getSupportedRemoteFrameModeProfiles() {
|
|
163
|
+
return Object.values(REMOTE_FRAME_MODE_PROFILES)
|
|
164
|
+
.filter(profile => profile.implemented === true)
|
|
165
|
+
.map(serializeRemoteFrameModeProfile);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildRemoteFrameProtocolDescriptor() {
|
|
169
|
+
return {
|
|
170
|
+
protocol: REMOTE_FRAME_PROTOCOL,
|
|
171
|
+
version: REMOTE_FRAME_PROTOCOL_VERSION,
|
|
172
|
+
handshake: REMOTE_FRAME_HANDSHAKE,
|
|
173
|
+
defaultMode: DEFAULT_REMOTE_FRAME_MODE,
|
|
174
|
+
modes: getSupportedRemoteFrameModeProfiles()
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildRemoteFrameTransferDescriptor(value, fallback = DEFAULT_REMOTE_FRAME_MODE) {
|
|
179
|
+
const profile = getRemoteFrameModeProfile(value, fallback);
|
|
180
|
+
return {
|
|
181
|
+
mode: profile.mode,
|
|
182
|
+
frameMode: profile.mode,
|
|
183
|
+
frameProfile: profile.profile,
|
|
184
|
+
encoding: profile.encoding,
|
|
185
|
+
codec: profile.codec,
|
|
186
|
+
compression: profile.compression,
|
|
187
|
+
transferProtocol: REMOTE_FRAME_PROTOCOL,
|
|
188
|
+
transferProtocolVersion: REMOTE_FRAME_PROTOCOL_VERSION,
|
|
189
|
+
handshake: REMOTE_FRAME_HANDSHAKE
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildRemoteFrameTransferDescriptorFromMessage(message = {}, fallback = DEFAULT_REMOTE_FRAME_MODE) {
|
|
194
|
+
const descriptor = buildRemoteFrameTransferDescriptor(message.frameMode || message.mode, fallback);
|
|
195
|
+
return {
|
|
196
|
+
...descriptor,
|
|
197
|
+
frameProfile: safeString(message.frameProfile, 80) || descriptor.frameProfile,
|
|
198
|
+
encoding: safeString(message.encoding, 40) || descriptor.encoding,
|
|
199
|
+
codec: safeString(message.codec, 40) || descriptor.codec,
|
|
200
|
+
compression: safeString(message.compression || message.mimeType || message.format, 80) || descriptor.compression,
|
|
201
|
+
transferProtocol: safeString(message.transferProtocol, 80) || descriptor.transferProtocol,
|
|
202
|
+
transferProtocolVersion: Number.isFinite(Number(message.transferProtocolVersion))
|
|
203
|
+
? Math.max(1, Math.floor(Number(message.transferProtocolVersion)))
|
|
204
|
+
: descriptor.transferProtocolVersion,
|
|
205
|
+
handshake: safeString(message.handshake, 80) || descriptor.handshake
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizeIncomingFrameModeProfile(value) {
|
|
210
|
+
if (!value) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (typeof value === 'string') {
|
|
215
|
+
return getRemoteFrameModeProfile(value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (typeof value !== 'object') {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const profile = buildRemoteFrameTransferDescriptor(value.mode || value.frameMode || value.profile);
|
|
223
|
+
return {
|
|
224
|
+
mode: profile.mode,
|
|
225
|
+
label: safeString(value.label, 80) || profile.mode,
|
|
226
|
+
transport: safeString(value.transport, 40) || 'ws-binary',
|
|
227
|
+
encoding: safeString(value.encoding, 40) || profile.encoding,
|
|
228
|
+
codec: safeString(value.codec, 40) || profile.codec,
|
|
229
|
+
compression: safeString(value.compression, 80) || profile.compression,
|
|
230
|
+
profile: safeString(value.profile || value.frameProfile, 80) || profile.frameProfile,
|
|
231
|
+
modeVersion: Number.isFinite(Number(value.modeVersion)) ? Math.max(0, Math.floor(Number(value.modeVersion))) : 1,
|
|
232
|
+
implemented: value.implemented !== false
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
61
236
|
function isLoopbackHost(value) {
|
|
62
237
|
const host = String(value || '').trim().toLowerCase();
|
|
63
238
|
return host === 'localhost'
|
|
@@ -542,6 +717,12 @@ function serializeDevice(device, options = {}) {
|
|
|
542
717
|
arch: device.arch,
|
|
543
718
|
pid: device.pid,
|
|
544
719
|
agentVersion: device.agentVersion,
|
|
720
|
+
protocol: device.protocol,
|
|
721
|
+
protocolVersion: device.protocolVersion,
|
|
722
|
+
frameProtocol: device.frameProtocol ? { ...device.frameProtocol } : null,
|
|
723
|
+
frameModes: Array.isArray(device.frameModes)
|
|
724
|
+
? device.frameModes.map(profile => ({ ...profile }))
|
|
725
|
+
: [],
|
|
545
726
|
capabilities: { ...device.capabilities },
|
|
546
727
|
connected: device.connected,
|
|
547
728
|
connectedAt: device.connectedAt,
|
|
@@ -1086,6 +1267,9 @@ export function createRemoteHub(options = {}) {
|
|
|
1086
1267
|
port: boundPort || requestedPort,
|
|
1087
1268
|
protocol: 'tcp-jsonl',
|
|
1088
1269
|
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
1270
|
+
agentProtocol: REMOTE_AGENT_PROTOCOL,
|
|
1271
|
+
frameProtocol: buildRemoteFrameProtocolDescriptor(),
|
|
1272
|
+
frameModes: getSupportedRemoteFrameModeProfiles(),
|
|
1089
1273
|
heartbeatMs,
|
|
1090
1274
|
taskTimeoutMs,
|
|
1091
1275
|
managerPackage,
|
|
@@ -1183,6 +1367,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1183
1367
|
|
|
1184
1368
|
function makeSyntheticFrame(device, streamId, mode = 'thumbnail', options = {}) {
|
|
1185
1369
|
const now = new Date().toISOString();
|
|
1370
|
+
const descriptor = buildRemoteFrameTransferDescriptor(mode, mode === 'thumbnail' ? 'thumbnail' : DEFAULT_REMOTE_FRAME_MODE);
|
|
1186
1371
|
const frameSeq = Number(device?.counters?.thumbnailFramesReceived || 0)
|
|
1187
1372
|
+ Number(device?.counters?.liveFramesReceived || 0)
|
|
1188
1373
|
+ 1;
|
|
@@ -1195,6 +1380,14 @@ export function createRemoteHub(options = {}) {
|
|
|
1195
1380
|
mimeType: 'image/png',
|
|
1196
1381
|
format: 'image/png',
|
|
1197
1382
|
mode,
|
|
1383
|
+
frameMode: descriptor.frameMode,
|
|
1384
|
+
frameProfile: descriptor.frameProfile,
|
|
1385
|
+
encoding: descriptor.encoding,
|
|
1386
|
+
codec: 'png',
|
|
1387
|
+
compression: 'image/png',
|
|
1388
|
+
transferProtocol: descriptor.transferProtocol,
|
|
1389
|
+
transferProtocolVersion: descriptor.transferProtocolVersion,
|
|
1390
|
+
handshake: descriptor.handshake,
|
|
1198
1391
|
fps: clampNumber(options.fps, 1, 24, mode === 'remote-fast' ? 20 : 1),
|
|
1199
1392
|
capturedAt: now,
|
|
1200
1393
|
receivedAt: now,
|
|
@@ -1239,7 +1432,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1239
1432
|
return null;
|
|
1240
1433
|
}
|
|
1241
1434
|
|
|
1242
|
-
const frame = makeSyntheticFrame(device, streamId || device.activeLiveStream.streamId,
|
|
1435
|
+
const frame = makeSyntheticFrame(device, streamId || device.activeLiveStream.streamId, device.activeLiveStream.mode || DEFAULT_REMOTE_FRAME_MODE, {
|
|
1243
1436
|
commandId: options.commandId || device.activeLiveStream.commandId,
|
|
1244
1437
|
width: options.maxWidth || 960,
|
|
1245
1438
|
height: options.maxHeight || 540,
|
|
@@ -1482,11 +1675,23 @@ export function createRemoteHub(options = {}) {
|
|
|
1482
1675
|
}
|
|
1483
1676
|
|
|
1484
1677
|
if (isLive) {
|
|
1678
|
+
const transfer = buildRemoteFrameTransferDescriptor(DEFAULT_REMOTE_FRAME_MODE);
|
|
1485
1679
|
device.activeLiveStream = {
|
|
1486
1680
|
streamId: `synthetic-live-${ordinal}`,
|
|
1487
1681
|
commandId: `synthetic-live-command-${ordinal}`,
|
|
1488
1682
|
active: true,
|
|
1489
|
-
|
|
1683
|
+
open: true,
|
|
1684
|
+
openedAt: seenAt,
|
|
1685
|
+
openTransport: 'synthetic',
|
|
1686
|
+
mode: transfer.mode,
|
|
1687
|
+
frameMode: transfer.frameMode,
|
|
1688
|
+
frameProfile: transfer.frameProfile,
|
|
1689
|
+
encoding: transfer.encoding,
|
|
1690
|
+
codec: transfer.codec,
|
|
1691
|
+
compression: transfer.compression,
|
|
1692
|
+
transferProtocol: transfer.transferProtocol,
|
|
1693
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
1694
|
+
handshake: transfer.handshake,
|
|
1490
1695
|
fps: 20,
|
|
1491
1696
|
startedAt: seenAt,
|
|
1492
1697
|
stoppedAt: '',
|
|
@@ -1543,6 +1748,24 @@ export function createRemoteHub(options = {}) {
|
|
|
1543
1748
|
|
|
1544
1749
|
closeExistingDeviceSocket(deviceId, sessionId);
|
|
1545
1750
|
|
|
1751
|
+
const frameProtocol = typeof hello.frameProtocol === 'object' && hello.frameProtocol
|
|
1752
|
+
? { ...hello.frameProtocol }
|
|
1753
|
+
: null;
|
|
1754
|
+
const frameModes = Array.isArray(hello.frameModes)
|
|
1755
|
+
? hello.frameModes.map(normalizeIncomingFrameModeProfile).filter(Boolean)
|
|
1756
|
+
: [];
|
|
1757
|
+
const capabilities = typeof hello.capabilities === 'object' && hello.capabilities
|
|
1758
|
+
? { ...hello.capabilities }
|
|
1759
|
+
: {};
|
|
1760
|
+
if (!capabilities.frameProtocol) {
|
|
1761
|
+
capabilities.frameProtocol = frameProtocol || buildRemoteFrameProtocolDescriptor();
|
|
1762
|
+
}
|
|
1763
|
+
if (!capabilities.frameModes) {
|
|
1764
|
+
capabilities.frameModes = frameModes.length > 0
|
|
1765
|
+
? frameModes.map(profile => ({ ...profile }))
|
|
1766
|
+
: getSupportedRemoteFrameModeProfiles();
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1546
1769
|
const device = {
|
|
1547
1770
|
socket,
|
|
1548
1771
|
deviceId,
|
|
@@ -1553,9 +1776,11 @@ export function createRemoteHub(options = {}) {
|
|
|
1553
1776
|
arch: safeString(hello.arch, 40),
|
|
1554
1777
|
pid: Number.isFinite(Number(hello.pid)) ? Number(hello.pid) : 0,
|
|
1555
1778
|
agentVersion: safeString(hello.agentVersion, 40),
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1779
|
+
protocol: safeString(hello.protocol, 80) || REMOTE_AGENT_PROTOCOL,
|
|
1780
|
+
protocolVersion: Number.isFinite(Number(hello.protocolVersion)) ? Math.max(1, Math.floor(Number(hello.protocolVersion))) : 1,
|
|
1781
|
+
frameProtocol: frameProtocol || buildRemoteFrameProtocolDescriptor(),
|
|
1782
|
+
frameModes: frameModes.length > 0 ? frameModes : getSupportedRemoteFrameModeProfiles(),
|
|
1783
|
+
capabilities,
|
|
1559
1784
|
connected: true,
|
|
1560
1785
|
connectedAt: now,
|
|
1561
1786
|
disconnectedAt: '',
|
|
@@ -1584,10 +1809,13 @@ export function createRemoteHub(options = {}) {
|
|
|
1584
1809
|
sockets.set(socket, deviceId);
|
|
1585
1810
|
writeJsonLine(socket, {
|
|
1586
1811
|
type: 'welcome',
|
|
1812
|
+
protocol: REMOTE_AGENT_PROTOCOL,
|
|
1587
1813
|
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
1588
1814
|
sessionId,
|
|
1589
1815
|
deviceId,
|
|
1590
1816
|
heartbeatMs,
|
|
1817
|
+
frameProtocol: buildRemoteFrameProtocolDescriptor(),
|
|
1818
|
+
frameModes: getSupportedRemoteFrameModeProfiles(),
|
|
1591
1819
|
serverTime: now
|
|
1592
1820
|
});
|
|
1593
1821
|
|
|
@@ -2023,6 +2251,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2023
2251
|
const payload = buildFramePayloadBuffer(framePayload, frameData);
|
|
2024
2252
|
const contentHash = buildFrameContentHash(message, payload);
|
|
2025
2253
|
const sameContentStreak = computeSameContentStreak(device.latestThumbnail, contentHash);
|
|
2254
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, 'thumbnail');
|
|
2026
2255
|
device.latestThumbnail = {
|
|
2027
2256
|
streamId: safeString(message.streamId, 128) || 'thumbnail',
|
|
2028
2257
|
frameSeq,
|
|
@@ -2031,6 +2260,14 @@ export function createRemoteHub(options = {}) {
|
|
|
2031
2260
|
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
2032
2261
|
mimeType,
|
|
2033
2262
|
format: mimeType,
|
|
2263
|
+
frameMode: transfer.frameMode,
|
|
2264
|
+
frameProfile: transfer.frameProfile,
|
|
2265
|
+
encoding: transfer.encoding,
|
|
2266
|
+
codec: transfer.codec,
|
|
2267
|
+
compression: transfer.compression || mimeType,
|
|
2268
|
+
transferProtocol: transfer.transferProtocol,
|
|
2269
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
2270
|
+
handshake: transfer.handshake,
|
|
2034
2271
|
capturedAt,
|
|
2035
2272
|
receivedAt: device.lastSeenAt,
|
|
2036
2273
|
byteLength,
|
|
@@ -2055,6 +2292,49 @@ export function createRemoteHub(options = {}) {
|
|
|
2055
2292
|
return true;
|
|
2056
2293
|
}
|
|
2057
2294
|
|
|
2295
|
+
function applyLiveStreamOpen(device, message, transport = 'json') {
|
|
2296
|
+
const streamId = safeString(message.streamId, 128) || 'live';
|
|
2297
|
+
if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
|
|
2298
|
+
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
2299
|
+
reason: 'stale-live-stream-open',
|
|
2300
|
+
streamId,
|
|
2301
|
+
transport
|
|
2302
|
+
});
|
|
2303
|
+
return false;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, device.activeLiveStream.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
2307
|
+
const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
|
|
2308
|
+
device.activeLiveStream = {
|
|
2309
|
+
...device.activeLiveStream,
|
|
2310
|
+
open: true,
|
|
2311
|
+
openedAt,
|
|
2312
|
+
openTransport: transport,
|
|
2313
|
+
mode: transfer.mode,
|
|
2314
|
+
frameMode: transfer.frameMode,
|
|
2315
|
+
frameProfile: transfer.frameProfile,
|
|
2316
|
+
encoding: transfer.encoding,
|
|
2317
|
+
codec: transfer.codec,
|
|
2318
|
+
compression: transfer.compression,
|
|
2319
|
+
transferProtocol: transfer.transferProtocol,
|
|
2320
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
2321
|
+
handshake: transfer.handshake,
|
|
2322
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : Number(device.activeLiveStream.width || 0),
|
|
2323
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(device.activeLiveStream.height || 0),
|
|
2324
|
+
openedFrameSeq: Number.isFinite(Number(message.frameSeq)) ? Number(message.frameSeq) : Number(device.activeLiveStream.openedFrameSeq || 0)
|
|
2325
|
+
};
|
|
2326
|
+
emitRemoteEvent('RemoteLiveStreamOpened', device, {
|
|
2327
|
+
streamId,
|
|
2328
|
+
mode: transfer.mode,
|
|
2329
|
+
frameMode: transfer.frameMode,
|
|
2330
|
+
frameProfile: transfer.frameProfile,
|
|
2331
|
+
encoding: transfer.encoding,
|
|
2332
|
+
compression: transfer.compression,
|
|
2333
|
+
transport
|
|
2334
|
+
});
|
|
2335
|
+
return true;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2058
2338
|
function applyLiveFrame(device, message, framePayload, transport = 'json-base64') {
|
|
2059
2339
|
const frameData = Buffer.isBuffer(framePayload)
|
|
2060
2340
|
? ''
|
|
@@ -2091,6 +2371,21 @@ export function createRemoteHub(options = {}) {
|
|
|
2091
2371
|
const payload = buildFramePayloadBuffer(framePayload, frameData);
|
|
2092
2372
|
const contentHash = buildFrameContentHash(message, payload);
|
|
2093
2373
|
const sameContentStreak = computeSameContentStreak(device.latestLiveFrame, contentHash);
|
|
2374
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, device.activeLiveStream.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
2375
|
+
if (device.activeLiveStream.open !== true) {
|
|
2376
|
+
device.activeLiveStream.open = true;
|
|
2377
|
+
device.activeLiveStream.openedAt = device.lastSeenAt;
|
|
2378
|
+
device.activeLiveStream.openTransport = `${transport}-implicit`;
|
|
2379
|
+
device.activeLiveStream.mode = transfer.mode;
|
|
2380
|
+
device.activeLiveStream.frameMode = transfer.frameMode;
|
|
2381
|
+
device.activeLiveStream.frameProfile = transfer.frameProfile;
|
|
2382
|
+
device.activeLiveStream.encoding = transfer.encoding;
|
|
2383
|
+
device.activeLiveStream.codec = transfer.codec;
|
|
2384
|
+
device.activeLiveStream.compression = transfer.compression;
|
|
2385
|
+
device.activeLiveStream.transferProtocol = transfer.transferProtocol;
|
|
2386
|
+
device.activeLiveStream.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
2387
|
+
device.activeLiveStream.handshake = transfer.handshake;
|
|
2388
|
+
}
|
|
2094
2389
|
device.latestLiveFrame = {
|
|
2095
2390
|
streamId,
|
|
2096
2391
|
frameSeq,
|
|
@@ -2099,7 +2394,15 @@ export function createRemoteHub(options = {}) {
|
|
|
2099
2394
|
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
2100
2395
|
mimeType,
|
|
2101
2396
|
format: mimeType,
|
|
2102
|
-
mode:
|
|
2397
|
+
mode: transfer.mode,
|
|
2398
|
+
frameMode: transfer.frameMode,
|
|
2399
|
+
frameProfile: transfer.frameProfile,
|
|
2400
|
+
encoding: transfer.encoding,
|
|
2401
|
+
codec: transfer.codec,
|
|
2402
|
+
compression: transfer.compression || mimeType,
|
|
2403
|
+
transferProtocol: transfer.transferProtocol,
|
|
2404
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
2405
|
+
handshake: transfer.handshake,
|
|
2103
2406
|
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : device.activeLiveStream.fps,
|
|
2104
2407
|
capturedAt,
|
|
2105
2408
|
receivedAt: device.lastSeenAt,
|
|
@@ -2123,6 +2426,15 @@ export function createRemoteHub(options = {}) {
|
|
|
2123
2426
|
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
2124
2427
|
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
2125
2428
|
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
2429
|
+
device.activeLiveStream.mode = transfer.mode;
|
|
2430
|
+
device.activeLiveStream.frameMode = transfer.frameMode;
|
|
2431
|
+
device.activeLiveStream.frameProfile = transfer.frameProfile;
|
|
2432
|
+
device.activeLiveStream.encoding = transfer.encoding;
|
|
2433
|
+
device.activeLiveStream.codec = transfer.codec;
|
|
2434
|
+
device.activeLiveStream.compression = transfer.compression;
|
|
2435
|
+
device.activeLiveStream.transferProtocol = transfer.transferProtocol;
|
|
2436
|
+
device.activeLiveStream.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
2437
|
+
device.activeLiveStream.handshake = transfer.handshake;
|
|
2126
2438
|
device.counters.liveFramesReceived += 1;
|
|
2127
2439
|
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
2128
2440
|
streamId,
|
|
@@ -2237,6 +2549,10 @@ export function createRemoteHub(options = {}) {
|
|
|
2237
2549
|
applyThumbnailFrame(device, message, message.data, 'json-base64');
|
|
2238
2550
|
break;
|
|
2239
2551
|
}
|
|
2552
|
+
case 'stream.open': {
|
|
2553
|
+
applyLiveStreamOpen(device, message, 'json');
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2240
2556
|
case 'stream.frame': {
|
|
2241
2557
|
applyLiveFrame(device, message, message.data, 'json-base64');
|
|
2242
2558
|
break;
|
|
@@ -2895,12 +3211,24 @@ export function createRemoteHub(options = {}) {
|
|
|
2895
3211
|
const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
|
|
2896
3212
|
const fps = clampNumber(options.fps, 1, 24, 20);
|
|
2897
3213
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
3214
|
+
const transfer = buildRemoteFrameTransferDescriptor(options.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
2898
3215
|
device.counters.commandsSent += 1;
|
|
2899
3216
|
device.activeLiveStream = {
|
|
2900
3217
|
streamId,
|
|
2901
3218
|
commandId,
|
|
2902
3219
|
active: true,
|
|
2903
|
-
|
|
3220
|
+
open: true,
|
|
3221
|
+
openedAt: now,
|
|
3222
|
+
openTransport: 'synthetic',
|
|
3223
|
+
mode: transfer.mode,
|
|
3224
|
+
frameMode: transfer.frameMode,
|
|
3225
|
+
frameProfile: transfer.frameProfile,
|
|
3226
|
+
encoding: transfer.encoding,
|
|
3227
|
+
codec: transfer.codec,
|
|
3228
|
+
compression: transfer.compression,
|
|
3229
|
+
transferProtocol: transfer.transferProtocol,
|
|
3230
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3231
|
+
handshake: transfer.handshake,
|
|
2904
3232
|
fps,
|
|
2905
3233
|
startedAt: now,
|
|
2906
3234
|
stoppedAt: '',
|
|
@@ -2920,9 +3248,10 @@ export function createRemoteHub(options = {}) {
|
|
|
2920
3248
|
streamId,
|
|
2921
3249
|
commandId,
|
|
2922
3250
|
fps,
|
|
3251
|
+
mode: transfer.mode,
|
|
2923
3252
|
synthetic: true
|
|
2924
3253
|
});
|
|
2925
|
-
return { ok: true, commandId, streamId, fps, synthetic: true };
|
|
3254
|
+
return { ok: true, commandId, streamId, fps, mode: transfer.mode, frameMode: transfer.frameMode, synthetic: true };
|
|
2926
3255
|
}
|
|
2927
3256
|
|
|
2928
3257
|
if (!device?.socket || device.socket.destroyed || !device.connected) {
|
|
@@ -2936,12 +3265,21 @@ export function createRemoteHub(options = {}) {
|
|
|
2936
3265
|
const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
|
|
2937
3266
|
const fps = clampNumber(options.fps, 1, 24, 12);
|
|
2938
3267
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
3268
|
+
const transfer = buildRemoteFrameTransferDescriptor(options.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
2939
3269
|
const result = sendCommand(deviceId, {
|
|
2940
3270
|
command: 'stream.start',
|
|
2941
3271
|
commandId,
|
|
2942
3272
|
payload: {
|
|
2943
3273
|
streamId,
|
|
2944
|
-
mode:
|
|
3274
|
+
mode: transfer.mode,
|
|
3275
|
+
frameMode: transfer.frameMode,
|
|
3276
|
+
frameProfile: transfer.frameProfile,
|
|
3277
|
+
encoding: transfer.encoding,
|
|
3278
|
+
codec: transfer.codec,
|
|
3279
|
+
compression: transfer.compression,
|
|
3280
|
+
transferProtocol: transfer.transferProtocol,
|
|
3281
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3282
|
+
handshake: transfer.handshake,
|
|
2945
3283
|
fps,
|
|
2946
3284
|
maxWidth: clampNumber(options.maxWidth, 320, 2560, 960),
|
|
2947
3285
|
maxHeight: clampNumber(options.maxHeight, 180, 1440, 540),
|
|
@@ -2958,7 +3296,18 @@ export function createRemoteHub(options = {}) {
|
|
|
2958
3296
|
streamId,
|
|
2959
3297
|
commandId,
|
|
2960
3298
|
active: true,
|
|
2961
|
-
|
|
3299
|
+
open: false,
|
|
3300
|
+
openedAt: '',
|
|
3301
|
+
openTransport: '',
|
|
3302
|
+
mode: transfer.mode,
|
|
3303
|
+
frameMode: transfer.frameMode,
|
|
3304
|
+
frameProfile: transfer.frameProfile,
|
|
3305
|
+
encoding: transfer.encoding,
|
|
3306
|
+
codec: transfer.codec,
|
|
3307
|
+
compression: transfer.compression,
|
|
3308
|
+
transferProtocol: transfer.transferProtocol,
|
|
3309
|
+
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3310
|
+
handshake: transfer.handshake,
|
|
2962
3311
|
fps,
|
|
2963
3312
|
startedAt: now,
|
|
2964
3313
|
stoppedAt: '',
|
|
@@ -2971,10 +3320,12 @@ export function createRemoteHub(options = {}) {
|
|
|
2971
3320
|
emitRemoteEvent('RemoteLiveStreamStarted', device, {
|
|
2972
3321
|
streamId,
|
|
2973
3322
|
commandId,
|
|
2974
|
-
fps
|
|
3323
|
+
fps,
|
|
3324
|
+
mode: transfer.mode,
|
|
3325
|
+
frameMode: transfer.frameMode
|
|
2975
3326
|
});
|
|
2976
3327
|
|
|
2977
|
-
return { ok: true, commandId, streamId, fps };
|
|
3328
|
+
return { ok: true, commandId, streamId, fps, mode: transfer.mode, frameMode: transfer.frameMode };
|
|
2978
3329
|
}
|
|
2979
3330
|
|
|
2980
3331
|
function stopLiveStream(deviceId, options = {}) {
|
|
@@ -88,6 +88,25 @@ try {
|
|
|
88
88
|
pid: process.pid,
|
|
89
89
|
agentVersion: '0.0.0-ws-smoke',
|
|
90
90
|
runtime: 'node-smoke',
|
|
91
|
+
protocol: 'mindexec.remote.agent',
|
|
92
|
+
protocolVersion: 2,
|
|
93
|
+
frameProtocol: {
|
|
94
|
+
protocol: 'mindexec.remote.frame.v2',
|
|
95
|
+
version: 2,
|
|
96
|
+
handshake: 'smartview-request-open-frame',
|
|
97
|
+
defaultMode: 'remote-fast'
|
|
98
|
+
},
|
|
99
|
+
frameModes: [{
|
|
100
|
+
mode: 'remote-fast',
|
|
101
|
+
label: 'RemoteFast JPEG Binary',
|
|
102
|
+
transport: 'ws-binary',
|
|
103
|
+
encoding: 'image',
|
|
104
|
+
codec: 'jpeg',
|
|
105
|
+
compression: 'image/jpeg',
|
|
106
|
+
profile: 'jpeg-binary-v1',
|
|
107
|
+
modeVersion: 1,
|
|
108
|
+
implemented: true
|
|
109
|
+
}],
|
|
91
110
|
capabilities: {
|
|
92
111
|
status: true,
|
|
93
112
|
thumbnail: true,
|
|
@@ -109,13 +128,20 @@ try {
|
|
|
109
128
|
}
|
|
110
129
|
}));
|
|
111
130
|
|
|
112
|
-
await waitFor(() => received.find(item => item.type === 'welcome'), 5000, 'welcome');
|
|
131
|
+
const welcome = await waitFor(() => received.find(item => item.type === 'welcome'), 5000, 'welcome');
|
|
132
|
+
assert.equal(welcome.protocol, 'mindexec.remote.agent');
|
|
133
|
+
assert.equal(welcome.frameProtocol?.handshake, 'smartview-request-open-frame');
|
|
134
|
+
assert.equal(welcome.frameProtocol?.defaultMode, 'remote-fast');
|
|
135
|
+
assert.ok(welcome.frameModes?.some(item => item.mode === 'remote-fast'));
|
|
113
136
|
const device = await waitFor(() => {
|
|
114
137
|
const current = hub.listDevices();
|
|
115
138
|
return current.length === 1 && current[0].deviceId === DEVICE_ID ? current[0] : null;
|
|
116
139
|
}, 5000, 'device registration');
|
|
117
140
|
assert.equal(device.connected, true);
|
|
118
141
|
assert.equal(device.capabilities.binaryFrames, true);
|
|
142
|
+
assert.equal(device.protocol, 'mindexec.remote.agent');
|
|
143
|
+
assert.equal(device.frameProtocol?.handshake, 'smartview-request-open-frame');
|
|
144
|
+
assert.ok(device.frameModes?.some(item => item.mode === 'remote-fast'));
|
|
119
145
|
|
|
120
146
|
const inputCommand = hub.sendInputControl(DEVICE_ID, { type: 'noop' });
|
|
121
147
|
assert.equal(inputCommand.ok, true);
|
|
@@ -170,10 +196,36 @@ try {
|
|
|
170
196
|
quality: 50
|
|
171
197
|
});
|
|
172
198
|
assert.equal(liveCommand.ok, true);
|
|
173
|
-
await waitFor(
|
|
199
|
+
const liveMessage = await waitFor(
|
|
174
200
|
() => received.find(item => item.commandId === liveCommand.commandId),
|
|
175
201
|
5000,
|
|
176
202
|
'live command');
|
|
203
|
+
assert.equal(liveMessage.command, 'stream.start');
|
|
204
|
+
assert.equal(liveMessage.payload?.handshake, 'smartview-request-open-frame');
|
|
205
|
+
assert.equal(liveMessage.payload?.mode, 'remote-fast');
|
|
206
|
+
assert.equal(liveMessage.payload?.frameMode, 'remote-fast');
|
|
207
|
+
assert.equal(liveMessage.payload?.frameProfile, 'jpeg-binary-v1');
|
|
208
|
+
assert.equal(liveMessage.payload?.compression, 'image/jpeg');
|
|
209
|
+
assert.equal(liveMessage.payload?.transferProtocol, 'mindexec.remote.frame.v2');
|
|
210
|
+
ws.send(JSON.stringify({
|
|
211
|
+
type: 'stream.open',
|
|
212
|
+
commandId: liveCommand.commandId,
|
|
213
|
+
streamId: 'remote-agent-ws-live',
|
|
214
|
+
mode: 'remote-fast',
|
|
215
|
+
frameMode: 'remote-fast',
|
|
216
|
+
frameProfile: 'jpeg-binary-v1',
|
|
217
|
+
encoding: 'image',
|
|
218
|
+
codec: 'png',
|
|
219
|
+
compression: 'image/png',
|
|
220
|
+
transferProtocol: 'mindexec.remote.frame.v2',
|
|
221
|
+
transferProtocolVersion: 2,
|
|
222
|
+
handshake: 'smartview-request-open-frame',
|
|
223
|
+
fps: 10,
|
|
224
|
+
width: 2,
|
|
225
|
+
height: 1,
|
|
226
|
+
openedAt: new Date().toISOString()
|
|
227
|
+
}));
|
|
228
|
+
await waitFor(() => hub.listDevices()[0]?.activeLiveStream?.open === true, 5000, 'live stream open');
|
|
177
229
|
writeAgentBinaryFrame(ws, {
|
|
178
230
|
frameKind: 'stream',
|
|
179
231
|
commandId: liveCommand.commandId,
|
|
@@ -182,6 +234,15 @@ try {
|
|
|
182
234
|
width: 2,
|
|
183
235
|
height: 1,
|
|
184
236
|
mimeType: 'image/png',
|
|
237
|
+
mode: 'remote-fast',
|
|
238
|
+
frameMode: 'remote-fast',
|
|
239
|
+
frameProfile: 'jpeg-binary-v1',
|
|
240
|
+
encoding: 'image',
|
|
241
|
+
codec: 'png',
|
|
242
|
+
compression: 'image/png',
|
|
243
|
+
transferProtocol: 'mindexec.remote.frame.v2',
|
|
244
|
+
transferProtocolVersion: 2,
|
|
245
|
+
handshake: 'smartview-request-open-frame',
|
|
185
246
|
fps: 10,
|
|
186
247
|
capturedAt: new Date().toISOString()
|
|
187
248
|
}, SMOKE_PNG);
|
|
@@ -191,6 +252,12 @@ try {
|
|
|
191
252
|
}, 5000, 'websocket binary live frame');
|
|
192
253
|
assert.equal(liveDevice.latestLiveFrame.transport, 'binary');
|
|
193
254
|
assert.equal(liveDevice.latestLiveFrame.streamId, 'remote-agent-ws-live');
|
|
255
|
+
assert.equal(liveDevice.activeLiveStream.open, true);
|
|
256
|
+
assert.equal(liveDevice.activeLiveStream.frameMode, 'remote-fast');
|
|
257
|
+
assert.equal(liveDevice.latestLiveFrame.frameMode, 'remote-fast');
|
|
258
|
+
assert.equal(liveDevice.latestLiveFrame.frameProfile, 'jpeg-binary-v1');
|
|
259
|
+
assert.equal(liveDevice.latestLiveFrame.compression, 'image/png');
|
|
260
|
+
assert.equal(liveDevice.latestLiveFrame.transferProtocol, 'mindexec.remote.frame.v2');
|
|
194
261
|
|
|
195
262
|
ws.close();
|
|
196
263
|
console.log('RemoteAgent WebSocket smoke OK');
|
|
@@ -214,6 +214,23 @@ try {
|
|
|
214
214
|
quality: 50
|
|
215
215
|
});
|
|
216
216
|
assert.equal(liveCommand.ok, true);
|
|
217
|
+
assert.equal(liveCommand.frameMode, 'remote-fast');
|
|
218
|
+
writeJsonLine(socket, {
|
|
219
|
+
type: 'stream.open',
|
|
220
|
+
commandId: liveCommand.commandId,
|
|
221
|
+
streamId: 'smoke-live',
|
|
222
|
+
mode: 'remote-fast',
|
|
223
|
+
frameMode: 'remote-fast',
|
|
224
|
+
frameProfile: 'jpeg-binary-v1',
|
|
225
|
+
encoding: 'image',
|
|
226
|
+
codec: 'png',
|
|
227
|
+
compression: 'image/png',
|
|
228
|
+
transferProtocol: 'mindexec.remote.frame.v2',
|
|
229
|
+
transferProtocolVersion: 2,
|
|
230
|
+
handshake: 'smartview-request-open-frame',
|
|
231
|
+
fps: 5,
|
|
232
|
+
openedAt: new Date().toISOString()
|
|
233
|
+
});
|
|
217
234
|
writeJsonLine(socket, {
|
|
218
235
|
type: 'stream.frame',
|
|
219
236
|
commandId: liveCommand.commandId,
|
|
@@ -223,6 +240,14 @@ try {
|
|
|
223
240
|
height: 1,
|
|
224
241
|
mimeType: 'image/png',
|
|
225
242
|
mode: 'remote-fast',
|
|
243
|
+
frameMode: 'remote-fast',
|
|
244
|
+
frameProfile: 'jpeg-binary-v1',
|
|
245
|
+
encoding: 'image',
|
|
246
|
+
codec: 'png',
|
|
247
|
+
compression: 'image/png',
|
|
248
|
+
transferProtocol: 'mindexec.remote.frame.v2',
|
|
249
|
+
transferProtocolVersion: 2,
|
|
250
|
+
handshake: 'smartview-request-open-frame',
|
|
226
251
|
fps: 5,
|
|
227
252
|
capturedAt: new Date().toISOString(),
|
|
228
253
|
data: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC'
|
|
@@ -233,7 +258,11 @@ try {
|
|
|
233
258
|
return current[0]?.latestLiveFrame?.streamId === 'smoke-live' ? current[0] : null;
|
|
234
259
|
});
|
|
235
260
|
assert.equal(liveDevice.activeLiveStream.active, true);
|
|
261
|
+
assert.equal(liveDevice.activeLiveStream.open, true);
|
|
262
|
+
assert.equal(liveDevice.activeLiveStream.handshake, 'smartview-request-open-frame');
|
|
236
263
|
assert.equal(liveDevice.latestLiveFrame.mode, 'remote-fast');
|
|
264
|
+
assert.equal(liveDevice.latestLiveFrame.frameMode, 'remote-fast');
|
|
265
|
+
assert.equal(liveDevice.latestLiveFrame.compression, 'image/png');
|
|
237
266
|
assert.equal(liveDevice.counters.liveFramesReceived, 1);
|
|
238
267
|
const serializedFirstLiveFrame = hub.getDeviceLiveFrame('smoke-device', { includeDataUrl: false });
|
|
239
268
|
const serializedFirstLiveUrl = new URL(serializedFirstLiveFrame.framePath, 'http://127.0.0.1');
|
|
@@ -247,6 +276,14 @@ try {
|
|
|
247
276
|
height: 1,
|
|
248
277
|
mimeType: 'image/png',
|
|
249
278
|
mode: 'remote-fast',
|
|
279
|
+
frameMode: 'remote-fast',
|
|
280
|
+
frameProfile: 'jpeg-binary-v1',
|
|
281
|
+
encoding: 'image',
|
|
282
|
+
codec: 'png',
|
|
283
|
+
compression: 'image/png',
|
|
284
|
+
transferProtocol: 'mindexec.remote.frame.v2',
|
|
285
|
+
transferProtocolVersion: 2,
|
|
286
|
+
handshake: 'smartview-request-open-frame',
|
|
250
287
|
fps: 5,
|
|
251
288
|
capturedAt: new Date().toISOString()
|
|
252
289
|
}, smokePngFrame);
|
|
@@ -265,6 +302,8 @@ try {
|
|
|
265
302
|
const serializedBinaryLiveFrame = hub.getDeviceLiveFrame('smoke-device', { includeDataUrl: false });
|
|
266
303
|
assert.equal(serializedBinaryLiveFrame.streamId, 'smoke-live');
|
|
267
304
|
assert.equal(serializedBinaryLiveFrame.frameSeq, 5);
|
|
305
|
+
assert.equal(serializedBinaryLiveFrame.frameMode, 'remote-fast');
|
|
306
|
+
assert.equal(serializedBinaryLiveFrame.compression, 'image/png');
|
|
268
307
|
assert.equal(serializedBinaryLiveFrame.contentHash, binaryLiveDevice.latestLiveFrame.contentHash);
|
|
269
308
|
assert.ok(serializedBinaryLiveFrame.framePath.includes('/api/remote/devices/smoke-device/live/frame?'));
|
|
270
309
|
assert.ok(!('dataUrl' in serializedBinaryLiveFrame));
|
package/server.js
CHANGED
|
@@ -2550,12 +2550,14 @@ function clampRemoteFrameWsNumber(value, min, max, fallback) {
|
|
|
2550
2550
|
}
|
|
2551
2551
|
|
|
2552
2552
|
function normalizeRemoteFrameWsLiveOptions(payload = {}) {
|
|
2553
|
+
const mode = String(payload.frameMode || payload.mode || 'remote-fast').trim() || 'remote-fast';
|
|
2553
2554
|
return {
|
|
2554
2555
|
fps: clampRemoteFrameWsNumber(payload.fps, 1, 24, REMOTE_FRAME_WS_DEFAULT_FPS),
|
|
2555
2556
|
maxWidth: clampRemoteFrameWsNumber(payload.maxWidth, 320, 2560, REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH),
|
|
2556
2557
|
maxHeight: clampRemoteFrameWsNumber(payload.maxHeight, 180, 1440, REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT),
|
|
2557
2558
|
quality: clampRemoteFrameWsNumber(payload.quality, 20, 95, REMOTE_FRAME_WS_DEFAULT_QUALITY),
|
|
2558
|
-
mode
|
|
2559
|
+
mode,
|
|
2560
|
+
frameMode: mode
|
|
2559
2561
|
};
|
|
2560
2562
|
}
|
|
2561
2563
|
|
|
@@ -2671,6 +2673,14 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
2671
2673
|
height: Number(frame.height || 0) || 0,
|
|
2672
2674
|
mimeType: frameEvent.mimeType || frame.mimeType || frame.format || 'image/jpeg',
|
|
2673
2675
|
mode: frame.mode || '',
|
|
2676
|
+
frameMode: frame.frameMode || frame.mode || '',
|
|
2677
|
+
frameProfile: frame.frameProfile || '',
|
|
2678
|
+
encoding: frame.encoding || '',
|
|
2679
|
+
codec: frame.codec || '',
|
|
2680
|
+
compression: frame.compression || frame.mimeType || frame.format || '',
|
|
2681
|
+
transferProtocol: frame.transferProtocol || '',
|
|
2682
|
+
transferProtocolVersion: Number(frame.transferProtocolVersion || 0) || 0,
|
|
2683
|
+
handshake: frame.handshake || '',
|
|
2674
2684
|
fps: Number(frame.fps || 0) || 0,
|
|
2675
2685
|
capturedAt: frame.capturedAt || '',
|
|
2676
2686
|
receivedAt: frame.receivedAt || '',
|