@apocaliss92/scrypted-reolink-native 0.1.3 → 0.1.5
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/.vscode/settings.json +1 -1
- package/README.md +12 -3
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +2 -2
- package/src/camera-battery.ts +72 -64
- package/src/camera.ts +11 -13
- package/src/common.ts +90 -85
- package/src/connect.ts +136 -0
- package/src/intercom.ts +1 -1
- package/src/main.ts +133 -80
- package/src/nvr.ts +367 -0
- package/src/presets.ts +6 -6
- package/src/stream-utils.ts +26 -25
- package/src/utils.ts +31 -2
package/src/presets.ts
CHANGED
|
@@ -90,7 +90,7 @@ export class ReolinkPtzPresets {
|
|
|
90
90
|
|
|
91
91
|
async refreshPtzPresets(): Promise<PtzPreset[]> {
|
|
92
92
|
const client = await this.camera.ensureClient();
|
|
93
|
-
const channel = this.camera.
|
|
93
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
94
94
|
const presets = await client.getPtzPresets(channel);
|
|
95
95
|
this.setCachedPtzPresets(presets);
|
|
96
96
|
this.syncEnabledPresetsSettingAndCaps(presets);
|
|
@@ -99,14 +99,14 @@ export class ReolinkPtzPresets {
|
|
|
99
99
|
|
|
100
100
|
async moveToPreset(presetId: number): Promise<void> {
|
|
101
101
|
const client = await this.camera.ensureClient();
|
|
102
|
-
const channel = this.camera.
|
|
102
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
103
103
|
await client.moveToPtzPreset(channel, presetId);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/** Create a new PTZ preset at current position. */
|
|
107
107
|
async createPtzPreset(name: string, presetId?: number): Promise<PtzPreset> {
|
|
108
108
|
const client = await this.camera.ensureClient();
|
|
109
|
-
const channel = this.camera.
|
|
109
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
110
110
|
const trimmed = String(name ?? '').trim();
|
|
111
111
|
if (!trimmed) throw new Error('Preset name is required');
|
|
112
112
|
const existing = await client.getPtzPresets(channel);
|
|
@@ -150,7 +150,7 @@ export class ReolinkPtzPresets {
|
|
|
150
150
|
/** Overwrite an existing preset with the current PTZ position (and keep its current name). */
|
|
151
151
|
async updatePtzPresetToCurrentPosition(presetId: number): Promise<void> {
|
|
152
152
|
const client = await this.camera.ensureClient();
|
|
153
|
-
const channel = this.camera.
|
|
153
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
154
154
|
|
|
155
155
|
const current = this.getCachedPtzPresets();
|
|
156
156
|
const found = current.find((p) => p.id === presetId);
|
|
@@ -163,7 +163,7 @@ export class ReolinkPtzPresets {
|
|
|
163
163
|
/** Best-effort delete/disable a preset (firmware dependent). */
|
|
164
164
|
async deletePtzPreset(presetId: number): Promise<void> {
|
|
165
165
|
const client = await this.camera.ensureClient();
|
|
166
|
-
const channel = this.camera.
|
|
166
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
167
167
|
await client.deletePtzPreset(channel, presetId);
|
|
168
168
|
|
|
169
169
|
// Keep enabled preset list clean (remove deleted id), but do not rewrite names for others.
|
|
@@ -181,7 +181,7 @@ export class ReolinkPtzPresets {
|
|
|
181
181
|
/** Rename a preset while trying to preserve its position (will move camera to that preset first). */
|
|
182
182
|
async renamePtzPreset(presetId: number, newName: string): Promise<void> {
|
|
183
183
|
const client = await this.camera.ensureClient();
|
|
184
|
-
const channel = this.camera.
|
|
184
|
+
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
185
185
|
const trimmed = String(newName ?? '').trim();
|
|
186
186
|
if (!trimmed) throw new Error('Preset name is required');
|
|
187
187
|
|
package/src/stream-utils.ts
CHANGED
|
@@ -97,14 +97,18 @@ export async function fetchVideoStreamOptionsFromApi(
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
props: {
|
|
101
|
+
client: ReolinkBaichuanApi,
|
|
102
|
+
ipAddress: string,
|
|
103
|
+
cachedNetPort: { rtsp?: { port?: number; enable?: number }; rtmp?: { port?: number; enable?: number } },
|
|
104
|
+
isFromNvr: boolean,
|
|
105
|
+
rtspChannel: number,
|
|
106
|
+
logger: Console
|
|
107
|
+
},
|
|
106
108
|
): Promise<UrlMediaStreamOptions[]> {
|
|
107
|
-
const
|
|
109
|
+
const { client, ipAddress, cachedNetPort, rtspChannel, logger } = props;
|
|
110
|
+
const rtspStreams: UrlMediaStreamOptions[] = [];
|
|
111
|
+
const rtmpStreams: UrlMediaStreamOptions[] = [];
|
|
108
112
|
|
|
109
113
|
// Use cached net port if provided, otherwise fetch it
|
|
110
114
|
const netPort = cachedNetPort || await client.getNetPort();
|
|
@@ -113,13 +117,8 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
113
117
|
const rtspPort = netPort.rtsp?.port ?? 554;
|
|
114
118
|
const rtmpPort = netPort.rtmp?.port ?? 1935;
|
|
115
119
|
|
|
116
|
-
if (!rtspEnabled && !rtmpEnabled) {
|
|
117
|
-
// If neither RTSP nor RTMP are enabled, return empty array
|
|
118
|
-
return streams;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
120
|
// Get stream metadata to build options
|
|
122
|
-
const streamMetadata = await client.getStreamMetadata(
|
|
121
|
+
const streamMetadata = await client.getStreamMetadata(rtspChannel);
|
|
123
122
|
const list = streamMetadata?.streams || [];
|
|
124
123
|
|
|
125
124
|
for (const stream of list) {
|
|
@@ -134,12 +133,12 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
134
133
|
if (rtspEnabled && profile !== 'ext') {
|
|
135
134
|
// RTSP format: rtsp://ip:port/h264Preview_XX_profile
|
|
136
135
|
// XX is 1-based channel with 2-digit padding
|
|
137
|
-
const channelStr = String(
|
|
136
|
+
const channelStr = String(rtspChannel + 1).padStart(2, '0');
|
|
138
137
|
const profileStr = profile === 'main' ? 'main' : 'sub';
|
|
139
138
|
const rtspPath = `/h264Preview_${channelStr}_${profileStr}`;
|
|
140
139
|
const rtspId = `h264Preview_${channelStr}_${profileStr}`;
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
rtspStreams.push({
|
|
143
142
|
name: `RTSP ${rtspId}`,
|
|
144
143
|
id: rtspId,
|
|
145
144
|
container: 'rtsp',
|
|
@@ -160,14 +159,14 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
160
159
|
const rtmpId = `${streamName}.bcs`; // ID for Scrypted (main.bcs, sub.bcs, ext.bcs)
|
|
161
160
|
|
|
162
161
|
// Use channel directly (0-based) in path, matching reolink_aio behavior
|
|
163
|
-
const rtmpPath = `/bcs/channel${
|
|
162
|
+
const rtmpPath = `/bcs/channel${rtspChannel}_${streamName}.bcs`;
|
|
164
163
|
const rtmpUrl = new URL(`rtmp://${ipAddress}:${rtmpPort}${rtmpPath}`);
|
|
165
164
|
const params = rtmpUrl.searchParams;
|
|
166
|
-
params.set('channel',
|
|
165
|
+
params.set('channel', rtspChannel.toString());
|
|
167
166
|
params.set('stream', streamType.toString());
|
|
168
167
|
// Credentials will be added by addRtspCredentials as user/password query params
|
|
169
168
|
|
|
170
|
-
|
|
169
|
+
rtmpStreams.push({
|
|
171
170
|
name: `RTMP ${rtmpId}`,
|
|
172
171
|
id: rtmpId,
|
|
173
172
|
container: 'rtmp',
|
|
@@ -177,12 +176,14 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
|
|
180
|
+
const nativeStreams = await fetchVideoStreamOptionsFromApi(client, rtspChannel, logger);
|
|
181
|
+
|
|
182
|
+
const streams: UrlMediaStreamOptions[] = [
|
|
183
|
+
...rtspStreams,
|
|
184
|
+
...rtmpStreams,
|
|
185
|
+
...nativeStreams,
|
|
186
|
+
];
|
|
186
187
|
|
|
187
188
|
return streams;
|
|
188
189
|
}
|
|
@@ -378,7 +379,7 @@ export class StreamManager {
|
|
|
378
379
|
async closeAllStreams(reason?: string): Promise<void> {
|
|
379
380
|
const servers = Array.from(this.nativeRfcServers.values());
|
|
380
381
|
this.nativeRfcServers.clear();
|
|
381
|
-
|
|
382
|
+
|
|
382
383
|
await Promise.allSettled(
|
|
383
384
|
servers.map(async (server) => {
|
|
384
385
|
try {
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DeviceCapabilities } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
-
import { ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
|
1
|
+
import type { DeviceCapabilities, ReolinkDeviceInfo } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
+
import { DeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
|
3
3
|
|
|
4
4
|
export const getDeviceInterfaces = (props: {
|
|
5
5
|
capabilities: DeviceCapabilities,
|
|
@@ -49,4 +49,33 @@ export const getDeviceInterfaces = (props: {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
return { interfaces, type: capabilities.isDoorbell ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const updateDeviceInfo = async (props: {
|
|
55
|
+
device: DeviceBase,
|
|
56
|
+
ipAddress: string,
|
|
57
|
+
deviceData: ReolinkDeviceInfo
|
|
58
|
+
}) => {
|
|
59
|
+
const { device, ipAddress, deviceData } = props;
|
|
60
|
+
try {
|
|
61
|
+
const info = device.info || {};
|
|
62
|
+
|
|
63
|
+
info.ip = ipAddress;
|
|
64
|
+
info.serialNumber = deviceData?.serialNumber || deviceData?.itemNo;
|
|
65
|
+
info.firmware = deviceData?.firmwareVersion;
|
|
66
|
+
info.version = deviceData?.hardwareVersion;
|
|
67
|
+
info.model = deviceData?.type;
|
|
68
|
+
info.manufacturer = 'Reolink';
|
|
69
|
+
info.managementUrl = `http://${ipAddress}`;
|
|
70
|
+
device.info = info;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// If API call fails, at least set basic info
|
|
73
|
+
const info = device.info || {};
|
|
74
|
+
info.ip = ipAddress;
|
|
75
|
+
info.manufacturer = 'Reolink native';
|
|
76
|
+
info.managementUrl = `http://${ipAddress}`;
|
|
77
|
+
device.info = info;
|
|
78
|
+
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
52
81
|
}
|