diff --git a/config.schema.json b/config.schema.json index 44f4c907..5691db3d 100644 --- a/config.schema.json +++ b/config.schema.json @@ -118,7 +118,7 @@ "description": "Rings the doorbell when motion is activated. This allows for motion alerts to appear on Apple TVs." }, "unbridge": { - "title": "Unbridge Camera", + "title": "Unbridge Camera (Recommended)", "type": "boolean", "description": "Bridged cameras can cause slowdowns of the entire Homebridge instance. If unbridged, the camera will need to be added to HomeKit manually." }, @@ -310,6 +310,7 @@ "cameras[].videoConfig.source", "cameras[].videoConfig.stillImageSource", "cameras[].videoConfig.audio", + "cameras[].unbridge", { "key": "cameras[]", "type": "section", @@ -330,7 +331,6 @@ "expandable": true, "expanded": false, "items": [ - "cameras[].unbridge", "cameras[].videoConfig.maxStreams", "cameras[].videoConfig.maxWidth", "cameras[].videoConfig.maxHeight", diff --git a/package-lock.json b/package-lock.json index 796d4bde..15ffda65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "homebridge-camera-ffmpeg", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "3.1.0", + "version": "3.1.1", "funding": [ { "type": "kofi", @@ -32,12 +32,12 @@ }, "devDependencies": { "@types/node": "^14.14.31", - "@typescript-eslint/eslint-plugin": "^4.15.1", - "@typescript-eslint/parser": "^4.15.1", - "eslint": "^7.20.0", - "homebridge": "^1.3.0", + "@typescript-eslint/eslint-plugin": "^4.16.1", + "@typescript-eslint/parser": "^4.16.1", + "eslint": "^7.21.0", + "homebridge": "^1.3.1", "rimraf": "^3.0.2", - "typescript": "^4.1.5" + "typescript": "^4.2.2" }, "engines": { "homebridge": ">=1.0.0", @@ -133,9 +133,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", + "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -145,7 +145,6 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -222,13 +221,13 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.1.tgz", - "integrity": "sha512-yW2epMYZSpNJXZy22Biu+fLdTG8Mn6b22kR3TqblVk50HGNV8Zya15WAXuQCr8tKw4Qf1BL4QtI6kv6PCkLoJw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz", + "integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "4.15.1", - "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/experimental-utils": "4.16.1", + "@typescript-eslint/scope-manager": "4.16.1", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -254,15 +253,15 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz", - "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz", + "integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/typescript-estree": "4.16.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, @@ -278,14 +277,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.1.tgz", - "integrity": "sha512-V8eXYxNJ9QmXi5ETDguB7O9diAXlIyS+e3xzLoP/oVE4WCAjssxLIa0mqCLsCGXulYJUfT+GV70Jv1vHsdKwtA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz", + "integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/typescript-estree": "4.16.1", "debug": "^4.1.1" }, "engines": { @@ -305,13 +304,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz", - "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", + "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1" + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -322,9 +321,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz", - "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", + "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", "dev": true, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -335,13 +334,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", - "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz", + "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -362,12 +361,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz", - "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", + "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/types": "4.16.1", "eslint-visitor-keys": "^2.0.0" }, "engines": { @@ -1038,13 +1037,13 @@ } }, "node_modules/eslint": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", - "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", + "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.3.0", + "@eslint/eslintrc": "^0.4.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -1057,7 +1056,7 @@ "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", "globals": "^12.1.0", @@ -1283,9 +1282,9 @@ } }, "node_modules/fastq": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", - "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -1305,9 +1304,9 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", - "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { "flat-cache": "^3.0.4" @@ -1584,9 +1583,9 @@ "dev": true }, "node_modules/hap-nodejs": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.9.1.tgz", - "integrity": "sha512-NtGTGf9UwT3QDZZiz23i8DwnlASPT5mmNADTge3AxvwlTo24CLVjDksTDaZCqKJlvcv/wwkmb7B548pdC+2ASg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.9.2.tgz", + "integrity": "sha512-Dm+jM8Fb0R0esDJ508NqluY/9J1azcP5vp2Y9jAGKs72X0HasyMvkMgMQoa/pqd6Psf22J3ohQYZ8rlE0RNspw==", "dev": true, "dependencies": { "@homebridge/ciao": "~1.1.2", @@ -1632,9 +1631,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true, "engines": { "node": ">= 0.4" @@ -1655,15 +1654,15 @@ } }, "node_modules/homebridge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.3.0.tgz", - "integrity": "sha512-3ex//Kox7wmgkI0DgesLGJtb/BIEtcuq/IEsXLpz7Kcg9DB+gNXUE0vm3RmSxZX6jPOyRkTDkoLivy2HhiEX3Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.3.1.tgz", + "integrity": "sha512-JTADGF5E2nE2WXD4KUh8qEd4zCw7Pmy/0Htw8+qReDgD2/ARcDjCDl4G8QFlYe6UAbY5kt6RNzVBO/ktUcEckQ==", "dev": true, "dependencies": { "chalk": "^4.1.0", "commander": "5.1.0", "fs-extra": "^9.1.0", - "hap-nodejs": "0.9.1", + "hap-nodejs": "0.9.2", "qrcode-terminal": "^0.12.0", "semver": "^7.3.4", "source-map-support": "^0.5.19" @@ -2860,12 +2859,12 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", - "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3" }, "funding": { @@ -2873,12 +2872,12 @@ } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", - "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3" }, "funding": { @@ -3114,9 +3113,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "node_modules/typescript": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", - "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3367,9 +3366,9 @@ } }, "@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", + "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -3379,7 +3378,6 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" } @@ -3443,13 +3441,13 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.1.tgz", - "integrity": "sha512-yW2epMYZSpNJXZy22Biu+fLdTG8Mn6b22kR3TqblVk50HGNV8Zya15WAXuQCr8tKw4Qf1BL4QtI6kv6PCkLoJw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz", + "integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.15.1", - "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/experimental-utils": "4.16.1", + "@typescript-eslint/scope-manager": "4.16.1", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -3459,55 +3457,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz", - "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz", + "integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/typescript-estree": "4.16.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.1.tgz", - "integrity": "sha512-V8eXYxNJ9QmXi5ETDguB7O9diAXlIyS+e3xzLoP/oVE4WCAjssxLIa0mqCLsCGXulYJUfT+GV70Jv1vHsdKwtA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz", + "integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/typescript-estree": "4.16.1", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz", - "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", + "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1" + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1" } }, "@typescript-eslint/types": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz", - "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", + "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", - "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz", + "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -3516,12 +3514,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz", - "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", + "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/types": "4.16.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -4036,13 +4034,13 @@ "dev": true }, "eslint": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", - "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", + "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.3.0", + "@eslint/eslintrc": "^0.4.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -4055,7 +4053,7 @@ "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", "globals": "^12.1.0", @@ -4228,9 +4226,9 @@ "dev": true }, "fastq": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", - "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -4249,9 +4247,9 @@ } }, "file-entry-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", - "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" @@ -4475,9 +4473,9 @@ "dev": true }, "hap-nodejs": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.9.1.tgz", - "integrity": "sha512-NtGTGf9UwT3QDZZiz23i8DwnlASPT5mmNADTge3AxvwlTo24CLVjDksTDaZCqKJlvcv/wwkmb7B548pdC+2ASg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.9.2.tgz", + "integrity": "sha512-Dm+jM8Fb0R0esDJ508NqluY/9J1azcP5vp2Y9jAGKs72X0HasyMvkMgMQoa/pqd6Psf22J3ohQYZ8rlE0RNspw==", "dev": true, "requires": { "@homebridge/ciao": "~1.1.2", @@ -4516,9 +4514,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "help-me": { @@ -4533,15 +4531,15 @@ } }, "homebridge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.3.0.tgz", - "integrity": "sha512-3ex//Kox7wmgkI0DgesLGJtb/BIEtcuq/IEsXLpz7Kcg9DB+gNXUE0vm3RmSxZX6jPOyRkTDkoLivy2HhiEX3Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.3.1.tgz", + "integrity": "sha512-JTADGF5E2nE2WXD4KUh8qEd4zCw7Pmy/0Htw8+qReDgD2/ARcDjCDl4G8QFlYe6UAbY5kt6RNzVBO/ktUcEckQ==", "dev": true, "requires": { "chalk": "^4.1.0", "commander": "5.1.0", "fs-extra": "^9.1.0", - "hap-nodejs": "0.9.1", + "hap-nodejs": "0.9.2", "qrcode-terminal": "^0.12.0", "semver": "^7.3.4", "source-map-support": "^0.5.19" @@ -5383,22 +5381,22 @@ } }, "string.prototype.trimend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", - "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3" } }, "string.prototype.trimstart": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", - "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3" } }, @@ -5595,9 +5593,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", - "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", "dev": true }, "unc-path-regex": { diff --git a/package.json b/package.json index 4e3a6f25..8c8feec4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Homebridge Camera FFmpeg", "name": "homebridge-camera-ffmpeg", - "version": "3.1.0", + "version": "3.1.1", "description": "Homebridge Plugin Providing FFmpeg-based Camera Support", "main": "dist/index.js", "license": "ISC", @@ -60,12 +60,12 @@ ], "devDependencies": { "@types/node": "^14.14.31", - "@typescript-eslint/eslint-plugin": "^4.15.1", - "@typescript-eslint/parser": "^4.15.1", - "eslint": "^7.20.0", - "homebridge": "^1.3.0", + "@typescript-eslint/eslint-plugin": "^4.16.1", + "@typescript-eslint/parser": "^4.16.1", + "eslint": "^7.21.0", + "homebridge": "^1.3.1", "rimraf": "^3.0.2", - "typescript": "^4.1.5" + "typescript": "^4.2.2" }, "dependencies": { "ffmpeg-for-homebridge": "^0.0.9", diff --git a/src/ffmpeg.ts b/src/ffmpeg.ts index 083a1e79..81c5d720 100644 --- a/src/ffmpeg.ts +++ b/src/ffmpeg.ts @@ -4,6 +4,18 @@ import { Writable } from 'stream'; import { Logger } from './logger'; import { StreamingDelegate } from './streamingDelegate'; +interface FfmpegProgress { + frame?: number; + fps?: number; + stream_0_0_q?: number; + bitrate?: number; + total_size?: number; + out_time_us?: number; + dup_frames?: number; + drop_frames?: number; + speed?: number; +} + export class FfmpegProcess { private readonly process: ChildProcess; @@ -12,29 +24,37 @@ export class FfmpegProcess { log.debug('Stream command: ' + videoProcessor + ' ' + ffmpegArgs, cameraName, debug); let started = false; + const startTime = Date.now(); this.process = spawn(videoProcessor, ffmpegArgs.split(/\s+/), { env: process.env }); - if (this.process.stdin) { - this.process.stdin.on('error', (error: Error) => { - if (!error.message.includes('EPIPE')) { - log.error(error.message, cameraName); - } - }); - } - if (this.process.stderr) { - this.process.stderr.on('data', (data) => { - if (!started) { - started = true; - if (callback) { - callback(); - } + this.process.stdout?.on('data', () => { + //const progress = this.parseProgress(data); + if (!started) { + started = true; + if (callback) { + callback(); } - - if (debug) { - data.toString().split(/\n/).forEach((line: string) => { - log.debug(line, cameraName, debug); - }); + const runtime = (Date.now() - startTime) / 1000; + const message = 'Getting the first frames took ' + runtime + ' seconds.'; + if (runtime < 5) { + log.debug(message, cameraName, debug); + } else if (runtime < 22) { + log.warn(message, cameraName); + } else { + log.error(message, cameraName); } + } + }); + this.process.stdin?.on('error', (error: Error) => { + if (!error.message.includes('EPIPE')) { + log.error(error.message, cameraName); + } + }); + if (debug) { + this.process.stderr?.on('data', (data) => { + data.toString().split('\n').forEach((line: string) => { + log.debug(line, cameraName, true); + }); }); } this.process.on('error', (error: Error) => { @@ -65,6 +85,28 @@ export class FfmpegProcess { }); } + parseProgress(data: Uint8Array): FfmpegProgress | undefined { + const input = data.toString(); + + if (input.indexOf('frame=') == 0) { + const progress: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + input.split('\n').forEach((line) => { + const split = line.split('=', 2); + + const key = split[0]; + const value = parseFloat(split[1]); + + if (!isNaN(value)) { + progress[key] = value; + } + }); + + return progress; + } else { + return undefined; + } + } + public stop(): void { this.process.kill('SIGKILL'); } diff --git a/src/index.ts b/src/index.ts index 6de3aa7a..fd70fd95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,6 +47,11 @@ class FfmpegPlatform implements DynamicPlatformPlugin { this.api = api; this.config = config as FfmpegPlatformConfig; + if (__dirname.includes('hoobs')) { + this.log.warn('This plugin has not been tested under HOOBS, it is highly recommended that ' + + 'you switch to Homebridge: https://git.io/Jtxb0'); + } + this.config.cameras?.forEach((cameraConfig: CameraConfig) => { let error = false; @@ -254,8 +259,8 @@ class FfmpegPlatform implements DynamicPlatformPlugin { this.motionTimers.delete(accessory.UUID); } const motionTrigger = accessory.getServiceById(hap.Service.Switch, 'MotionTrigger'); + const config = this.cameraConfigs.get(accessory.UUID); if (active) { - const config = this.cameraConfigs.get(accessory.UUID); motionSensor.updateCharacteristic(hap.Characteristic.MotionDetected, true); if (motionTrigger) { motionTrigger.updateCharacteristic(hap.Characteristic.On, true); @@ -289,6 +294,9 @@ class FfmpegPlatform implements DynamicPlatformPlugin { if (motionTrigger) { motionTrigger.updateCharacteristic(hap.Characteristic.On, false); } + if (config?.motionDoorbell) { + this.doorbellHandler(accessory, false); + } return { error: false, message: 'Motion switched off.' diff --git a/src/streamingDelegate.ts b/src/streamingDelegate.ts index c5105755..87a63bac 100644 --- a/src/streamingDelegate.ts +++ b/src/streamingDelegate.ts @@ -61,9 +61,11 @@ export class StreamingDelegate implements CameraStreamingDelegate { private readonly hap: HAP; private readonly log: Logger; private readonly cameraName: string; + private readonly unbridge: boolean; private readonly videoConfig: VideoConfig; private readonly videoProcessor: string; readonly controller: CameraController; + private snapshotPromise?: Promise; // keep track of sessions pendingSessions: Record = {}; @@ -72,10 +74,11 @@ export class StreamingDelegate implements CameraStreamingDelegate { constructor(log: Logger, cameraConfig: CameraConfig, api: API, hap: HAP, videoProcessor?: string) { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types this.log = log; - this.videoConfig = cameraConfig.videoConfig!; this.hap = hap; this.cameraName = cameraConfig.name!; + this.unbridge = cameraConfig.unbridge ?? false; + this.videoConfig = cameraConfig.videoConfig!; this.videoProcessor = videoProcessor || ffmpegPath || 'ffmpeg'; api.on(APIEvent.SHUTDOWN, () => { @@ -114,6 +117,8 @@ export class StreamingDelegate implements CameraStreamingDelegate { { type: AudioStreamingCodecType.AAC_ELD, samplerate: AudioStreamingSamplerate.KHZ_16 + /*type: AudioStreamingCodecType.OPUS, + samplerate: AudioStreamingSamplerate.KHZ_24*/ } ] } @@ -141,14 +146,11 @@ export class StreamingDelegate implements CameraStreamingDelegate { const noneFilter = filters.indexOf('none'); if (noneFilter >= 0) { filters.splice(noneFilter, 1); - } - if (noneFilter < 0) { - if (width > 0 || height > 0) { - filters.push('scale=' + (width > 0 ? '\'min(' + width + ',iw)\'' : 'iw') + ':' + - (height > 0 ? '\'min(' + height + ',ih)\'' : 'ih') + - ':force_original_aspect_ratio=decrease'); - filters.push('scale=trunc(iw/2)*2:trunc(ih/2)*2'); // Force to fit encoder restrictions - } + } else if (width > 0 || height > 0) { + filters.push('scale=' + (width > 0 ? '\'min(' + width + ',iw)\'' : 'iw') + ':' + + (height > 0 ? '\'min(' + height + ',ih)\'' : 'ih') + + ':force_original_aspect_ratio=decrease'); + filters.push('scale=trunc(iw/2)*2:trunc(ih/2)*2'); // Force to fit encoder restrictions } return { @@ -158,35 +160,92 @@ export class StreamingDelegate implements CameraStreamingDelegate { }; } - handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void { - const resolution = this.determineResolution(request, true); + fetchSnapshot(): Promise { + this.snapshotPromise = new Promise((resolve) => { + const startTime = Date.now(); + const ffmpegArgs = (this.videoConfig.stillImageSource || this.videoConfig.source!) + // Still + ' -frames:v 1' + + ' -f image2 -'; - this.log.debug('Snapshot requested: ' + request.width + ' x ' + request.height, this.cameraName, this.videoConfig.debug); - this.log.debug('Sending snapshot: ' + (resolution.width > 0 ? resolution.width : 'native') + ' x ' + - (resolution.height > 0 ? resolution.height : 'native'), this.cameraName, this.videoConfig.debug); + this.log.debug('Snapshot command: ' + this.videoProcessor + ' ' + ffmpegArgs, this.cameraName, this.videoConfig.debug); + const ffmpeg = spawn(this.videoProcessor, ffmpegArgs.split(/\s+/), { env: process.env }); - let ffmpegArgs = this.videoConfig.stillImageSource || this.videoConfig.source!; + let snapshotBuffer = Buffer.alloc(0); + ffmpeg.stdout.on('data', (data) => { + snapshotBuffer = Buffer.concat([snapshotBuffer, data]); + }); + ffmpeg.on('error', (error: Error) => { + this.log.error('An error occurred while making snapshot request: ' + error.message, this.cameraName); + }); + ffmpeg.on('close', () => { + resolve(snapshotBuffer); + + setTimeout(() => { + this.snapshotPromise = undefined; + }, 3 * 1000); // Expire cached snapshot after 3 seconds + + const runtime = (Date.now() - startTime) / 1000; + let message = 'Fetching snapshot took ' + runtime + ' seconds.'; + if (runtime < 5) { + this.log.debug(message, this.cameraName, this.videoConfig.debug); + } else { + if (!this.unbridge) { + message += ' It is highly recommended you switch to unbridge mode.'; + } + if (runtime < 22) { + this.log.warn(message, this.cameraName); + } else { + message += ' The request has timed out and the snapshot has not been refreshed in HomeKit.'; + this.log.error(message, this.cameraName); + } + } + }); + }); + return this.snapshotPromise; + } - ffmpegArgs += // Still - ' -frames:v 1' + - (resolution.videoFilter ? ' -filter:v ' + resolution.videoFilter : '') + - ' -f image2 -'; + resizeSnapshot(snapshot: Buffer, resolution: ResolutionInfo): Promise { + return new Promise((resolve) => { + const ffmpegArgs = '-i pipe:' + // Resize + ' -frames:v 1' + + (resolution.videoFilter ? ' -filter:v ' + resolution.videoFilter : '') + + ' -f image2 -'; - try { const ffmpeg = spawn(this.videoProcessor, ffmpegArgs.split(/\s+/), { env: process.env }); - let imageBuffer = Buffer.alloc(0); - this.log.debug('Snapshot command: ' + this.videoProcessor + ' ' + ffmpegArgs, this.cameraName, this.videoConfig.debug); - ffmpeg.stdout.on('data', (data: Uint8Array) => { - imageBuffer = Buffer.concat([imageBuffer, data]); + let resizeBuffer = Buffer.alloc(0); + this.log.debug('Resize command: ' + this.videoProcessor + ' ' + ffmpegArgs, this.cameraName, this.videoConfig.debug); + ffmpeg.stdout.on('data', (data) => { + resizeBuffer = Buffer.concat([resizeBuffer, data]); }); const log = this.log; - ffmpeg.on('error', (error: string) => { - log.error('An error occurred while making snapshot request: ' + error, this.cameraName); + ffmpeg.on('error', (error: Error) => { + log.error('An error occurred while resizing snapshot: ' + error.message, this.cameraName); }); ffmpeg.on('close', () => { - callback(undefined, imageBuffer); + resolve(resizeBuffer); }); + ffmpeg.stdin.end(snapshot); + }); + } + + async handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): Promise { + const resolution = this.determineResolution(request, true); + + try { + const cachedSnapshot = !!this.snapshotPromise; + + this.log.debug('Snapshot requested: ' + request.width + ' x ' + request.height, + this.cameraName, this.videoConfig.debug); + + const snapshot = await (this.snapshotPromise || this.fetchSnapshot()); + + this.log.debug('Sending snapshot: ' + (resolution.width > 0 ? resolution.width : 'native') + ' x ' + + (resolution.height > 0 ? resolution.height : 'native') + + (cachedSnapshot ? ' (cached)' : ''), this.cameraName, this.videoConfig.debug); + + const resized = await this.resizeSnapshot(snapshot, resolution); + callback(undefined, resized); } catch (err) { this.log.error(err, this.cameraName); callback(err); @@ -269,7 +328,8 @@ export class StreamingDelegate implements CameraStreamingDelegate { request.video.fps + ' fps, ' + request.video.max_bit_rate + ' kbps', this.cameraName, this.videoConfig.debug); this.log.info('Starting video stream: ' + (resolution.width > 0 ? resolution.width : 'native') + ' x ' + (resolution.height > 0 ? resolution.height : 'native') + ', ' + (fps > 0 ? fps : 'native') + - ' fps, ' + (videoBitrate > 0 ? videoBitrate : '???') + ' kbps', this.cameraName); + ' fps, ' + (videoBitrate > 0 ? videoBitrate : '???') + ' kbps' + + (this.videoConfig.audio ? (' (' + request.audio.codec + ')') : ''), this.cameraName); let ffmpegArgs = this.videoConfig.source!; @@ -294,30 +354,39 @@ export class StreamingDelegate implements CameraStreamingDelegate { '?rtcpport=' + sessionInfo.videoPort + '&pkt_size=' + mtu; if (this.videoConfig.audio) { - ffmpegArgs += // Audio - (this.videoConfig.mapaudio ? ' -map ' + this.videoConfig.mapaudio : ' -vn -sn -dn') + - ' -codec:a libfdk_aac' + - ' -profile:a aac_eld' + - ' -flags +global_header' + - ' -f null' + - ' -ar ' + request.audio.sample_rate + 'k' + - ' -b:a ' + request.audio.max_bit_rate + 'k' + - ' -ac ' + request.audio.channel + - ' -payload_type ' + request.audio.pt; - - ffmpegArgs += // Audio Stream - ' -ssrc ' + sessionInfo.audioSSRC + - ' -f rtp' + - ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' + - ' -srtp_out_params ' + sessionInfo.audioSRTP.toString('base64') + - ' srtp://' + sessionInfo.address + ':' + sessionInfo.audioPort + - '?rtcpport=' + sessionInfo.audioPort + '&pkt_size=188'; + if (request.audio.codec === AudioStreamingCodecType.OPUS || request.audio.codec === AudioStreamingCodecType.AAC_ELD) { + ffmpegArgs += // Audio + (this.videoConfig.mapaudio ? ' -map ' + this.videoConfig.mapaudio : ' -vn -sn -dn') + + (request.audio.codec === AudioStreamingCodecType.OPUS ? + ' -codec:a libopus' + + ' -application lowdelay' : + ' -codec:a libfdk_aac' + + ' -profile:a aac_eld') + + ' -flags +global_header' + + ' -f null' + + ' -ar ' + request.audio.sample_rate + 'k' + + ' -b:a ' + request.audio.max_bit_rate + 'k' + + ' -ac ' + request.audio.channel + + ' -payload_type ' + request.audio.pt; + + ffmpegArgs += // Audio Stream + ' -ssrc ' + sessionInfo.audioSSRC + + ' -f rtp' + + ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' + + ' -srtp_out_params ' + sessionInfo.audioSRTP.toString('base64') + + ' srtp://' + sessionInfo.address + ':' + sessionInfo.audioPort + + '?rtcpport=' + sessionInfo.audioPort + '&pkt_size=188'; + } else { + this.log.error('Unsupported audio codec requested: ' + request.audio.codec, this.cameraName); + } } if (this.videoConfig.debug) { ffmpegArgs += ' -loglevel level+verbose'; } + ffmpegArgs += ' -progress pipe:1'; + const activeSession: ActiveSession = {}; activeSession.socket = createSocket(sessionInfo.ipv6 ? 'udp6' : 'udp4'); @@ -333,7 +402,7 @@ export class StreamingDelegate implements CameraStreamingDelegate { this.log.info('Device appears to be inactive. Stopping stream.', this.cameraName); this.controller.forceStopStreamingSession(request.sessionID); this.stopStream(request.sessionID); - }, request.video.rtcp_interval * 2 * 1000); + }, request.video.rtcp_interval * 5 * 1000); }); activeSession.socket.bind(sessionInfo.videoReturnPort);