@apocaliss92/scrypted-onvif-rebroadcast 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/.vscode/launch.json +0 -23
- package/.vscode/settings.json +0 -4
- package/.vscode/tasks.json +0 -20
- package/src/cameraMixin.ts +0 -419
- package/src/main.ts +0 -107
- package/src/onvifServer.ts +0 -1324
- package/src/types.ts +0 -41
package/dist/plugin.zip
ADDED
|
Binary file
|
package/package.json
CHANGED
package/.vscode/launch.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Use IntelliSense to learn about possible attributes.
|
|
3
|
-
// Hover to view descriptions of existing attributes.
|
|
4
|
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
-
"version": "0.2.0",
|
|
6
|
-
"configurations": [
|
|
7
|
-
{
|
|
8
|
-
"name": "Scrypted Debugger",
|
|
9
|
-
"address": "${config:scrypted.debugHost}",
|
|
10
|
-
"port": 10081,
|
|
11
|
-
"request": "attach",
|
|
12
|
-
"skipFiles": [
|
|
13
|
-
"**/plugin-remote-worker.*",
|
|
14
|
-
"<node_internals>/**"
|
|
15
|
-
],
|
|
16
|
-
"preLaunchTask": "scrypted: deploy+debug",
|
|
17
|
-
"sourceMaps": true,
|
|
18
|
-
"localRoot": "${workspaceFolder}/out",
|
|
19
|
-
"remoteRoot": "/plugin/",
|
|
20
|
-
"type": "node"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
package/.vscode/settings.json
DELETED
package/.vscode/tasks.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
3
|
-
// for the documentation about the tasks.json format
|
|
4
|
-
"version": "2.0.0",
|
|
5
|
-
"tasks": [
|
|
6
|
-
{
|
|
7
|
-
"label": "scrypted: deploy+debug",
|
|
8
|
-
"type": "shell",
|
|
9
|
-
"presentation": {
|
|
10
|
-
"echo": true,
|
|
11
|
-
"reveal": "silent",
|
|
12
|
-
"focus": false,
|
|
13
|
-
"panel": "shared",
|
|
14
|
-
"showReuseMessage": true,
|
|
15
|
-
"clear": false
|
|
16
|
-
},
|
|
17
|
-
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
|
18
|
-
},
|
|
19
|
-
]
|
|
20
|
-
}
|
package/src/cameraMixin.ts
DELETED
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
import sdk, {
|
|
2
|
-
EventListenerRegister,
|
|
3
|
-
ObjectsDetected,
|
|
4
|
-
Setting,
|
|
5
|
-
SettingValue,
|
|
6
|
-
ScryptedInterface,
|
|
7
|
-
Settings,
|
|
8
|
-
} from "@scrypted/sdk";
|
|
9
|
-
import {
|
|
10
|
-
SettingsMixinDeviceBase,
|
|
11
|
-
SettingsMixinDeviceOptions,
|
|
12
|
-
} from "@scrypted/sdk/settings-mixin";
|
|
13
|
-
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
14
|
-
import { OnvifServer } from "./onvifServer";
|
|
15
|
-
import {
|
|
16
|
-
RtspStreamInfo,
|
|
17
|
-
OnvifServiceConfig,
|
|
18
|
-
DeviceCapabilities,
|
|
19
|
-
OnvifEvent,
|
|
20
|
-
} from "./types";
|
|
21
|
-
import os from "os";
|
|
22
|
-
|
|
23
|
-
import type OnvifRebroadcastPlugin from "./main";
|
|
24
|
-
|
|
25
|
-
const { systemManager } = sdk;
|
|
26
|
-
|
|
27
|
-
function getLocalIp(): string {
|
|
28
|
-
const interfaces = os.networkInterfaces();
|
|
29
|
-
for (const name of Object.keys(interfaces)) {
|
|
30
|
-
for (const iface of interfaces[name] ?? []) {
|
|
31
|
-
if (iface.family === "IPv4" && !iface.internal) {
|
|
32
|
-
return iface.address;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return "127.0.0.1";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class OnvifRebroadcastCameraMixin extends SettingsMixinDeviceBase<any> {
|
|
40
|
-
private plugin: OnvifRebroadcastPlugin;
|
|
41
|
-
private logger: {
|
|
42
|
-
log: (...args: any[]) => void;
|
|
43
|
-
debug: (...args: any[]) => void;
|
|
44
|
-
warn: (...args: any[]) => void;
|
|
45
|
-
error: (...args: any[]) => void;
|
|
46
|
-
};
|
|
47
|
-
private onvifServer: OnvifServer | null = null;
|
|
48
|
-
private discoveredStreams: RtspStreamInfo[] = [];
|
|
49
|
-
private assignedPort: number = 0;
|
|
50
|
-
private killed = false;
|
|
51
|
-
private motionListener: EventListenerRegister | null = null;
|
|
52
|
-
private detectionListener: EventListenerRegister | null = null;
|
|
53
|
-
|
|
54
|
-
storageSettings = new StorageSettings(this, {
|
|
55
|
-
onvifPort: {
|
|
56
|
-
title: "ONVIF port",
|
|
57
|
-
description:
|
|
58
|
-
"Port for this camera ONVIF server (leave empty for auto, each camera needs a unique port)",
|
|
59
|
-
type: "number",
|
|
60
|
-
},
|
|
61
|
-
serverEnabled: {
|
|
62
|
-
title: "ONVIF server enabled",
|
|
63
|
-
type: "boolean",
|
|
64
|
-
defaultValue: true,
|
|
65
|
-
immediate: true,
|
|
66
|
-
onPut: async (_oldValue, newValue) => {
|
|
67
|
-
if (newValue) {
|
|
68
|
-
await this.discoverStreams();
|
|
69
|
-
await this.startOnvifServer();
|
|
70
|
-
} else {
|
|
71
|
-
await this.stopOnvifServer();
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
debugEvents: {
|
|
76
|
-
title: "Debug events",
|
|
77
|
-
description:
|
|
78
|
-
"Enable verbose logging for events (motion, object detection)",
|
|
79
|
-
type: "boolean",
|
|
80
|
-
defaultValue: false,
|
|
81
|
-
immediate: true,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
constructor(
|
|
86
|
-
options: SettingsMixinDeviceOptions<any>,
|
|
87
|
-
plugin: OnvifRebroadcastPlugin,
|
|
88
|
-
) {
|
|
89
|
-
super(options);
|
|
90
|
-
this.plugin = plugin;
|
|
91
|
-
this.logger = {
|
|
92
|
-
log: (message: string, ...args: any[]) =>
|
|
93
|
-
this.console.log(message, ...args),
|
|
94
|
-
debug: (message: string, ...args: any[]) => {
|
|
95
|
-
if (this.storageSettings.values.debugEvents)
|
|
96
|
-
this.console.log(`[DEBUG] ${message}`, ...args);
|
|
97
|
-
},
|
|
98
|
-
warn: (message: string, ...args: any[]) =>
|
|
99
|
-
this.console.warn(message, ...args),
|
|
100
|
-
error: (message: string, ...args: any[]) =>
|
|
101
|
-
this.console.error(message, ...args),
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
setTimeout(() => this.init(), 5000);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private async init() {
|
|
108
|
-
if (this.killed) return;
|
|
109
|
-
|
|
110
|
-
this.console.log(`ONVIF Rebroadcast mixin initialized for ${this.name}`);
|
|
111
|
-
|
|
112
|
-
await this.discoverStreams();
|
|
113
|
-
|
|
114
|
-
if (this.killed) return;
|
|
115
|
-
|
|
116
|
-
if (this.storageSettings.values.serverEnabled) {
|
|
117
|
-
await this.startOnvifServer();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async getMixinSettings(): Promise<Setting[]> {
|
|
122
|
-
const settings = await this.storageSettings.getSettings();
|
|
123
|
-
|
|
124
|
-
if (this.assignedPort && this.onvifServer?.isRunning) {
|
|
125
|
-
const localIp = getLocalIp();
|
|
126
|
-
const baseUrl = `http://${localIp}:${this.assignedPort}/onvif`;
|
|
127
|
-
|
|
128
|
-
settings.push({
|
|
129
|
-
key: 'deviceServiceUrl',
|
|
130
|
-
title: 'ONVIF Device Service Url',
|
|
131
|
-
description: `${baseUrl}/device_service`,
|
|
132
|
-
value: `${baseUrl}/device_service`,
|
|
133
|
-
type: 'string',
|
|
134
|
-
readonly: true,
|
|
135
|
-
subgroup: 'Service URLs',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
settings.push({
|
|
139
|
-
key: 'mediaServiceUrl',
|
|
140
|
-
title: 'ONVIF Media Service Url',
|
|
141
|
-
description: `${baseUrl}/media_service`,
|
|
142
|
-
value: `${baseUrl}/media_service`,
|
|
143
|
-
type: 'string',
|
|
144
|
-
readonly: true,
|
|
145
|
-
subgroup: 'Service URLs',
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return settings;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async putMixinSetting(key: string, value: SettingValue): Promise<void> {
|
|
154
|
-
await this.storageSettings.putSetting(key, value);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Discover RTSP rebroadcast streams from Scrypted for this camera.
|
|
159
|
-
*/
|
|
160
|
-
private async discoverStreams() {
|
|
161
|
-
this.discoveredStreams = [];
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const device = systemManager.getDeviceById(
|
|
165
|
-
this.id,
|
|
166
|
-
) as unknown as Settings;
|
|
167
|
-
if (!device?.getSettings) return;
|
|
168
|
-
|
|
169
|
-
const deviceSettings = await device.getSettings();
|
|
170
|
-
const rtspSettings = deviceSettings.filter(
|
|
171
|
-
(setting) => setting.title === "RTSP Rebroadcast Url",
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
// Also try to get video stream options for resolution info
|
|
175
|
-
let streamOptions: any[] = [];
|
|
176
|
-
try {
|
|
177
|
-
const videoDevice = systemManager.getDeviceById(this.id) as any;
|
|
178
|
-
if (videoDevice?.getVideoStreamOptions) {
|
|
179
|
-
streamOptions = await videoDevice.getVideoStreamOptions();
|
|
180
|
-
}
|
|
181
|
-
} catch {
|
|
182
|
-
/* ignore */
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const setting of rtspSettings) {
|
|
186
|
-
const rtspUrl = setting.value as string;
|
|
187
|
-
if (!rtspUrl) continue;
|
|
188
|
-
|
|
189
|
-
const streamName =
|
|
190
|
-
setting.subgroup?.replace("Stream: ", "") ?? "Default";
|
|
191
|
-
|
|
192
|
-
// Replace localhost with actual IP so external clients can reach it
|
|
193
|
-
const localIp = getLocalIp();
|
|
194
|
-
const resolvedUrl = rtspUrl.replace("localhost", localIp);
|
|
195
|
-
|
|
196
|
-
// Try to find resolution from stream options
|
|
197
|
-
const matchedOption = streamOptions.find((s) => s.name === streamName);
|
|
198
|
-
const width = matchedOption?.video?.width;
|
|
199
|
-
const height = matchedOption?.video?.height;
|
|
200
|
-
|
|
201
|
-
this.discoveredStreams.push({
|
|
202
|
-
name: streamName,
|
|
203
|
-
rtspUrl: resolvedUrl,
|
|
204
|
-
width,
|
|
205
|
-
height,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this.logger.debug(
|
|
210
|
-
`${this.name}: found ${this.discoveredStreams.length} RTSP rebroadcast stream(s)`,
|
|
211
|
-
);
|
|
212
|
-
for (const s of this.discoveredStreams) {
|
|
213
|
-
this.logger.debug(
|
|
214
|
-
` - ${s.name}: ${s.rtspUrl} (${s.width ?? "?"}x${s.height ?? "?"})`,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
} catch (e) {
|
|
218
|
-
this.console.warn(
|
|
219
|
-
`Failed to discover streams for ${this.name}: ${(e as Error).message}`,
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Detect which Scrypted interfaces this device supports and map to ONVIF capabilities.
|
|
226
|
-
*/
|
|
227
|
-
private detectCapabilities(): DeviceCapabilities {
|
|
228
|
-
const device = systemManager.getDeviceById(this.id);
|
|
229
|
-
const interfaces = device?.interfaces ?? this.mixinDeviceInterfaces;
|
|
230
|
-
const has = (iface: ScryptedInterface) => interfaces.includes(iface);
|
|
231
|
-
|
|
232
|
-
this.logger.debug(`${this.name} interfaces: ${interfaces.join(", ")}`);
|
|
233
|
-
|
|
234
|
-
const capabilities: DeviceCapabilities = {
|
|
235
|
-
hasPtz: has(ScryptedInterface.PanTiltZoom),
|
|
236
|
-
hasIntercom: has(ScryptedInterface.Intercom),
|
|
237
|
-
hasMotionSensor: has(ScryptedInterface.MotionSensor),
|
|
238
|
-
hasAudioSensor: has(ScryptedInterface.AudioSensor),
|
|
239
|
-
hasObjectDetection: has(ScryptedInterface.ObjectDetector),
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Get PTZ sub-capabilities
|
|
243
|
-
if (capabilities.hasPtz) {
|
|
244
|
-
try {
|
|
245
|
-
const device = systemManager.getDeviceById(this.id) as any;
|
|
246
|
-
const ptzCaps = device?.ptzCapabilities;
|
|
247
|
-
if (ptzCaps) {
|
|
248
|
-
capabilities.ptzCapabilities = {
|
|
249
|
-
pan: ptzCaps.pan,
|
|
250
|
-
tilt: ptzCaps.tilt,
|
|
251
|
-
zoom: ptzCaps.zoom,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
} catch {
|
|
255
|
-
/* ignore */
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.logger.debug(
|
|
260
|
-
`${this.name} capabilities: PTZ=${capabilities.hasPtz}, Intercom=${capabilities.hasIntercom}, Motion=${capabilities.hasMotionSensor}, Audio=${capabilities.hasAudioSensor}, ObjectDetect=${capabilities.hasObjectDetection}`,
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
return capabilities;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Start the ONVIF server for this camera.
|
|
268
|
-
* This makes the camera discoverable via ONVIF WS-Discovery and
|
|
269
|
-
* serves GetProfiles/GetStreamUri with the Scrypted rebroadcast RTSP URLs.
|
|
270
|
-
*/
|
|
271
|
-
private async startOnvifServer() {
|
|
272
|
-
await this.stopOnvifServer();
|
|
273
|
-
|
|
274
|
-
if (this.discoveredStreams.length === 0) {
|
|
275
|
-
this.logger.debug(
|
|
276
|
-
`No streams discovered for ${this.name}, trying to discover...`,
|
|
277
|
-
);
|
|
278
|
-
await this.discoverStreams();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.discoveredStreams.length === 0) {
|
|
282
|
-
this.console.warn(
|
|
283
|
-
`No RTSP rebroadcast streams found for ${this.name}. Make sure the Rebroadcast plugin is installed.`,
|
|
284
|
-
);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const port = (this.storageSettings.values.onvifPort as number) || 0;
|
|
289
|
-
const localIp = getLocalIp();
|
|
290
|
-
|
|
291
|
-
const username = this.plugin.storageSettings.values.username as string;
|
|
292
|
-
const password = this.plugin.storageSettings.values.password as string;
|
|
293
|
-
|
|
294
|
-
const capabilities = this.detectCapabilities();
|
|
295
|
-
|
|
296
|
-
// Use actual device info from Scrypted
|
|
297
|
-
const device = systemManager.getDeviceById(this.id);
|
|
298
|
-
const deviceInfo = device?.info;
|
|
299
|
-
|
|
300
|
-
const config: OnvifServiceConfig = {
|
|
301
|
-
deviceName: device?.name || this.name,
|
|
302
|
-
deviceId: this.id,
|
|
303
|
-
manufacturer: deviceInfo?.manufacturer || "Unknown",
|
|
304
|
-
model: deviceInfo?.model || "Unknown",
|
|
305
|
-
firmwareVersion: deviceInfo?.firmware || deviceInfo?.version || "1.0.0",
|
|
306
|
-
serialNumber: deviceInfo?.serialNumber || `scrypted-${this.id}`,
|
|
307
|
-
hostname: localIp,
|
|
308
|
-
onvifPort: port,
|
|
309
|
-
streams: this.discoveredStreams,
|
|
310
|
-
username: username || undefined,
|
|
311
|
-
password: password || undefined,
|
|
312
|
-
capabilities,
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
this.onvifServer = new OnvifServer(this.console, config);
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
this.assignedPort = await this.onvifServer.start(port);
|
|
319
|
-
|
|
320
|
-
// Save the assigned port to settings so it persists across restarts
|
|
321
|
-
if (this.assignedPort !== port) {
|
|
322
|
-
await this.storageSettings.putSetting("onvifPort", this.assignedPort);
|
|
323
|
-
this.console.log(
|
|
324
|
-
`Saved assigned port ${this.assignedPort} to settings for ${this.name}`,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.console.log(
|
|
329
|
-
`ONVIF device "${this.name}" available at http://${localIp}:${this.assignedPort}/onvif/device_service`,
|
|
330
|
-
);
|
|
331
|
-
this.logger.debug(`Camera is now discoverable via ONVIF WS-Discovery`);
|
|
332
|
-
|
|
333
|
-
this.startEventListeners(capabilities);
|
|
334
|
-
} catch (e) {
|
|
335
|
-
this.console.error(
|
|
336
|
-
`Failed to start ONVIF server for ${this.name}`,
|
|
337
|
-
(e as Error).message,
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Start listening to Scrypted device events and forward them to the ONVIF event queue.
|
|
344
|
-
*/
|
|
345
|
-
private startEventListeners(capabilities: DeviceCapabilities) {
|
|
346
|
-
this.stopEventListeners();
|
|
347
|
-
|
|
348
|
-
if (capabilities.hasMotionSensor) {
|
|
349
|
-
this.motionListener = systemManager.listenDevice(
|
|
350
|
-
this.id,
|
|
351
|
-
{ event: ScryptedInterface.MotionSensor },
|
|
352
|
-
(_source, _eventDetails, data) => {
|
|
353
|
-
const motionActive = !!data;
|
|
354
|
-
this.logger.debug(`${this.name} motion: ${motionActive}`);
|
|
355
|
-
this.onvifServer?.pushEvent({
|
|
356
|
-
topic: "tns1:VideoSource/MotionAlarm",
|
|
357
|
-
timestamp: new Date(),
|
|
358
|
-
source: `video_src_0`,
|
|
359
|
-
data: { State: motionActive },
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
);
|
|
363
|
-
this.logger.debug(`Motion event listener started for ${this.name}`);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (capabilities.hasObjectDetection) {
|
|
367
|
-
this.detectionListener = systemManager.listenDevice(
|
|
368
|
-
this.id,
|
|
369
|
-
{ event: ScryptedInterface.ObjectDetector },
|
|
370
|
-
(_source, _eventDetails, data) => {
|
|
371
|
-
const detected: ObjectsDetected = data;
|
|
372
|
-
if (!detected?.detections?.length) return;
|
|
373
|
-
|
|
374
|
-
for (const detection of detected.detections) {
|
|
375
|
-
this.logger.debug(
|
|
376
|
-
`${this.name} detection: ${detection.className} (${((detection.score ?? 0) * 100).toFixed(0)}%)`,
|
|
377
|
-
);
|
|
378
|
-
this.onvifServer?.pushEvent({
|
|
379
|
-
topic: "tns1:RuleEngine/ObjectDetector/ObjectDetection",
|
|
380
|
-
timestamp: new Date(),
|
|
381
|
-
source: `video_src_0`,
|
|
382
|
-
data: {
|
|
383
|
-
ObjectType: detection.className ?? "unknown",
|
|
384
|
-
IsMotion: detection.className === "motion",
|
|
385
|
-
Score: detection.score ?? 0,
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
);
|
|
391
|
-
this.logger.debug(
|
|
392
|
-
`Object detection event listener started for ${this.name}`,
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
private stopEventListeners() {
|
|
398
|
-
this.motionListener?.removeListener();
|
|
399
|
-
this.motionListener = null;
|
|
400
|
-
this.detectionListener?.removeListener();
|
|
401
|
-
this.detectionListener = null;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
private async stopOnvifServer() {
|
|
405
|
-
this.stopEventListeners();
|
|
406
|
-
if (this.onvifServer) {
|
|
407
|
-
await this.onvifServer.stop();
|
|
408
|
-
this.onvifServer = null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async release() {
|
|
413
|
-
if (this.killed) return;
|
|
414
|
-
this.killed = true;
|
|
415
|
-
this.console.log(`Releasing ONVIF mixin for ${this.name}`);
|
|
416
|
-
await this.stopOnvifServer();
|
|
417
|
-
super.release();
|
|
418
|
-
}
|
|
419
|
-
}
|
package/src/main.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import sdk, {
|
|
2
|
-
MixinProvider,
|
|
3
|
-
ScryptedDeviceBase,
|
|
4
|
-
ScryptedDeviceType,
|
|
5
|
-
ScryptedInterface,
|
|
6
|
-
Setting,
|
|
7
|
-
Settings,
|
|
8
|
-
SettingValue,
|
|
9
|
-
WritableDeviceState,
|
|
10
|
-
} from "@scrypted/sdk";
|
|
11
|
-
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
12
|
-
import { OnvifRebroadcastCameraMixin } from "./cameraMixin";
|
|
13
|
-
|
|
14
|
-
export default class OnvifRebroadcastPlugin
|
|
15
|
-
extends ScryptedDeviceBase
|
|
16
|
-
implements Settings, MixinProvider
|
|
17
|
-
{
|
|
18
|
-
currentMixinsMap: Record<string, OnvifRebroadcastCameraMixin> = {};
|
|
19
|
-
|
|
20
|
-
storageSettings = new StorageSettings(this, {
|
|
21
|
-
username: {
|
|
22
|
-
title: "Username",
|
|
23
|
-
description:
|
|
24
|
-
"Username for ONVIF authentication (leave empty to disable auth)",
|
|
25
|
-
type: "string",
|
|
26
|
-
group: "Authentication",
|
|
27
|
-
},
|
|
28
|
-
password: {
|
|
29
|
-
title: "Password",
|
|
30
|
-
description: "Password for ONVIF authentication",
|
|
31
|
-
type: "password",
|
|
32
|
-
group: "Authentication",
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
constructor(nativeId: string) {
|
|
37
|
-
super(nativeId);
|
|
38
|
-
this.console.log("ONVIF Rebroadcast plugin loaded");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async getSettings(): Promise<Setting[]> {
|
|
42
|
-
return this.storageSettings.getSettings();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async putSetting(key: string, value: SettingValue): Promise<void> {
|
|
46
|
-
await this.storageSettings.putSetting(key, value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async canMixin(
|
|
50
|
-
type: ScryptedDeviceType,
|
|
51
|
-
interfaces: string[],
|
|
52
|
-
): Promise<string[]> {
|
|
53
|
-
if (
|
|
54
|
-
(type === ScryptedDeviceType.Camera ||
|
|
55
|
-
type === ScryptedDeviceType.Doorbell) &&
|
|
56
|
-
(interfaces.includes(ScryptedInterface.VideoCamera) ||
|
|
57
|
-
interfaces.includes(ScryptedInterface.Camera))
|
|
58
|
-
) {
|
|
59
|
-
return [ScryptedInterface.Settings];
|
|
60
|
-
}
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async getMixin(
|
|
65
|
-
mixinDevice: any,
|
|
66
|
-
mixinDeviceInterfaces: ScryptedInterface[],
|
|
67
|
-
mixinDeviceState: WritableDeviceState,
|
|
68
|
-
): Promise<any> {
|
|
69
|
-
const existing = this.currentMixinsMap[mixinDeviceState.id];
|
|
70
|
-
if (existing) {
|
|
71
|
-
this.console.log(
|
|
72
|
-
`Releasing previous mixin for ${mixinDeviceState.name} before creating new one`,
|
|
73
|
-
);
|
|
74
|
-
try {
|
|
75
|
-
await existing.release();
|
|
76
|
-
} catch (e) {
|
|
77
|
-
this.console.warn(
|
|
78
|
-
`Error releasing previous mixin: ${(e as Error).message}`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const mixin = new OnvifRebroadcastCameraMixin(
|
|
84
|
-
{
|
|
85
|
-
mixinDevice,
|
|
86
|
-
mixinDeviceInterfaces,
|
|
87
|
-
mixinDeviceState,
|
|
88
|
-
mixinProviderNativeId: this.nativeId,
|
|
89
|
-
group: "ONVIF Rebroadcast",
|
|
90
|
-
groupKey: "onvifRebroadcast",
|
|
91
|
-
},
|
|
92
|
-
this,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
this.currentMixinsMap[mixinDeviceState.id] = mixin;
|
|
96
|
-
return mixin;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
|
100
|
-
delete this.currentMixinsMap[id];
|
|
101
|
-
try {
|
|
102
|
-
await mixinDevice.release();
|
|
103
|
-
} catch (e) {
|
|
104
|
-
// this.console.warn(`Error releasing mixin ${id}: ${(e as Error).message}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|