seydx / camera.ui

NVR like user Interface for RTSP capable cameras

Home Page:https://github.com/seydx/camera.ui

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Crash and Restart of Child Bridge Due to TypeError in homebridge-camera-ui (with fix)

ShaAlexander opened this issue · comments

Describe the bug

A crash and restart of the child bridge occur, followed by the error: TypeError: Cannot read properties of null (reading 'get') in the homebridge-camera-ui plugin.

To Reproduce

Restart the system. The issue occurs without a clear trigger.

Expected behavior

The system should restart without crashing and should not encounter a null property error.

Logs

TypeError: Cannot read properties of null (reading 'get')
    at file:///var/lib/homebridge/node_modules/homebridge-camera-ui/src/accessories/camera.js:280:57
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Environment

  • Camera UI Version: v5.0.27
    -** Homebridge Version**: [Homebridge v1.8.2]· [UI v4.56.2]

Additional context

The error seems to originate from accessing a null property in the fetchSnapshot method.

Proposed Fix

The issue can be fixed by adding null checks before accessing properties. Here is the updated code for the fetchSnapshot method in /var/lib/homebridge/node_modules/homebridge-camera-ui/src/accessories/camera.js:

fetchSnapshot(snapFilter) {
    // eslint-disable-next-line no-async-promise-executor, no-unused-vars
    this.snapshotPromise = new Promise(async (resolve, reject) => {
        const atHome = await this.getPrivacyState();

        if (atHome) {
            this.snapshotPromise = undefined;
            return resolve(privacyImageInBytes);
        }

        let input = this.accessory.context.config.videoConfig.stillImageSource.split(/\s+/);
        const startTime = Date.now();
        const controller = this.cameraUi.cameraController.get(this.accessory.displayName);

        if (this.accessory.context.config.prebuffering && controller?.prebuffer) {
            try {
                input = await controller.prebuffer.getVideo();
            } catch (error) {
                this.log.warn(`Error getting prebuffer video: ${error.message}`, this.accessory.displayName);
            }
        }

        const ffmpegArguments = ['-hide_banner', '-loglevel', 'error', ...input, '-frames:v', '1'];

        if (snapFilter) {
            ffmpegArguments.push('-filter:v', ...snapFilter.split(/\s+/));
        }

        ffmpegArguments.push('-f', 'image2', '-');

        this.log.debug(
            `Snapshot command: ${this.config.options.videoProcessor} ${ffmpegArguments.join(' ')}`,
            this.accessory.displayName
        );

        const ffmpeg = spawn(this.config.options.videoProcessor, ffmpegArguments, {
            env: process.env,
        });

        let errors = [];
        let snapshotBuffer = Buffer.alloc(0);

        ffmpeg.stdout.on('data', (data) => {
            snapshotBuffer = Buffer.concat([snapshotBuffer, data]);
        });

        ffmpeg.on('error', (error) => {
            this.log.error(
                `FFmpeg process creation failed: ${error.message} - Showing "offline" image instead.`,
                this.accessory.displayName
            );
            resolve(offlineImageInBytes);
            this.snapshotPromise = undefined;
        });

        ffmpeg.stderr.on('data', (data) => {
            errors = errors.slice(-5);
            errors.push(data.toString().replace(/(\r\n|\n|\r)/gm, ' '));
        });

        ffmpeg.on('close', () => {
            if (snapshotBuffer.length > 0) {
                resolve(snapshotBuffer);
            } else {
                this.log.error('Failed to fetch snapshot. Showing "offline" image instead.', this.accessory.displayName);

                if (errors.length > 0) {
                    this.log.error(errors.join(' - '), this.accessory.displayName, 'Homebridge');
                }

                this.snapshotPromise = undefined;
                return resolve(offlineImageInBytes);
            }

            setTimeout(() => {
                this.snapshotPromise = undefined;
            }, 5 * 1000); // Expire cached snapshot after 5 seconds

            const runtime = (Date.now() - startTime) / 1000;

            let message = `Fetching snapshot took ${runtime} seconds.`;

            if (runtime < 5) {
                this.log.debug(message, this.accessory.displayName);
            } else {
                if (!this.accessory.context.config.unbridge) {
                    message += ' It is highly recommended you switch to unbridge mode.';
                }

                if (runtime < 22) {
                    this.log.info(message, this.accessory.displayName, 'Homebridge');
                } else {
                    message += ' The request has timed out and the snapshot has not been refreshed in HomeKit.';
                    this.log.error(message, this.accessory.displayName, 'Homebridge');
                }
            }
        });
    });

    return this.snapshotPromise;
}
commented

🎉 A new version of camera.ui

A new version of camera.ui is currently under active development. An initial alpha/beta release and previews are coming soon. Stay tuned for exciting updates: #448 .

This version will no longer be developed / fixed. The new version contains many novelties and fixes (most important is for HKSV recording).