diff --git a/plugins/prebuffer-mixin/package-lock.json b/plugins/prebuffer-mixin/package-lock.json index 37b5eba52..1b3e1976d 100644 --- a/plugins/prebuffer-mixin/package-lock.json +++ b/plugins/prebuffer-mixin/package-lock.json @@ -10,13 +10,15 @@ "license": "Apache-2.0", "dependencies": { "@scrypted/common": "file:../../common", + "@scrypted/libav": "^1.0.193", "@scrypted/sdk": "file:../../sdk", "h264-sps-parser": "^0.2.1", "semver": "^7.3.7" }, "devDependencies": { "@types/node": "^22.13.14", - "@types/semver": "^7.3.12" + "@types/semver": "^7.3.12", + "prebuild-install": "npm:@scrypted/prebuild-install@^7.1.9" } }, "../../common": { @@ -78,10 +80,34 @@ "../sdk": { "extraneous": true }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@scrypted/common": { "resolved": "../../common", "link": true }, + "node_modules/@scrypted/libav": { + "version": "1.0.193", + "resolved": "https://registry.npmjs.org/@scrypted/libav/-/libav-1.0.193.tgz", + "integrity": "sha512-f/fK9c3wsFaQp987IkFZtV9Eiq/K6dgw8Xtxr7QjWBATwtkNfmeF2sN8YQxqVwhUsong6TLxyDJl09UgXo/QwQ==", + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^2.0.3", + "follow-redirects": "^1.15.9", + "node-addon-api": "^8.3.1", + "tar": "^7.4.3" + } + }, "node_modules/@scrypted/sdk": { "resolved": "../../sdk", "link": true @@ -102,11 +128,369 @@ "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/h264-sps-parser": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/h264-sps-parser/-/h264-sps-parser-0.2.1.tgz", "integrity": "sha512-1OCliBfrgCe2fu6eRRqN0xvhp9tFH/UVa1zdVmX/WcQAZrEB6xm/2RJ13F4h2OtQUMJqWv31fZakmTgrvbyIDA==" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "name": "@scrypted/prebuild-install", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.9.tgz", + "integrity": "sha512-lKKX7TMlwToQIH2UX8p0JwFbp0Y2dwedEqgkpcTmRvaA9DOfhau7m40i4+Qo7F1ISMHo/NK1XntURrFlSm4ibg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "follow-redirects": "^1.15.6", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -119,15 +503,133 @@ "node": ">=10" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } } }, "dependencies": { + "@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "requires": { + "minipass": "^7.0.4" + } + }, "@scrypted/common": { "version": "file:../../common", "requires": { @@ -139,6 +641,17 @@ "typescript": "^5.5.3" } }, + "@scrypted/libav": { + "version": "1.0.193", + "resolved": "https://registry.npmjs.org/@scrypted/libav/-/libav-1.0.193.tgz", + "integrity": "sha512-f/fK9c3wsFaQp987IkFZtV9Eiq/K6dgw8Xtxr7QjWBATwtkNfmeF2sN8YQxqVwhUsong6TLxyDJl09UgXo/QwQ==", + "requires": { + "detect-libc": "^2.0.3", + "follow-redirects": "^1.15.9", + "node-addon-api": "^8.3.1", + "tar": "^7.4.3" + } + }, "@scrypted/sdk": { "version": "file:../../sdk", "requires": { @@ -182,21 +695,319 @@ "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, "h264-sps-parser": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/h264-sps-parser/-/h264-sps-parser-0.2.1.tgz", "integrity": "sha512-1OCliBfrgCe2fu6eRRqN0xvhp9tFH/UVa1zdVmX/WcQAZrEB6xm/2RJ13F4h2OtQUMJqWv31fZakmTgrvbyIDA==" }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "requires": { + "minipass": "^7.1.2" + } + }, + "mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "prebuild-install": { + "version": "npm:@scrypted/prebuild-install@7.1.9", + "resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.9.tgz", + "integrity": "sha512-lKKX7TMlwToQIH2UX8p0JwFbp0Y2dwedEqgkpcTmRvaA9DOfhau7m40i4+Qo7F1ISMHo/NK1XntURrFlSm4ibg==", + "dev": true, + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "follow-redirects": "^1.15.6", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, "semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + }, + "tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + } + }, + "tar-fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" } } } diff --git a/plugins/prebuffer-mixin/package.json b/plugins/prebuffer-mixin/package.json index a61685198..4427d0060 100644 --- a/plugins/prebuffer-mixin/package.json +++ b/plugins/prebuffer-mixin/package.json @@ -38,12 +38,14 @@ }, "dependencies": { "@scrypted/common": "file:../../common", + "@scrypted/libav": "^1.0.193", "@scrypted/sdk": "file:../../sdk", "h264-sps-parser": "^0.2.1", "semver": "^7.3.7" }, "devDependencies": { "@types/node": "^22.13.14", - "@types/semver": "^7.3.12" + "@types/semver": "^7.3.12", + "prebuild-install": "npm:@scrypted/prebuild-install@^7.1.9" } } diff --git a/plugins/prebuffer-mixin/src/ffmpeg-rebroadcast.ts b/plugins/prebuffer-mixin/src/ffmpeg-session.ts similarity index 100% rename from plugins/prebuffer-mixin/src/ffmpeg-rebroadcast.ts rename to plugins/prebuffer-mixin/src/ffmpeg-session.ts diff --git a/plugins/prebuffer-mixin/src/libav-parser.ts b/plugins/prebuffer-mixin/src/libav-parser.ts new file mode 100644 index 000000000..bb64fda1f --- /dev/null +++ b/plugins/prebuffer-mixin/src/libav-parser.ts @@ -0,0 +1,214 @@ +import net from 'net'; +import tls from 'tls'; +import { Deferred } from "@scrypted/common/src/deferred"; +import { parseSdp } from "@scrypted/common/src/sdp-utils"; +import { StreamChunk } from "@scrypted/common/src/stream-parser"; +import { AVFormatContext, createAVFormatContext } from '@scrypted/libav'; +import { ResponseMediaStreamOptions } from "@scrypted/sdk"; +import { EventEmitter } from "stream"; +import { ParserSession, setupActivityTimer } from "./ffmpeg-session"; +import { negotiateMediaStream } from "./rfc4571"; +import { installLibavAddon } from "./libav-setup"; +import { RTSP_FRAME_MAGIC } from "../../../common/src/rtsp-server"; +import { sleep } from "@scrypted/common/src/sleep"; +import { once } from 'events'; + +export async function startLibavSession(console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: { + useUdp: boolean, + audioSoftMuted: boolean, + activityTimeout: number, +}): Promise> { + await installLibavAddon(); + + const formatContext = createAVFormatContext(); + try { + return await startLibavSessionWrapped(formatContext, console, url, mediaStreamOptions, options); + } + catch (e) { + await formatContext.close(); + throw e; + } +} + +export async function startLibavSessionWrapped(formatContext: AVFormatContext, console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: { + useUdp: boolean, + audioSoftMuted: boolean, + activityTimeout: number, +}): Promise> { + const events = new EventEmitter(); + + let tlsProxy: net.Server; + try { + if (url.startsWith('rtsps:') || url.startsWith('https:')) { + let { hostname, port } = new URL(url); + if (!port) { + if (url.startsWith('rtsps:')) + port = '322'; + else + port = '443'; + } + + const portNumber = parseInt(port); + if (!portNumber) + throw new Error('invalid port number'); + + tlsProxy = net.createServer(async socket => { + try { + const tlsSocket = tls.connect({ + host: hostname, + port: portNumber, + rejectUnauthorized: false, + }); + await once(tlsSocket, 'secureConnect'); + socket.on('data', data => { + console.log('proxy data', data.toString()); + }); + + socket.pipe(tlsSocket).pipe(socket); + } + catch (e) { + console.error('tls proxy error', e); + socket.destroy(); + } + }); + + tlsProxy.listen(0, '127.0.0.1'); + await once(tlsProxy, 'listening'); + const localPort = (tlsProxy.address() as net.AddressInfo).port; + // rewrite the url to use the local port + const u = new URL(url); + u.protocol = u.protocol.replace('s:', ':'); + u.hostname = '127.0.0.1'; + u.port = localPort.toString(); + url = u.toString(); + } + + await formatContext.open(url, { + rtsp_transport: options.useUdp ? 'udp' : 'tcp', + }); + } + catch (e) { + tlsProxy?.close(); + throw e; + } + + let sdp = formatContext.createSDP(); + const parsedSdp = parseSdp(sdp); + // sdp may contain multiple audio/video sections. take only the first video section. + sdp = [...parsedSdp.header.lines, ...parsedSdp.msections.map(msection => msection.lines).flat()].join('\r\n'); + + const killDeferred = new Deferred(); + const startDeferred = new Deferred(); + killDeferred.promise.catch(e => { + events.emit('killed'); + events.emit('error', e); + tlsProxy?.close(); + }); + + const kill = (e?: Error) => { + killDeferred.reject(e || new Error('killed')); + startDeferred.reject(e || new Error('killed')); + } + + const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.activityTimeout); + + (async () => { + const indexToContext = new Map(); + try { + await startDeferred.promise; + + formatContext.streams.forEach(stream => { + if (options.audioSoftMuted && stream.type === 'audio') + return; + if (stream.type !== 'video' && stream.type !== 'audio') + return; + + const { codec } = stream; + const rtp = createAVFormatContext(); + rtp.create('rtp', rtp => { + const prefix = Buffer.alloc(4); + prefix.writeUInt8(RTSP_FRAME_MAGIC, 0); + prefix.writeUInt8(stream.index, 1); + prefix.writeUInt16BE(rtp.length, 2); + + const chunk: StreamChunk = { + chunks: [prefix, rtp], + type: codec === 'hevc' ? 'h265' : codec, + }; + + events.emit('rtsp', chunk); + }); + const index = rtp.newStream({ + formatContext, + streamIndex: stream.index, + }); + indexToContext.set(stream.index, { + rtp, + index, + }); + }); + + while (!killDeferred.finished) { + using packet = await formatContext.readFrame(); + if (killDeferred.finished) + break; + if (!packet) + continue; + const context = indexToContext.get(packet.streamIndex); + if (!context) + continue; + context.rtp.writeFrame(context.index, packet); + resetActivityTimer?.(); + } + } + catch (e) { + kill(e); + } + finally { + kill(new Error('rtsp read loop exited')); + + await sleep(1000); + + for (const context of indexToContext.values()) { + await context.rtp.close(); + } + indexToContext.clear(); + await formatContext.close(); + } + })(); + + return { + start: () => { + startDeferred.resolve(); + }, + sdp: Promise.resolve(sdp), + get isActive() { return !killDeferred.finished }, + kill(error?: Error) { + kill(error); + }, + killed: killDeferred.promise, + resetActivityTimer, + negotiateMediaStream: (requestMediaStream, inputVideoCodec, inputAudioCodec) => { + return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream); + }, + emit(container: 'rtsp', chunk: StreamChunk) { + events.emit(container, chunk); + return this; + }, + on(event: string, cb: any) { + events.on(event, cb); + return this; + }, + once(event: any, cb: any) { + events.once(event, cb); + return this; + }, + removeListener(event, cb) { + events.removeListener(event, cb); + return this; + } + } +} \ No newline at end of file diff --git a/plugins/prebuffer-mixin/src/libav-setup.ts b/plugins/prebuffer-mixin/src/libav-setup.ts new file mode 100644 index 000000000..6be2aec08 --- /dev/null +++ b/plugins/prebuffer-mixin/src/libav-setup.ts @@ -0,0 +1,19 @@ +import * as libav from '@scrypted/libav'; +import path from 'path'; + +function getAddonInstallPath() { + if (process.versions.electron) + process.env.npm_config_runtime = 'electron'; + const binaryUrl = libav.getBinaryUrl(); + const u = new URL(binaryUrl); + const withoutExtension = path.basename(u.pathname).replace(/\.tar.gz$/, ''); + return path.join(process.env.SCRYPTED_PLUGIN_VOLUME, libav.version, withoutExtension); +} + +export async function installLibavAddon(installOnly = false) { + const nr = installOnly + ? null + // @ts-expect-error + : __non_webpack_require__; + await libav.install(getAddonInstallPath(), nr); +} diff --git a/plugins/prebuffer-mixin/src/main.ts b/plugins/prebuffer-mixin/src/main.ts index 234e155e1..45e3b57cf 100644 --- a/plugins/prebuffer-mixin/src/main.ts +++ b/plugins/prebuffer-mixin/src/main.ts @@ -14,7 +14,7 @@ import { parse as h264SpsParse } from "h264-sps-parser"; import net, { AddressInfo } from 'net'; import path from 'path'; import { Duplex } from 'stream'; -import { ParserOptions, ParserSession, startParserSession } from './ffmpeg-rebroadcast'; +import { ParserOptions, ParserSession, startParserSession } from './ffmpeg-session'; import { FileRtspServer } from './file-rtsp-server'; import { getUrlLocalAdresses } from './local-addresses'; import { REBROADCAST_MIXIN_INTERFACE_TOKEN } from './rebroadcast-mixin-token'; @@ -22,6 +22,7 @@ import { connectRFC4571Parser, startRFC4571Parser } from './rfc4571'; import { RtspSessionParserSpecific, startRtspSession } from './rtsp-session'; import { getSpsResolution } from './sps-resolution'; import { createStreamSettings } from './stream-settings'; +import { startLibavSession } from './libav-parser'; const { mediaManager, log, systemManager, deviceManager } = sdk; @@ -32,6 +33,8 @@ const SCRYPTED_PARSER_TCP = 'Scrypted (TCP)'; const SCRYPTED_PARSER_UDP = 'Scrypted (UDP)'; const FFMPEG_PARSER_TCP = 'FFmpeg (TCP)'; const FFMPEG_PARSER_UDP = 'FFmpeg (UDP)'; +const LIBAV_PARSER_TCP = 'Scrypted libav (TCP)'; +const LIBAV_PARSER_UDP = 'Scrypted libav (UDP)'; const STRING_DEFAULT = 'Default'; interface PrebufferStreamChunk extends StreamChunk { @@ -59,7 +62,6 @@ class PrebufferSession { rtspPrebuffer: PrebufferStreamChunk[] = [] parsers: { [container: string]: StreamParser }; sdp: Promise; - usingScryptedParser = false; usingScryptedUdpParser = false; mixinDevice: VideoCamera; @@ -236,6 +238,8 @@ class PrebufferSession { rtspParser = localStorage.getItem('defaultRtspParser'); } switch (rtspParser) { + case LIBAV_PARSER_TCP: + case LIBAV_PARSER_UDP: case FFMPEG_PARSER_TCP: case FFMPEG_PARSER_UDP: case SCRYPTED_PARSER_TCP: @@ -392,6 +396,8 @@ class PrebufferSession { SCRYPTED_PARSER_UDP, FFMPEG_PARSER_TCP, FFMPEG_PARSER_UDP, + LIBAV_PARSER_TCP, + LIBAV_PARSER_UDP, ], } ); @@ -542,12 +548,11 @@ class PrebufferSession { // before launching the parser session, clear out the last detected codec. // an erroneous cached codec could cause ffmpeg to fail to start. this.storage.removeItem(this.lastDetectedAudioCodecKey); - this.usingScryptedParser = false; - + let usingScryptedParser = false; const h264Oddities = this.getLastH264Oddities(); if (isRfc4571) { - this.usingScryptedParser = true; + usingScryptedParser = true; this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser') const json = await mediaManager.convertMediaObjectToJSON(mo, 'x-scrypted/x-rfc4571'); let { url, sdp, mediaStreamOptions } = json; @@ -566,21 +571,22 @@ class PrebufferSession { sessionMso = ffmpegInput.mediaStreamOptions || this.advertisedMediaStreamOptions; let { parser, isDefault } = this.getParser(sessionMso); - this.usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP; + usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP; + const usingLibavParser = parser === LIBAV_PARSER_TCP || parser === LIBAV_PARSER_UDP; this.usingScryptedUdpParser = parser === SCRYPTED_PARSER_UDP; // prefer ffmpeg if this is a prebuffered stream. if (isDefault - && this.usingScryptedParser + && usingScryptedParser && h264Oddities && !this.stopInactive && sessionMso.tool !== 'scrypted') { this.console.warn('H264 oddities were detected in prebuffered video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted.'); - this.usingScryptedParser = false; + usingScryptedParser = false; parser = FFMPEG_PARSER_TCP; } - if (this.usingScryptedParser) { + if (usingScryptedParser) { const rtspParser = createRtspParser(); rbo.parsers.rtsp = rtspParser; @@ -590,6 +596,16 @@ class PrebufferSession { rtspRequestTimeout: 10000, }); } + else if (usingLibavParser) { + const rtspParser = createRtspParser(); + rbo.parsers.rtsp = rtspParser; + + session = await startLibavSession(this.console, ffmpegInput.url, ffmpegInput.mediaStreamOptions, { + useUdp: parser === LIBAV_PARSER_UDP, + audioSoftMuted, + activityTimeout: 10000, + }); + } else { let acodec: string[]; @@ -645,7 +661,7 @@ class PrebufferSession { console.error('rebroadcast error', e) }); - if (this.usingScryptedParser && !isRfc4571) { + if (usingScryptedParser && !isRfc4571) { // watch the stream for 10 seconds to see if an weird nalu is encountered. // if one is found and using scrypted parser as default, will need to restart rebroadcast to prevent // downstream issues. @@ -1005,10 +1021,6 @@ class PrebufferSession { const codecInfo = await this.parseCodecs(true); const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options, codecInfo.inputVideoCodec, codecInfo.inputAudioCodec); let sdp = await this.sdp; - if (!mediaStreamOptions.video?.h264Info && this.usingScryptedParser) { - mediaStreamOptions.video ||= {}; - mediaStreamOptions.video.h264Info = this.getLastH264Probe(); - } if (this.mixin.streamSettings.storageSettings.values.noAudio) mediaStreamOptions.audio = null; @@ -1621,6 +1633,8 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP SCRYPTED_PARSER_UDP, FFMPEG_PARSER_TCP, FFMPEG_PARSER_UDP, + LIBAV_PARSER_TCP, + LIBAV_PARSER_UDP, ], onPut: () => { this.log.a('Rebroadcast Plugin will restart momentarily.'); @@ -1753,7 +1767,9 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP this.setHasEnabledMixin(mixinDeviceState.id); const { id } = mixinDeviceState; - const forked = sdk.fork(); + const forked = sdk.fork({ + runtime: 'node', + }); const { worker } = forked; const result = await forked.result; const mixin = await result.newPrebufferMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState); diff --git a/plugins/prebuffer-mixin/src/rfc4571.ts b/plugins/prebuffer-mixin/src/rfc4571.ts index fb7643b1f..37f59c1c3 100644 --- a/plugins/prebuffer-mixin/src/rfc4571.ts +++ b/plugins/prebuffer-mixin/src/rfc4571.ts @@ -7,7 +7,7 @@ import { MediaStreamOptions, ResponseMediaStreamOptions } from "@scrypted/sdk"; import { parse as spsParse } from "h264-sps-parser"; import net from 'net'; import { EventEmitter, Readable } from "stream"; -import { ParserSession, setupActivityTimer } from "./ffmpeg-rebroadcast"; +import { ParserSession, setupActivityTimer } from "./ffmpeg-session"; import { getSpsResolution } from "./sps-resolution"; export function negotiateMediaStream(sdp: string, mediaStreamOptions: MediaStreamOptions, inputVideoCodec: string, inputAudioCodec: string, requestMediaStream: MediaStreamOptions) { diff --git a/plugins/prebuffer-mixin/src/rtsp-session.ts b/plugins/prebuffer-mixin/src/rtsp-session.ts index 2b7b79bec..e6fb2a9f6 100644 --- a/plugins/prebuffer-mixin/src/rtsp-session.ts +++ b/plugins/prebuffer-mixin/src/rtsp-session.ts @@ -5,7 +5,7 @@ import { StreamChunk } from "@scrypted/common/src/stream-parser"; import { ResponseMediaStreamOptions } from "@scrypted/sdk"; import dgram from 'dgram'; import { EventEmitter } from "stream"; -import { ParserSession, setupActivityTimer } from "./ffmpeg-rebroadcast"; +import { ParserSession, setupActivityTimer } from "./ffmpeg-session"; import { negotiateMediaStream } from "./rfc4571"; export type RtspChannelCodecMapping = { [key: number]: string };