Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ShaAlexander opened this issue Jun 6, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@ShaAlexander
Copy link

ShaAlexander commented Jun 6, 2024

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;
}
@ShaAlexander ShaAlexander added the bug Something isn't working label Jun 6, 2024
@mkz212
Copy link
Contributor

mkz212 commented Jul 29, 2024

🎉 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants