Skip to content

Commit 6fa51ce

Browse files
committed
Fix motionDoorbell & port pick, better quit ffmpeg
1 parent b299428 commit 6fa51ce

File tree

8 files changed

+993
-1399
lines changed

8 files changed

+993
-1399
lines changed

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
# Homebridge Camera FFmpeg
22

3-
[![npm](https://badgen.net/npm/v/homebridge-camera-ffmpeg) ![npm](https://badgen.net/npm/dt/homebridge-camera-ffmpeg)](https://www.npmjs.com/package/homebridge-camera-ffmpeg) [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
3+
[![npm](https://badgen.net/npm/v/homebridge-camera-ffmpeg) ![npm](https://badgen.net/npm/dt/homebridge-camera-ffmpeg)](https://www.npmjs.com/package/homebridge-camera-ffmpeg) [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins) [![certified-hoobs-plugin](https://badgen.net/badge/HOOBS/certified/yellow)](https://plugins.hoobs.org/plugin/homebridge-camera-ffmpeg)
44

55
[Homebridge](https://homebridge.io) Plugin Providing [FFmpeg](https://www.ffmpeg.org)-based Camera Support
66

77
## Installation
88

9-
Before installing this plugin, you should install Homebridge using the [official instructions](https://github.com/homebridge/homebridge/wiki).
10-
11-
### Install via Homebridge Config UI X
12-
13-
1. Search for `Camera FFmpeg` on the Plugins tab of [Config UI X](https://www.npmjs.com/package/homebridge-config-ui-x).
14-
2. Install the `Homebridge Camera FFmpeg` plugin and use the form to enter your camera configurations.
9+
This plugin is supported under both [Homebridge](https://homebridge.io) and [HOOBS](https://hoobs.org/). It is highly recommended that you use either [Homebridge Config UI X](https://www.npmjs.com/package/homebridge-config-ui-x) or the HOOBS UI to install and configure this plugin.
1510

1611
### Manual Installation
1712

config.schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"portmqtt": {
2727
"title": "MQTT Port",
2828
"type": "integer",
29-
"placeholder": "1883",
29+
"placeholder": 1883,
3030
"description": "The port of the MQTT broker."
3131
},
3232
"tlsmqtt": {
@@ -47,7 +47,7 @@
4747
"porthttp": {
4848
"title": "HTTP Port",
4949
"type": "integer",
50-
"placeholder": "8080",
50+
"placeholder": 8080,
5151
"description": "The port to listen on for HTTP-based automation. If not set, HTTP support is not started."
5252
},
5353
"localhttp": {

package-lock.json

Lines changed: 919 additions & 1364 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"displayName": "Homebridge Camera FFmpeg",
33
"name": "homebridge-camera-ffmpeg",
4-
"version": "3.1.2",
4+
"version": "3.1.3",
55
"description": "Homebridge Plugin Providing FFmpeg-based Camera Support",
66
"main": "dist/index.js",
77
"license": "ISC",
@@ -59,17 +59,18 @@
5959
"README.md"
6060
],
6161
"devDependencies": {
62-
"@types/node": "^14.14.31",
63-
"@typescript-eslint/eslint-plugin": "^4.16.1",
64-
"@typescript-eslint/parser": "^4.16.1",
65-
"eslint": "^7.21.0",
66-
"homebridge": "^1.3.2",
62+
"@types/node": "^16.7.1",
63+
"@types/ws": "^7.4.7",
64+
"@typescript-eslint/eslint-plugin": "^4.29.3",
65+
"@typescript-eslint/parser": "^4.29.3",
66+
"eslint": "^7.32.0",
67+
"homebridge": "^1.3.4",
6768
"rimraf": "^3.0.2",
68-
"typescript": "^4.2.3"
69+
"typescript": "^4.3.5"
6970
},
7071
"dependencies": {
7172
"ffmpeg-for-homebridge": "^0.0.9",
72-
"get-port": "^5.1.1",
73-
"mqtt": "^4.2.6"
73+
"mqtt": "^4.2.8",
74+
"pick-port": "^1.0.0"
7475
}
7576
}

src/ffmpeg.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
22
import { StreamRequestCallback } from 'homebridge';
3+
import os from 'os';
34
import readline from 'readline';
45
import { Writable } from 'stream';
56
import { Logger } from './logger';
@@ -21,6 +22,8 @@ type FfmpegProgress = {
2122

2223
export class FfmpegProcess {
2324
private readonly process: ChildProcessWithoutNullStreams;
25+
private killTimeout?: NodeJS.Timeout;
26+
readonly stdin: Writable;
2427

2528
constructor(cameraName: string, sessionId: string, videoProcessor: string, ffmpegArgs: string, log: Logger,
2629
debug = false, delegate: StreamingDelegate, callback?: StreamRequestCallback) {
@@ -29,6 +32,7 @@ export class FfmpegProcess {
2932
let started = false;
3033
const startTime = Date.now();
3134
this.process = spawn(videoProcessor, ffmpegArgs.split(/\s+/), { env: process.env });
35+
this.stdin = this.process.stdin;
3236

3337
this.process.stdout.on('data', (data) => {
3438
const progress = this.parseProgress(data);
@@ -56,7 +60,7 @@ export class FfmpegProcess {
5660
callback();
5761
callback = undefined;
5862
}
59-
if (line.match(/\[(panic|fatal|error)\]/)) {
63+
if (debug && line.match(/\[(panic|fatal|error)\]/)) { // For now only write anything out when debug is set
6064
log.error(line, cameraName);
6165
} else if (debug) {
6266
log.debug(line, cameraName, true);
@@ -70,13 +74,17 @@ export class FfmpegProcess {
7074
delegate.stopStream(sessionId);
7175
});
7276
this.process.on('exit', (code: number, signal: NodeJS.Signals) => {
77+
if (this.killTimeout) {
78+
clearTimeout(this.killTimeout);
79+
}
80+
7381
const message = 'FFmpeg exited with code: ' + code + ' and signal: ' + signal;
7482

75-
if (code == 0) {
76-
log.debug(message + ' (Graceful)', cameraName, debug);
83+
if (this.killTimeout && code === 0) {
84+
log.debug(message + ' (Expected)', cameraName, debug);
7785
} else if (code == null || code === 255) {
7886
if (this.process.killed) {
79-
log.debug(message + ' (Expected)', cameraName, debug);
87+
log.debug(message + ' (Forced)', cameraName, debug);
8088
} else {
8189
log.error(message + ' (Unexpected)', cameraName);
8290
}
@@ -125,10 +133,9 @@ export class FfmpegProcess {
125133
}
126134

127135
public stop(): void {
128-
this.process.stdin.write("q\r\n");
129-
}
130-
131-
public getStdin(): Writable {
132-
return this.process.stdin;
136+
this.process.stdin.write('q' + os.EOL);
137+
this.killTimeout = setTimeout(() => {
138+
this.process.kill('SIGKILL');
139+
}, 2 * 1000);
133140
}
134141
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
259259
if (motionTrigger) {
260260
motionTrigger.updateCharacteristic(hap.Characteristic.On, true);
261261
}
262-
if (config?.motionDoorbell) {
262+
if (!timeout && config?.motionDoorbell) {
263263
this.doorbellHandler(accessory, true);
264264
}
265265
let timeoutConfig = config?.motionTimeout ?? 1;

src/pick-port.d.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
declare module 'pick-port' {
2+
namespace pickPort {
3+
type pickPortOptions = {
4+
type?: ('udp' | 'tcp');
5+
ip?: string;
6+
minPort?: number;
7+
maxPort?: number;
8+
reserveTimeout?: number;
9+
};
10+
11+
type reserveOptions = {
12+
type: ('udp' | 'tcp');
13+
ip: string;
14+
port: number;
15+
reserveTimeout: number;
16+
};
17+
18+
type isReservedOptions = {
19+
type: ('udp' | 'tcp');
20+
ip: string;
21+
port: number;
22+
};
23+
24+
function reserve(options: reserveOptions): void;
25+
function isReserved(options: isReservedOptions): boolean;
26+
}
27+
28+
function pickPort(options: pickPort.pickPortOptions): Promise<number>;
29+
30+
export = pickPort;
31+
}

src/streamingDelegate.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import { spawn } from 'child_process';
2323
import { createSocket, Socket } from 'dgram';
2424
import ffmpegPath from 'ffmpeg-for-homebridge';
25-
import getPort from 'get-port';
25+
import pickPort, { pickPortOptions } from 'pick-port';
2626
import { CameraConfig, VideoConfig } from './configTypes';
2727
import { FfmpegProcess } from './ffmpeg';
2828
import { Logger } from './logger';
@@ -189,7 +189,7 @@ export class StreamingDelegate implements CameraStreamingDelegate {
189189
});
190190
ffmpeg.stderr.on('data', (data) => {
191191
data.toString().split('\n').forEach((line: string) => {
192-
if (line.length > 0) {
192+
if (this.videoConfig.debug && line.length > 0) { // For now only write anything out when debug is set
193193
this.log.error(line, this.cameraName + '] [Snapshot');
194194
}
195195
});
@@ -273,13 +273,18 @@ export class StreamingDelegate implements CameraStreamingDelegate {
273273
}
274274

275275
async prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): Promise<void> {
276-
const videoReturnPort = await getPort();
276+
const ipv6 = request.addressVersion === 'ipv6';
277+
278+
const options: pickPortOptions = {
279+
type: 'udp',
280+
ip: ipv6 ? '::' : '0.0.0.0',
281+
reserveTimeout: 15
282+
};
283+
const videoReturnPort = await pickPort(options);
277284
const videoSSRC = this.hap.CameraController.generateSynchronisationSource();
278-
const audioReturnPort = await getPort();
285+
const audioReturnPort = await pickPort(options);
279286
const audioSSRC = this.hap.CameraController.generateSynchronisationSource();
280287

281-
const ipv6 = request.addressVersion === 'ipv6';
282-
283288
const sessionInfo: SessionInfo = {
284289
address: request.targetAddress,
285290
ipv6: ipv6,
@@ -455,7 +460,7 @@ export class StreamingDelegate implements CameraStreamingDelegate {
455460
'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:' + sessionInfo.audioSRTP.toString('base64') + '\r\n';
456461
activeSession.returnProcess = new FfmpegProcess(this.cameraName + '] [Two-way', request.sessionID,
457462
this.videoProcessor, ffmpegReturnArgs, this.log, this.videoConfig.debugReturn, this);
458-
activeSession.returnProcess.getStdin().end(sdpReturnAudio);
463+
activeSession.returnProcess.stdin.end(sdpReturnAudio);
459464
}
460465

461466
this.ongoingSessions.set(request.sessionID, activeSession);

0 commit comments

Comments
 (0)