diff --git a/src-widgets/craco.config.js b/src-widgets/craco.config.js
deleted file mode 100644
index 8fc0cc0..0000000
--- a/src-widgets/craco.config.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('@iobroker/vis-2-widgets-react-dev/craco.config.js');
diff --git a/src-widgets/eslint.config.mjs b/src-widgets/eslint.config.mjs
new file mode 100644
index 0000000..810a5a3
--- /dev/null
+++ b/src-widgets/eslint.config.mjs
@@ -0,0 +1,30 @@
+import config, { reactConfig } from '@iobroker/eslint-config';
+
+// disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc'
+config.forEach(rule => {
+ if (rule?.plugins?.jsdoc) {
+ rule.rules['jsdoc/require-jsdoc'] = 'off';
+ rule.rules['jsdoc/require-param'] = 'off';
+ }
+});
+
+export default [
+ ...config,
+ ...reactConfig,
+ {
+ rules: {
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ },
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ projectService: {
+ allowDefaultProject: ['*.js', '*.mjs'],
+ },
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+];
diff --git a/src-widgets/public/index.html b/src-widgets/index.html
similarity index 90%
rename from src-widgets/public/index.html
rename to src-widgets/index.html
index acbb90f..886f7ee 100644
--- a/src-widgets/public/index.html
+++ b/src-widgets/index.html
@@ -22,6 +22,10 @@
script.src = window.location.protocol + '//' + window.location.hostname + ':8082/lib/js/socket.io.js';
document.head.appendChild(script);
+
diff --git a/src-widgets/modulefederation.config.js b/src-widgets/modulefederation.config.js
deleted file mode 100644
index 5e21e54..0000000
--- a/src-widgets/modulefederation.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const makeFederation = require('@iobroker/vis-2-widgets-react-dev/modulefederation.config');
-
-module.exports = makeFederation('vis2CameraWidgets', {
- './RtspCamera': './src/RtspCamera',
- './SnapshotCamera': './src/SnapshotCamera',
- './translations': './src/translations',
-});
diff --git a/src-widgets/package.json b/src-widgets/package.json
index 8e3173e..b12240e 100644
--- a/src-widgets/package.json
+++ b/src-widgets/package.json
@@ -3,39 +3,34 @@
"private": true,
"version": "2.1.2",
"dependencies": {
- "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
- "@craco/craco": "^7.1.0",
"@iobroker/adapter-react-v5": "^7.1.4",
- "@iobroker/vis-2-widgets-react-dev": "^4.0.3",
+ "@iobroker/eslint-config": "^0.1.6",
+ "@iobroker/types": "^6.0.11",
+ "@iobroker/types-vis-2": "^2.10.7",
"@mui/icons-material": "^6.1.1",
"@mui/material": "^6.1.1",
- "craco-esbuild": "^0.6.1",
- "craco-module-federation": "^1.1.0",
- "eslint": "^8.57.0",
- "eslint-config-airbnb": "^19.0.4",
- "eslint-config-react-app": "^7.0.1",
- "eslint-plugin-eqeqeq-fix": "^1.0.3",
- "eslint-plugin-import": "^2.29.1",
- "eslint-plugin-jsx-a11y": "^6.9.0",
- "eslint-plugin-only-warn": "^1.1.0",
- "eslint-plugin-react": "^7.34.4",
- "eslint-plugin-react-hooks": "^4.6.2",
+ "@types/react": "^18.3.7",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "@originjs/vite-plugin-federation": "^1.3.6",
"hls.js": "^1.5.15",
"moment": "^2.30.1",
- "node-sass": "^9.0.0",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-scripts": "^5.0.1",
"sass": "^1.79.1",
- "uuid": "^10.0.0"
+ "uuid": "^10.0.0",
+ "vite": "^5.4.6",
+ "vite-tsconfig-paths": "^5.0.1",
+ "vite-plugin-top-level-await": "^1.4.4"
},
"scripts": {
- "start": "set PORT=4173 && craco start",
+ "start": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
"lint": "eslint -c eslint.config.mjs src",
- "build": "craco build",
- "test": "react-scripts test",
- "eject": "react-scripts eject",
+ "tsc": "tsc -p tsconfig.json",
+ "prettier": "prettier -c prettier.config.mjs --write src",
"i18n": "node node_modules/@iobroker/vis-2-widgets-react-dev/searchI18n vis_2_widgets_camera"
},
"eslintConfig": {
diff --git a/src-widgets/prettier.config.mjs b/src-widgets/prettier.config.mjs
new file mode 100644
index 0000000..2f00708
--- /dev/null
+++ b/src-widgets/prettier.config.mjs
@@ -0,0 +1,3 @@
+import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs';
+
+export default prettierConfig;
diff --git a/src-widgets/src/.eslintrc.js b/src-widgets/src/.eslintrc.js
deleted file mode 100644
index c80dfde..0000000
--- a/src-widgets/src/.eslintrc.js
+++ /dev/null
@@ -1,76 +0,0 @@
-module.exports = {
- env: {
- browser: true,
- es2021: true,
- },
- extends: [
- 'eslint:recommended',
- 'plugin:react/recommended',
- 'airbnb',
- // 'react-app',
- 'plugin:eqeqeq-fix/recommended',
- ],
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- ecmaVersion: 'latest',
- sourceType: 'module',
- },
- plugins: [
- 'only-warn',
- 'react',
- ],
- rules: {
- 'arrow-parens': [1, 'as-needed'],
- 'react/jsx-indent': 'off',
- 'react/jsx-indent-props': 'off',
- 'react/no-access-state-in-setstate': 'off',
- 'jsx-a11y/click-events-have-key-events': 'off',
- 'jsx-a11y/no-static-element-interactions': 'off',
- 'no-plusplus': 'off',
- 'react/react-in-jsx-scope': 'off',
- 'react/prop-types': 'off',
- 'react/no-render-return-value': 'off',
- 'max-len': 'off',
- 'react/destructuring-assignment': 'off',
- 'react/prefer-stateless-function': 'off',
- 'react/self-closing-comp': 'off',
- 'react/jsx-filename-extension': 'off',
- 'no-nested-ternary': 'off',
- 'react/no-array-index-key': 'off',
- 'react/jsx-props-no-spreading': 'off',
- 'react/sort-comp': 'off',
- 'react/no-did-update-set-state': 'off',
- 'global-require': 'off',
- 'import/extensions': 'off',
- 'operator-linebreak': 'off',
- 'no-unused-expressions': 'off',
- 'prefer-destructuring': 'off',
- 'no-return-assign': 'off',
- 'no-multi-spaces': 'off',
- 'key-spacing': 'off',
- 'no-undef': 2,
- 'react/forbid-prop-types': 'off',
- 'react/require-default-props': 'off',
- 'import/no-extraneous-dependencies': 'off',
- 'react/jsx-wrap-multilines': 'off',
- 'react/jsx-closing-tag-location': 'off',
- 'no-restricted-syntax': 'off',
- 'guard-for-in': 'off',
- // 'linebreak-style': ["error", "windows"],
- 'linebreak-style': ['off'],
- 'no-param-reassign': 'off',
- 'no-await-in-loop': 'off',
- 'no-console': ['error', { allow: ['warn', 'error', 'log'] }],
- 'no-underscore-dangle': 'off',
- 'no-constant-condition': 'off',
- 'no-loop-func': 'off',
- 'no-continue': 'off',
- 'implicit-arrow-linebreak': 'off',
- radix: 'off',
- indent: ['error', 4, { SwitchCase: 1 }],
- 'no-alert': 'off',
- 'react/function-component-definition': 'off',
- },
-};
diff --git a/src-widgets/src/Generic.jsx b/src-widgets/src/Generic.jsx
deleted file mode 100644
index f224b86..0000000
--- a/src-widgets/src/Generic.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Generic extends window.visRxWidget {
- static getI18nPrefix() {
- return 'cameras_';
- }
-
- // static getObjectIcon(obj, id, imagePrefix) {
- // imagePrefix = imagePrefix || '../..'; // http://localhost:8081';
- // let src = '';
- // const common = obj && obj.common;
- //
- // if (common) {
- // const cIcon = common.icon;
- // if (cIcon) {
- // if (!cIcon.startsWith('data:image/')) {
- // if (cIcon.includes('.')) {
- // let instance;
- // if (obj.type === 'instance' || obj.type === 'adapter') {
- // src = `${imagePrefix}/adapter/${common.name}/${cIcon}`;
- // } else if (id && id.startsWith('system.adapter.')) {
- // instance = id.split('.', 3);
- // if (cIcon[0] === '/') {
- // instance[2] += cIcon;
- // } else {
- // instance[2] += `/${cIcon}`;
- // }
- // src = `${imagePrefix}/adapter/${instance[2]}`;
- // } else {
- // instance = id.split('.', 2);
- // if (cIcon[0] === '/') {
- // instance[0] += cIcon;
- // } else {
- // instance[0] += `/${cIcon}`;
- // }
- // src = `${imagePrefix}/adapter/${instance[0]}`;
- // }
- // } else {
- // return null;
- // }
- // } else {
- // src = cIcon;
- // }
- // }
- // }
- //
- // return src || null;
- // }
- //
- // wrapContent(content, addToHeader, cardContentStyle, headerStyle, onCardClick) {
- // return super.wrapContent(content, addToHeader, cardContentStyle, headerStyle, onCardClick, { Card, CardContent });
- // }
-}
-
-Generic.propTypes = {
- context: PropTypes.object,
- themeType: PropTypes.string,
- style: PropTypes.object,
- data: PropTypes.object,
-};
-
-export default Generic;
diff --git a/src-widgets/src/RtspCamera.jsx b/src-widgets/src/RtspCamera.tsx
similarity index 50%
rename from src-widgets/src/RtspCamera.jsx
rename to src-widgets/src/RtspCamera.tsx
index d8d4f73..d14fe75 100644
--- a/src-widgets/src/RtspCamera.jsx
+++ b/src-widgets/src/RtspCamera.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, type JSX } from 'react';
import {
Button,
CircularProgress,
@@ -12,9 +12,19 @@ import {
import { Close } from '@mui/icons-material';
-import Generic from './Generic';
+import VisRxWidget from './VisRxWidget';
+import type { VisRxWidgetState, VisRxWidgetProps } from '@iobroker/types-vis-2/visRxWidget';
+import type {
+ RxRenderWidgetProps,
+ RxWidgetInfoAttributesField,
+ RxWidgetInfoCustomComponentContext,
+ RxWidgetInfoCustomComponentProperties,
+} from '@iobroker/types-vis-2';
+import { I18n } from '@iobroker/adapter-react-v5';
-const styles = {
+export const TRANSLATION_PREFIX = 'cameras_';
+
+const styles: Record = {
camera: {
width: '100%',
height: '100%',
@@ -35,26 +45,75 @@ const styles = {
},
};
-export const CameraField = props => {
- const [cameras, setCameras] = React.useState(null);
- const [camera, setCamera] = React.useState(props.data[props.field.name] || '');
+interface RtspCameraRxData {
+ noCard: boolean;
+ widgetTitle: string;
+ width: number;
+ camera: string;
+}
+
+interface RtspCameraState extends VisRxWidgetState {
+ loading: boolean;
+ full: boolean;
+ alive: boolean;
+}
+
+interface CameraEntry {
+ enabled: boolean;
+ value: string;
+ label: string;
+ subLabel: string;
+}
+
+interface CameraFieldProps {
+ data: Record;
+ field: RxWidgetInfoAttributesField;
+ rtsp?: boolean;
+ onDataChange: (newData: Partial) => void;
+ context: RxWidgetInfoCustomComponentContext;
+ t: (word: string) => string;
+}
+
+export const CameraField = (props: CameraFieldProps): JSX.Element => {
+ const [cameras, setCameras] = React.useState(null);
+ const [camera, setCamera] = React.useState(
+ (props.data as unknown as Record)[props.field.name] || '',
+ );
useEffect(() => {
- (async () => {
- const _cameras = [];
- const instances = await props.context.socket.getAdapterInstances('cameras');
+ void (async () => {
+ const _cameras: CameraEntry[] = [];
+ const instances: ioBroker.InstanceObject[] = await props.context.socket.getAdapterInstances('cameras');
instances.forEach(instance => {
const instanceId = instance._id.split('.').pop();
instance.native.cameras
- .filter(iCamera => !props.rtsp || iCamera.type === 'rtsp' || iCamera.rtsp)
- .forEach(iCamera => {
- _cameras.push({
- enabled: iCamera.enabled !== false,
- value: `${instanceId}/${iCamera.name}`,
- label: `cameras.${instanceId}/${iCamera.name}`,
- subLabel: iCamera.desc ? `${iCamera.desc}/${iCamera.ip}` : iCamera.ip || '',
- });
- });
+ .filter(
+ (iCamera: {
+ enabled: boolean;
+ name: string;
+ desc: string;
+ ip?: string;
+ type: string;
+ rtsp?: boolean;
+ }) => !props.rtsp || iCamera.type === 'rtsp' || iCamera.rtsp,
+ )
+ .forEach(
+ (iCamera: {
+ enabled: boolean;
+ name: string;
+ desc: string;
+ ip?: string;
+ type: string;
+ rtsp?: boolean;
+ }) => {
+ _cameras.push({
+ enabled: iCamera.enabled !== false,
+ value: `${instanceId}/${iCamera.name}`,
+ label: `cameras.${instanceId}/${iCamera.name}`,
+ subLabel: iCamera.desc ? `${iCamera.desc}/${iCamera.ip}` : iCamera.ip || '',
+ });
+ },
+ );
});
setCameras(_cameras);
})();
@@ -66,7 +125,7 @@ export const CameraField = props => {
variant="standard"
value={camera}
onChange={e => {
- props.setData({ [props.field.name]: e.target.value });
+ props.onDataChange({ [props.field.name]: e.target.value });
setCamera(e.target.value);
}}
>
@@ -87,7 +146,7 @@ export const CameraField = props => {
color: 'red',
}}
>
- {Generic.t('disabled')}
+ {props.t('disabled')}
) : null}
@@ -98,15 +157,31 @@ export const CameraField = props => {
);
};
-class RtspCamera extends Generic {
- constructor(props) {
+class RtspCamera extends VisRxWidget {
+ private videoInterval: ReturnType | null = null;
+
+ private readonly videoRef: React.RefObject;
+
+ private readonly fullVideoRef: React.RefObject;
+
+ private currentCam: string | null = null;
+
+ private useMessages: boolean | undefined;
+
+ private subscribedOnAlive: string = '';
+
+ constructor(props: VisRxWidgetProps) {
super(props);
- this.videoInterval = null;
this.videoRef = React.createRef();
this.fullVideoRef = React.createRef();
- this.currentCam = null;
- this.state.full = false;
- this.state.alive = false;
+ Object.assign(this.state, {
+ full: false,
+ alive: false,
+ });
+ }
+
+ static getI18nPrefix(): string {
+ return TRANSLATION_PREFIX;
}
static getWidgetInfo() {
@@ -142,13 +217,19 @@ class RtspCamera extends Generic {
label: 'Camera',
name: 'camera',
type: 'custom',
- component: (field, data, setData, props) => (
+ component: (
+ field: RxWidgetInfoAttributesField,
+ data: RtspCameraRxData,
+ onDataChange: (newData: Partial) => void,
+ props: RxWidgetInfoCustomComponentProperties,
+ ) => (
}
+ onDataChange={onDataChange}
context={props.context}
+ t={(word: string) => I18n.t(TRANSLATION_PREFIX + word)}
/>
),
},
@@ -164,64 +245,67 @@ class RtspCamera extends Generic {
};
}
+ // @ts-expect-error Fix later
// eslint-disable-next-line class-methods-use-this
getWidgetInfo() {
return RtspCamera.getWidgetInfo();
}
- static drawCamera(ref, data) {
+ static drawCamera(ref: React.RefObject, data: string) {
const canvas = ref.current;
if (!canvas) {
return;
}
const context = canvas.getContext('2d');
- try {
- const imageObj = new Image();
- imageObj.src = `data:image/jpeg;base64,${data}`;
- imageObj.onload = () => {
- canvas.width = imageObj.width;
- canvas.height = imageObj.height;
- // const hRatio = canvas.width / imageObj.width;
- // const vRatio = canvas.height / imageObj.height;
- // const ratio = Math.min(hRatio, vRatio);
- // const centerShiftX = (canvas.width - imageObj.width * ratio) / 2;
- // const centerShiftY = (canvas.height - imageObj.height * ratio) / 2;
- // context.clearRect(0, 0, canvas.width, canvas.height);
- context.drawImage(
- imageObj,
- 0,
- 0,
- imageObj.width,
- imageObj.height,
- // centerShiftX,
- // centerShiftY,
- // imageObj.width * ratio,
- // imageObj.height * ratio,
- );
- };
- imageObj.onerror = e => {
+ if (context) {
+ try {
+ const imageObj = new Image();
+ imageObj.src = `data:image/jpeg;base64,${data}`;
+ imageObj.onload = () => {
+ canvas.width = imageObj.width;
+ canvas.height = imageObj.height;
+ // const hRatio = canvas.width / imageObj.width;
+ // const vRatio = canvas.height / imageObj.height;
+ // const ratio = Math.min(hRatio, vRatio);
+ // const centerShiftX = (canvas.width - imageObj.width * ratio) / 2;
+ // const centerShiftY = (canvas.height - imageObj.height * ratio) / 2;
+ // context.clearRect(0, 0, canvas.width, canvas.height);
+ context.drawImage(
+ imageObj,
+ 0,
+ 0,
+ imageObj.width,
+ imageObj.height,
+ // centerShiftX,
+ // centerShiftY,
+ // imageObj.width * ratio,
+ // imageObj.height * ratio,
+ );
+ };
+ imageObj.onerror = e => {
+ console.error(e);
+ };
+ } catch (e) {
console.error(e);
- };
- } catch (e) {
- console.error(e);
+ }
}
}
- updateStream = (id, state) => {
+ updateStream = (_id: string, state: ioBroker.State | null | undefined) => {
if (state?.val) {
if (this.state.loading) {
this.setState({ loading: false });
}
- RtspCamera.drawCamera(this.videoRef, state.val);
+ RtspCamera.drawCamera(this.videoRef, state.val as string);
if (this.state.full) {
- RtspCamera.drawCamera(this.fullVideoRef, state.val);
+ RtspCamera.drawCamera(this.fullVideoRef, state.val as string);
}
}
};
- static getNameAndInstance(value) {
+ static getNameAndInstance(value: string): { instanceId: string; name: string } | null {
if (!value) {
return null;
}
@@ -235,7 +319,7 @@ class RtspCamera extends Generic {
};
}
- onCameras = data => {
+ onCameras = (data: string | { accepted: boolean; error?: string }): void => {
if (data) {
// if it is success or error object
if (typeof data === 'object' && (data.accepted || data.error)) {
@@ -249,15 +333,15 @@ class RtspCamera extends Generic {
this.setState({ loading: false });
}
- RtspCamera.drawCamera(this.videoRef, data);
+ RtspCamera.drawCamera(this.videoRef, data as string);
if (this.state.full) {
- RtspCamera.drawCamera(this.fullVideoRef, data);
+ RtspCamera.drawCamera(this.fullVideoRef, data as string);
}
}
};
- async propertiesUpdate() {
+ async propertiesUpdate(): Promise {
if (this.useMessages === undefined) {
this.useMessages = await this.props.context.socket.checkFeatureSupported('INSTANCE_MESSAGES');
}
@@ -267,126 +351,143 @@ class RtspCamera extends Generic {
// this.width = this.getImageWidth();
// if we were subscribed, unsubscribe
if (this.currentCam) {
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.currentCam);
- if (this.useMessages) {
- await this.props.context.socket.unsubscribeFromInstance(
- `cameras.${instanceId}`,
- `startCamera/${name}`,
- this.onCameras,
- );
- } else {
- // Bluefox 2023.09.28: delete this branch after js-controller 5.0.13 will be mainstream
- await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, {
- val: false,
- });
- await this.props.context.socket.unsubscribeState(
- `cameras.${instanceId}.${name}.stream`,
- this.updateStream,
- );
+ const result = RtspCamera.getNameAndInstance(this.currentCam);
+ if (result) {
+ const { instanceId, name } = result;
+ if (this.useMessages) {
+ await this.props.context.socket.unsubscribeFromInstance(
+ `cameras.${instanceId}`,
+ `startCamera/${name}`,
+ this.onCameras as (data: Record) => void,
+ );
+ } else {
+ // Bluefox 2023.09.28: delete this branch after js-controller 5.0.13 will be mainstream
+ await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, {
+ val: false,
+ });
+ await this.props.context.socket.unsubscribeState(
+ `cameras.${instanceId}.${name}.stream`,
+ this.updateStream,
+ );
+ }
}
}
// subscribe on new camera
if (this.state.rxData.camera) {
this.setState({ loading: true });
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.state.rxData.camera);
- if (this.useMessages) {
- await this.props.context.socket.subscribeOnInstance(
- `cameras.${instanceId}`,
- `startCamera/${name}`,
- { width: this.getImageWidth() },
- this.onCameras,
- );
- } else {
- await this.props.context.socket.subscribeState(
- `cameras.${instanceId}.${name}.stream`,
- this.updateStream,
- );
+ const result = RtspCamera.getNameAndInstance(this.state.rxData.camera);
+ if (result) {
+ const { instanceId, name } = result;
+ if (this.useMessages) {
+ await this.props.context.socket.subscribeOnInstance(
+ `cameras.${instanceId}`,
+ `startCamera/${name}`,
+ { width: this.getImageWidth() },
+ this.onCameras as (data: Record) => void,
+ );
+ } else {
+ await this.props.context.socket.subscribeState(
+ `cameras.${instanceId}.${name}.stream`,
+ this.updateStream,
+ );
+ }
}
} else {
const canvas = this.videoRef.current;
if (canvas) {
const context = canvas.getContext('2d');
- context.clearRect(0, 0, canvas.width, canvas.height);
+ context?.clearRect(0, 0, canvas.width, canvas.height);
}
}
this.currentCam = this.state.rxData.camera;
} else if (this.currentCam) {
// not alive
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.currentCam);
- if (!this.useMessages) {
- await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, { val: false });
- await this.props.context.socket.unsubscribeState(
- `cameras.${instanceId}.${name}.stream`,
- this.updateStream,
- );
+ const result = RtspCamera.getNameAndInstance(this.currentCam);
+ if (result) {
+ const { instanceId, name } = result;
+ if (!this.useMessages) {
+ await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, {
+ val: false,
+ });
+ await this.props.context.socket.unsubscribeState(
+ `cameras.${instanceId}.${name}.stream`,
+ this.updateStream,
+ );
+ }
}
this.currentCam = null;
}
} else if (this.currentCam && this.state.alive) {
// refresh stream
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.currentCam);
- if (this.useMessages) {
- await this.props.context.socket.subscribeOnInstance(
- `cameras.${instanceId}`,
- `startCamera/${name}`,
- { width: this.getImageWidth() },
- this.onCameras,
- );
- } else {
- await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, {
- val: true,
- expire: 30, // expire in 30 seconds
- });
+ const result = RtspCamera.getNameAndInstance(this.currentCam);
+ if (result) {
+ const { instanceId, name } = result;
+ if (this.useMessages) {
+ await this.props.context.socket.subscribeOnInstance(
+ `cameras.${instanceId}`,
+ `startCamera/${name}`,
+ { width: this.getImageWidth() },
+ this.onCameras as (data: Record) => void,
+ );
+ } else {
+ await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, {
+ val: true,
+ expire: 30, // expire in 30 seconds
+ });
+ }
}
} else if (this.currentCam && !this.state.alive) {
// not alive
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.currentCam);
- if (!this.useMessages) {
- await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, { val: false });
- await this.props.context.socket.unsubscribeState(
- `cameras.${instanceId}.${name}.stream`,
- this.updateStream,
- );
+ const result = RtspCamera.getNameAndInstance(this.currentCam);
+ if (result) {
+ const { instanceId, name } = result;
+ if (!this.useMessages) {
+ await this.props.context.socket.setState(`cameras.${instanceId}.${name}.running`, { val: false });
+ await this.props.context.socket.unsubscribeState(
+ `cameras.${instanceId}.${name}.stream`,
+ this.updateStream,
+ );
+ }
}
this.currentCam = null;
}
}
- getImageWidth(isFull) {
+ getImageWidth(isFull?: boolean): number {
isFull = isFull === undefined ? this.state.full : isFull;
// if (parseInt(this.state.rxData.width, 10)) {
// return parseInt(this.state.rxData.width, 10);
// }
if (isFull && this.fullVideoRef.current) {
- return this.fullVideoRef.current.parentElement.clientWidth || 0;
+ return this.fullVideoRef.current.parentElement?.clientWidth || 0;
}
- return this.videoRef.current?.parentElement.clientWidth || 0;
+ return this.videoRef.current?.parentElement?.clientWidth || 0;
}
- async subscribeOnAlive() {
+ subscribeOnAlive(): void {
const data = RtspCamera.getNameAndInstance(this.state.rxData.camera);
- if (this.subsribedOnAlive !== (data ? data.instanceId : null)) {
- if (this.subsribedOnAlive) {
+ if (this.subscribedOnAlive !== (data ? data.instanceId : null)) {
+ if (this.subscribedOnAlive) {
this.props.context.socket.unsubscribeState(
- `system.adapter.cameras.${this.subsribedOnAlive}.alive`,
+ `system.adapter.cameras.${this.subscribedOnAlive}.alive`,
this.onAliveChanged,
);
- this.subsribedOnAlive = '';
+ this.subscribedOnAlive = '';
}
if (data) {
this.props.context.socket.subscribeState(
`system.adapter.cameras.${data.instanceId}.alive`,
this.onAliveChanged,
);
- this.subsribedOnAlive = data.instanceId;
+ this.subscribedOnAlive = data.instanceId;
}
}
}
- onAliveChanged = (id, state) => {
+ onAliveChanged = (id: string, state: ioBroker.State | null | undefined) => {
const data = RtspCamera.getNameAndInstance(this.state.rxData.camera);
if (data && id === `system.adapter.cameras.${data.instanceId}.alive`) {
const alive = !!state?.val;
@@ -406,7 +507,7 @@ class RtspCamera extends Generic {
}
async onRxDataChanged(/* prevRxData */) {
- await this.subscribeOnAlive();
+ this.subscribeOnAlive();
await this.propertiesUpdate();
}
@@ -415,25 +516,32 @@ class RtspCamera extends Generic {
this.videoInterval && clearInterval(this.videoInterval);
this.videoInterval = null;
- if (this.subsribedOnAlive) {
+ if (this.subscribedOnAlive) {
this.props.context.socket.unsubscribeState(
- `system.adapter.cameras.${this.subsribedOnAlive}.alive`,
+ `system.adapter.cameras.${this.subscribedOnAlive}.alive`,
this.onAliveChanged,
);
- this.subsribedOnAlive = null;
+ this.subscribedOnAlive = '';
}
if (this.currentCam) {
- const { instanceId, name } = RtspCamera.getNameAndInstance(this.currentCam);
- if (this.useMessages) {
- this.props.context.socket
- .unsubscribeFromInstance(`cameras.${instanceId}`, `startCamera/${name}`, this.onCameras)
- .catch(e => console.error(e));
+ const result = RtspCamera.getNameAndInstance(this.currentCam);
+ if (result) {
+ const { instanceId, name } = result;
+ if (this.useMessages) {
+ this.props.context.socket
+ .unsubscribeFromInstance(
+ `cameras.${instanceId}`,
+ `startCamera/${name}`,
+ this.onCameras as (data: Record) => void,
+ )
+ .catch((e: Error) => console.error(e));
+ }
}
}
}
- renderDialog() {
+ renderDialog(): JSX.Element | null {
return this.state.full ? (
) : null;
}
- renderWidgetBody(props) {
+ renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element | null {
super.renderWidgetBody(props);
const content = (
@@ -479,7 +587,10 @@ class RtspCamera extends Generic {
{this.state.loading && this.state.alive && }
{!this.state.alive ? (
- {Generic.t('Camera instance %s inactive', (this.state.rxData.camera || '').split('/')[0])}
+ {I18n.t(
+ `${TRANSLATION_PREFIX}Camera instance %s inactive`,
+ (this.state.rxData.camera || '').split('/')[0],
+ )}
) : null}