camera.ui
camera.ui copied to clipboard
Crash and Restart of Child Bridge Due to TypeError in homebridge-camera-ui (with fix)
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;
}