@scrypted/prebuffer-mixin 0.1.151 → 0.1.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +24 -26
- package/src/rtsp-server.ts +0 -202
package/dist/main.nodejs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{var e={454:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=o(t);if(r&&r.has(e))return r.get(e);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=n?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=e[s]}i.default=e,r&&r.set(e,i);return i}(r(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}const{systemManager:n}=i.default,s="v4";class AutoenableMixinProvider extends i.ScryptedDeviceBase{constructor(e){var t,r,o;super(e),o={},(r="hasEnabledMixin")in(t=this)?Object.defineProperty(t,r,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[r]=o;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=n.getComponent("plugins"),n.listen((async(e,t,r)=>{t.eventInterface!==i.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)}));for(const e of Object.keys(n.getSystemState())){const t=n.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id]===s)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const r=(e.mixins||[]).slice();r.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,r),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==s&&(this.hasEnabledMixin[e]=s,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=AutoenableMixinProvider},201:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createRebroadcaster=async function(e){let t=0;const r=(0,i.createServer)((r=>{g(r,e),r.once("close",(()=>t--)),t++})),o=await(0,n.listenZero)(r);return{server:r,port:o,get clients(){return t}}},t.handleRebroadcasterClient=g,t.parseAudioCodec=f,t.parseResolution=p,t.parseVideoCodec=h,t.startParserSession=async function(e,t){const{console:r}=t;let i,a,u=!0;const m=new s.EventEmitter;let g,v,y,S,b;m.on("error",(e=>r.error("rebroadcast error",e)));const P=new Promise(((e,t)=>{S=e,b=t}));function M(){var e;u&&(m.emit("killed"),m.emit("error",new Error("killed"))),u=!1,null==C||C.kill(),null==C||C.kill("SIGKILL"),null===(e=b)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(i),clearTimeout(a)}function w(){r.error("timeout waiting for data, killing parser session"),M()}function x(){t.timeout&&(clearTimeout(i),i=setTimeout(w,t.timeout))}x();const O=e.inputArguments.slice();a=setTimeout(M,3e4);const D=["pipe","pipe","pipe"];let I=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){const t=d.default.createSocket("udp4"),r=await(0,n.bindZero)(t),o=d.default.createSocket("udp4");await(0,n.bind)(o,r.port+1),m.once("killed",(()=>{t.close(),o.close()})),O.push(...i.outputArguments,r.url.replace("udp://","rtp://")),(async()=>{for await(const s of i.parseDatagram(t,parseInt(null===(r=y)||void 0===r?void 0:r[2]),parseInt(null===(o=y)||void 0===o?void 0:o[3]))){var r,o,n;null===(n=S)||void 0===n||n(void 0),m.emit(e,s),x()}})(),(async()=>{for await(const s of i.parseDatagram(o,parseInt(null===(t=y)||void 0===t?void 0:t[2]),parseInt(null===(r=y)||void 0===r?void 0:r[3]),"rtcp")){var t,r,n;null===(n=S)||void 0===n||n(void 0),m.emit(e,s),x()}})()}else if(i.tcpProtocol){const t=await(0,n.listenZeroSingleClient)(),o=new URL(i.tcpProtocol);o.port=t.port.toString(),O.push(...i.outputArguments,o.toString()),(async()=>{const o=await t.clientPromise;try{for await(const t of i.parse(o,parseInt(null===(n=y)||void 0===n?void 0:n[2]),parseInt(null===(s=y)||void 0===s?void 0:s[3]))){var n,s,a;null===(a=S)||void 0===a||a(void 0),m.emit(e,t),x()}}catch(e){r.error("rebroadcast parse error",e),M()}})()}else O.push(...i.outputArguments,"pipe:"+I++),D.push("pipe")}O.push("-sdp_file","pipe:"+I++),D.push("pipe"),O.unshift("-hide_banner"),(0,c.safePrintFFmpegArguments)(r,O);const C=o.default.spawn(await l.getFFmpegPath(),O,{stdio:D});(0,c.ffmpegLogInitialOutput)(r,C),C.on("exit",M);const A=new Promise((e=>{const t=[];C.stdio[I-1].on("data",(r=>{t.push(r),e(t)}))}));let k=0;return Object.keys(t.parsers).forEach((async e=>{const i=t.parsers[e];if(!i.parse||i.tcpProtocol)return;const o=C.stdio[3+k];k++;try{for await(const t of i.parse(o,parseInt(null===(n=y)||void 0===n?void 0:n[2]),parseInt(null===(s=y)||void 0===s?void 0:s[3]))){var n,s,a;null===(a=S)||void 0===a||a(void 0),m.emit(e,t),x()}}catch(e){r.error("rebroadcast parse error",e),M()}})),f(C).then((e=>g=e)),h(C).then((e=>v=e)),p(C).then((e=>y=e)),await P,S=void 0,b=void 0,clearTimeout(a),{sdp:A,inputAudioCodec:g,inputVideoCodec:v,inputVideoResolution:y,resetActivityTimer:x,isActive:()=>u,kill:M,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},on(e,t){return m.on(e,t),this},once(e,t){return m.once(e,t),this},removeListener(e,t){return m.removeListener(e,t),this}}};var i=r(808),o=u(r(81)),n=r(769),s=r(361),a=u(r(510)),c=r(833),d=u(r(891));function u(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:l}=a.default;async function p(e){return new Promise((t=>{const r=i=>{const o=i.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(o);n&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(n))};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function m(e,t){return new Promise((r=>{const i=o=>{const n=o.toString(),s=n.indexOf(`${t}: `);if(-1!==s){const o=n.substring(s+t.length+1).trim();let a=o.indexOf(" ");const c=o.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),r(o.substring(0,a)))}};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function h(e){return m(e,"Video")}async function f(e){return m(e,"Audio")}async function g(e,t){const r=await e;let i=!0;const o=()=>{r.removeAllListeners(),r.destroy();const e=n;n=void 0,null==e||e()};let n=null==t?void 0:t.connect((e=>{i&&(i=!1,e.startStream&&r.write(e.startStream));for(const t of e.chunks)r.write(t);return r.writableLength}),o);r.once("close",(()=>{o()})),r.on("error",(e=>{var r;return null==t||null===(r=t.console)||void 0===r?void 0:r.log("client stream ended")}))}},769:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bind=async function(e,t){return e.bind(t),await(0,n.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}},t.bindZero=async function(e){e.bind(0),await(0,n.once)(e,"listening");const{port:t}=e.address();return{port:t,url:`udp://127.0.0.1:${t}`}},t.listenZero=s,t.listenZeroSingleClient=async function(){const e=new o.default.Server,t=await s(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{r(new Error("timeout waiting for client"))}),3e4);e.on("connection",(r=>{e.close(),clearTimeout(i),t(r)}))}));return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:r}};var i,o=(i=r(808))&&i.__esModule?i:{default:i},n=r(361);async function s(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}},833:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(568);Object.keys(i).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===i[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return i[e]}}))}))},961:(e,t)=>{"use strict";async function r(e,t){if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise(((r,i)=>{const o=()=>{const i=e.read(t);i&&(s(),r(i))},n=()=>{s(),i(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",o),e.removeListener("end",n)};e.on("readable",o),e.on("end",n)}))}Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=r,t.readLine=async function(e){return o(e,i)},t.readUntil=o;const i="\n".charCodeAt(0);async function o(e,t){const i=[];let o=0;for(;;){const n=await r(e,1);if(!n)throw new Error("end of stream");if(n[0]===t)break;i[o++]=n[0]}return Buffer.from(i).toString()}},567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=o(t);if(r&&r.has(e))return r.get(e);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=n?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=e[s]}i.default=e,r&&r.set(e,i);return i}(r(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(o=function(e){return e?r:t})(e)}const{deviceManager:n}=i.default;class SettingsMixinDeviceBase extends i.MixinDeviceBase{constructor(e,t,r){super(e,r.mixinDeviceInterfaces,t,r.providerNativeId,r.mixinStorageSuffix),this.settingsGroup=r.group,this.settingsGroupKey=r.groupKey,process.nextTick((()=>n.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)))}async getSettings(){const e=this.mixinDeviceInterfaces.includes(i.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,t=this.getMixinSettings(),r=[];try{const t=await e||[];r.push(...t)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=n.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return r}async putSetting(e,t){const r=this.settingsGroupKey+":";if(null==e||!e.startsWith(r))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(r.length),t),n.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)}release(){n.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.createDgramParser=a,t.createFragmentedMp4Parser=function(e){return{container:"mp4",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=c(e);let r,i,o;for await(const e of t)r?i||(i=e):r=e,yield{startStream:o,chunks:[e.header,e.data],type:e.type},r&&i&&!o&&(o=Buffer.concat([r.header,r.data,i.header,i.data]))},findSyncFrame:n}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,(e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")})),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const o=r.chunks[i];let n=0;for(;n+188<o.length;){const r=o.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}}},t.createRawVideoParser=function(e){var t;const r=(null===(t=e)||void 0===t?void 0:t.pixelFormat)||d;let i;e=e||{};const{size:s,everyNFrames:a}=e;s&&(i=`scale=${s.width}:${s.height}`);a&&a>1&&(i?i+=",":i="",i+=`select=not(mod(n\\,${a}))`);return{container:"rawvideo",outputArguments:[...i?["-vf",i]:[],"-an","-vcodec","rawvideo","-pix_fmt",r.name,"-f","rawvideo"],async*parse(e,t,i){if(!t||!i)throw new Error("error parsing rawvideo, unknown width and height");t=(null==s?void 0:s.width)||t,i=(null==s?void 0:s.height)||i;const n=r.computeLength(t,i);for(;;){const r=await(0,o.readLength)(e,n);yield{chunks:[r],width:t,height:i}}},findSyncFrame:n}},t.createRtpParser=function(...e){return{container:"rtsp",inputArguments:["-v","verbose","-rtsp_transport","tcp"],outputArguments:[...e,"-f","rtp"],parseDatagram:a(),findSyncFrame:n}},t.parseFragmentedMP4=c;var i=r(361),o=r(961);function n(e){return e}function s(e,t){return async function*(r){let o=[],n=0;for(;;){const s=r.read();if(!s){await(0,i.once)(r,"readable");continue}if(o.push(s),n+=s.length,n<e)continue;const a=Buffer.concat(o);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);o=[u],n=u.length,yield{chunks:[d]}}}}function a(){return async function*(e,t,r,o){for(;;){const[t]=await(0,i.once)(e,"message");yield{chunks:[t],type:o}}}}async function*c(e){for(;;){const t=await(0,o.readLength)(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),n=await(0,o.readLength)(e,r);yield{header:t,length:r,type:i,data:n}}}const d={name:"yuv420p",computeLength:(e,t)=>e*t*1.5};t.PIXEL_FORMAT_YUV420P=d;t.PIXEL_FORMAT_RGB24={name:"rgb24",computeLength:(e,t)=>e*t*3}},168:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RtspServer=void 0,t.createRtspParser=function(){let e;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,o.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp","-vcodec","copy","-acodec","copy","-f","rtsp"],findSyncFrame:n,sdp:new Promise((t=>e=t)),async*parse(t,r,i){const o=new RtspServer(t);await o.handleSetup(),e(o.sdp);for await(const{type:e,rtcp:t,header:n,packet:s}of o.handleRecord())yield{chunks:[n,s],type:e,width:r,height:i}}}};var i=r(961),o=r(113);function n(e){return e}class RtspServer{constructor(e,t){this.duplex=e,this.sdp=t,this.session=(0,o.randomBytes)(4).toString("hex")}async handleSetup(){let e=[];for(;;){let t=await(0,i.readLine)(this.duplex);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await(0,i.readLength)(this.duplex,4),t=e.readUInt16BE(2),r=await(0,i.readLength)(this.duplex,t),o=e.readUInt8(1);yield{type:o<2?"video":"audio",rtcp:o%2==1,header:e,packet:r}}}send(e,t){const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(t,1),r.writeUInt16BE(e.length,2),this.duplex.write(r),this.duplex.write(Buffer.from(e))}sendVideo(e,t){this.send(e,t?1:0)}sendAudio(e,t){this.send(e,t?3:2)}options(e,t){const r={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,r)}describe(e,t){const r={};r["Content-Base"]=e,r["Content-Type"]="application/sdp",this.respond(200,"OK",t,r,Buffer.from(this.sdp))}setup(e,t){const r={};r.Transport=t.transport,r.Session=this.session,this.respond(200,"OK",t,r)}play(e,t){const r={};r["RTP-Info"]=`url=${e}/trackID=0;seq=0;rtptime=0,url=${e}/trackID=1;seq=0;rtptime=0`,r.Range="npt=now-",r.Session=this.session,this.respond(200,"OK",t,r)}async announce(e,t){const r=parseInt(t["content-length"]),o=await(0,i.readLength)(this.duplex,r);this.sdp=o.toString();const n={};n.Session=this.session,this.respond(200,"OK",t,n)}async record(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async headers(e){let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=function(e){const t={};for(const r of e.slice(1)){const e=r.indexOf(":");let i="";-1!==e&&(i=r.substring(e+1).trim()),t[r.substring(0,e).toLowerCase()]=i}return t}(e);if(this[t])return await this[t](r,i),"play"!==t&&"record"!==t;this.respond(400,"Bad Request",i,{})}respond(e,t,r,i,o){let n=`RTSP/1.0 ${e} ${t}\r\n`;r.cseq&&(i.CSeq=r.cseq),o&&(i["Content-Length"]=o.length.toString());for(const[e,t]of Object.entries(i))n+=`${e}: ${t}\r\n`;n+="\r\n",this.duplex.write(n),o&&this.duplex.write(o)}}t.RtspServer=RtspServer},510:(e,t,r)=>{"use strict";var i=Object.create?function(e,t,r,i){void 0===i&&(i=r),Object.defineProperty(e,i,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,i){void 0===i&&(i=r),e[i]=t[r]},o=function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||i(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,o(r(393),t);const n=r(393);class ScryptedDeviceBase extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=ScryptedDeviceBase;class MixinDeviceBase extends n.DeviceBase{constructor(e,t,r,i,o){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=i,this._mixinStorageSuffix=o,this._listeners=new Set,this._deviceState=r}get storage(){if(!this._storage){const e=this._mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=MixinDeviceBase,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var r of Object.values(n.ScryptedInterfaceProperty))Object.defineProperty(ScryptedDeviceBase.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(MixinDeviceBase.prototype,r,{set:t(r),get:e(r)})}();let s={};try{s=Object.assign(s,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=s},393:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ThermostatMode=t.TemperatureUnit=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterfaceDescriptors=t.ScryptedInterface=t.ScryptedDeviceType=t.SCRYPTED_MEDIA_SCHEME=t.MediaPlayerState=t.LockState=t.HumidityMode=t.FanMode=t.DeviceBase=void 0;let r;t.DeviceBase=class DeviceBase{},t.ScryptedInterfaceProperty=r,function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(r||(t.ScryptedInterfaceProperty=r={}));let i,o,n,s,a,c,d,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=i,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(i||(t.ScryptedDeviceType=i={})),t.HumidityMode=o,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(o||(t.HumidityMode=o={})),t.FanMode=n,function(e){e.Auto="Auto",e.Manual="Manual"}(n||(t.FanMode=n={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=c,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(c||(t.LockState=c={})),t.MediaPlayerState=d,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(d||(t.MediaPlayerState=d={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan"}(u||(t.ScryptedInterface=u={})),t.ScryptedMimeTypes=l,function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(l||(t.ScryptedMimeTypes=l={}));t.SCRYPTED_MEDIA_SCHEME="scryped-media://"},568:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,i){var o,n;function s(e){const o=n=>{const s=n.toString();for(const e of r)if(-1!==s.indexOf(e))return;if(!i&&(-1!==s.indexOf("frame=")||-1!==s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",o),void t.stderr.removeListener("data",o);e(s)};return o}null===(o=t.stdout)||void 0===o||o.on("data",s(e.log)),null===(n=t.stderr)||void 0===n||n.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))},t.safePrintFFmpegArguments=function(e,t){const r=[];for(const e of t)try{const t=new URL(e);r.push(`${t.protocol}[REDACTED]`)}catch(t){r.push(e)}e.log(r.join(" "))};const r=["decode_slice_header error","no frame!","non-existing PPS"]},81:e=>{"use strict";e.exports=require("child_process")},113:e=>{"use strict";e.exports=require("crypto")},891:e=>{"use strict";e.exports=require("dgram")},361:e=>{"use strict";e.exports=require("events")},808:e=>{"use strict";e.exports=require("net")}},t={};function r(i){var o=t[i];if(void 0!==o)return o.exports;var n=t[i]={exports:{}};return e[i](n,n.exports,r),n.exports}var i={};(()=>{"use strict";var e=i;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=l(t);if(r&&r.has(e))return r.get(e);var i={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var n in e)if("default"!==n&&Object.prototype.hasOwnProperty.call(e,n)){var s=o?Object.getOwnPropertyDescriptor(e,n):null;s&&(s.get||s.set)?Object.defineProperty(i,n,s):i[n]=e[n]}i.default=e,r&&r.set(e,i);return i}(r(510)),o=r(361),n=r(567),s=r(201),a=r(129),c=r(454),d=r(769),u=r(168);function l(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(l=function(e){return e?r:t})(e)}function p(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}const{mediaManager:m,log:h,systemManager:f,deviceManager:g}=t.default,v=1e4,y="prebufferDuration",S="sendKeyframe",b="Default",P="AAC or No Audio",M=`${P} (Copy)`,w="Compatible Audio",x="Other Audio",O=["aac","mp3","mp2","opus"],D="-fflags +genpts",I=[P,w,x],C=["mpegts","mp4","rtsp"];class PrebufferSession{constructor(e,t,r,i){p(this,"prebuffers",{mp4:[],mpegts:[],rtsp:[]}),p(this,"detectedIdrInterval",0),p(this,"prevIdr",0),p(this,"audioDisabled",!1),p(this,"activeClients",0),this.mixin=e,this.streamName=t,this.streamId=r,this.stopInactive=i,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";I.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(P),r=-1!==e.indexOf(w),i=-1!==e.indexOf(x);return{isUsingDefaultAudioConfig:!(t||r||i),aacAudio:t,compatibleAudio:r,reencodeAudio:i}}async getMixinSettings(){const e=[],t=this.parserSession;let r=0,i=0;for(const e of this.prebuffers.mp4){i=i||e.time;for(const t of e.chunk.chunks)r+=t.byteLength}const o=Date.now()-i,n=Math.round(r/o*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,c,d;(e.push({title:"Audio Codec Transcoding",group:s,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||b,choices:[b,M,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:s,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:D,choices:[D,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0},{title:"Rebroadcast Mode",group:s,description:"THIS FEATURE IS IN TESTING. DO NOT CHANGE THIS FROM MPEG-TS. The stream format to use when rebroadcasting.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||"MPEG-TS"}),t)?e.push({key:"detectedResolution",group:s,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==t||null===(a=t.inputVideoResolution)||void 0===a?void 0:a[0])||"unknown"} @ ${n||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:s,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==t||null===(c=t.inputVideoCodec)||void 0===c?void 0:c.toString())||"unknown")+"/"+((null==t||null===(d=t.inputAudioCodec)||void 0===d?void 0:d.toString())||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:s,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}):e.push({title:"Status",group:s,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}async startPrebufferSession(){var e,r,i,o,n,c,d;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const l=parseInt(this.storage.getItem(y))||v;let p;try{p=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const f=null===(null===(e=p)||void 0===e?void 0:e.audio),g=null===(r=p)||void 0===r||null===(i=r.audio)||void 0===i?void 0:i.codec,{isUsingDefaultAudioConfig:S,aacAudio:b,compatibleAudio:P,reencodeAudio:M}=this.getAudioConfig();let w=this.storage.getItem("lastDetectedAudioCodec")||void 0;"null"===w&&(w=null);let x=!1;f||g||!S||void 0!==w||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),x=!0),!f&&g&&void 0!==w&&w!==g&&this.console.warn("Audio codec plugin reported vs detected mismatch",g,w);const I=void 0===w?null==g?void 0:g.toLowerCase():null===(o=w)||void 0===o?void 0:o.toLowerCase(),A=!O.includes(I);x||!1===(null===(n=p)||void 0===n?void 0:n.userConfigurable)||f||A&&(S&&h.a(`${this.mixin.name} is using the ${I} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",I));const k=await this.mixinDevice.getVideoStream(p),T=await m.convertMediaObjectToBuffer(k,t.ScryptedMimeTypes.FFmpegInput),_=JSON.parse(T.toString()),E=["-bsf:a","aac_adtstoasc"],R=[];let L;this.audioDisabled=!1;const B=null===w;let j=!1;var H;!x&&S&&A&&(!1===(null===(H=p)||void 0===H?void 0:H.userConfigurable)?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",I):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),j=!0);if(f||x)L=["-an"],this.audioDisabled=!0;else if(M||j)L=["-bsf:a","aac_adtstoasc","-acodec","libfdk_aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(b||B)L=["-acodec","copy"],L.push(...E);else if(P)L=["-acodec","copy"],L.push(...R);else{L=["-acodec","copy"];const e="aac"===I?E:R;L.push(...e)}const F=["-vcodec","copy"],V={console:this.console,timeout:6e4,parsers:{mp4:(0,a.createFragmentedMp4Parser)({vcodec:F,acodec:L})}};if("RTSP"===this.storage.getItem(this.rebroadcastModeKey)){const e=(0,u.createRtspParser)();this.sdp=e.sdp,V.parsers.rtsp=e}else V.parsers.mpegts=(0,a.createMpegTsParser)({vcodec:F,acodec:L});this.parsers=V.parsers;const N=this.storage.getItem(this.ffmpegInputArgumentsKey)||D;_.inputArguments.unshift(...N.split(" ")),this.storage.removeItem("lastDetectedAudioCodec");const U=await(0,s.startParserSession)(_,V);if(U.inputAudioCodec?O.includes(null===(c=U.inputAudioCodec)||void 0===c?void 0:c.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem("lastDetectedAudioCodec",U.inputAudioCodec||"null"),"h264"!==U.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),x)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),U.kill(),this.startPrebufferSession();if(this.parserSession=U,null!==(d=_.mediaStreamOptions)&&void 0!==d&&d.refreshAt){let e,r=_.mediaStreamOptions;const i=async()=>{if(!U.isActive)return;const e=await this.mixinDevice.getVideoStream(r),i=await m.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());r=n.mediaStreamOptions,o(n)},o=t=>{const r=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),e=setTimeout(i,r)};o(_),U.once("killed",(()=>clearTimeout(e)))}U.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===U&&(this.parserSession=void 0)}));for(const e of C){let t=0;U.on(e,(r=>{const i=this.prebuffers[e],o=Date.now();for("mdat"===r.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),i.push({time:o,chunk:r});i.length&&i[0].time<o-l;)i.shift(),t++;t>1e3&&(this.prebuffers[e]=i.slice(),t=0)}))}return U}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e){this.printActiveClients(),this.stopInactive&&(this.activeClients||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.activeClients||(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),e.kill())}),3e4)))}async getVideoStream(e){var t,r;this.ensurePrebufferSession();const i=await this.parserSessionPromise,o="false"!==this.storage.getItem(S),n=(null==e?void 0:e.prebuffer)||(o?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);this.console.log(this.streamName,"prebuffer request started");const a="RTSP"===this.storage.getItem(this.rebroadcastModeKey)?"rtsp":"mpegts",c=this.parsers[null==e?void 0:e.container]?null==e?void 0:e.container:a,l=Object.assign({},i.mediaStreamOptions);l.prebuffer=n;const{reencodeAudio:p}=this.getAudioConfig();this.audioDisabled?l.audio=null:l.audio=p?{codec:"aac",encoder:"libfdk_aac",profile:"aac_low"}:{codec:null==i?void 0:i.inputAudioCodec},l.video&&null!==(t=i.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(r=i.inputVideoResolution)&&void 0!==r&&r[3]&&Object.assign(l.video,{width:parseInt(i.inputVideoResolution[2]),height:parseInt(i.inputVideoResolution[3])});const h=Date.now();let f=0;const g=this.prebuffers[c];for(const e of g)if(!(e.time<h-n))for(const t of e.chunk.chunks)f+=t.length;const v=Math.max(5e5,f).toString(),y=await(async e=>{const t=this.prebuffers[e];let r,o;if("rtsp"===e){this.sdp.then((e=>console.log(e)));const e=await(0,d.listenZeroSingleClient)();r=e.clientPromise.then((async e=>{let t=await this.sdp;const r=new u.RtspServer(e,t);return await r.handlePlayback(),e})),o=e.url.replace("tcp://","rtsp://")}else{const e=await(0,d.listenZeroSingleClient)();r=e.clientPromise,o=`tcp://127.0.0.1:${e.port}`}return(0,s.handleRebroadcasterClient)(r,{console:this.console,connect:(r,o)=>{this.activeClients++,this.printActiveClients();const s=Date.now(),a=e=>{r(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{o(),this.console.log(this.streamName,"prebuffer request ended"),i.removeListener(e,a),i.removeListener("killed",c)};i.on(e,a),i.once("killed",c);for(const e of t)e.time<s-n||a(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(i),c()}}}),o})(c),b={url:y,container:c,inputArguments:["-analyzeduration","0","-probesize",v,...this.parsers[c].inputArguments||[],"-f",this.parsers[c].container,"-i",y],mediaStreamOptions:l};return m.createFFmpegMediaObject(b)}}class PrebufferMixin extends n.SettingsMixinDeviceBase{constructor(e,t,r,i){super(e,r,{providerNativeId:i,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),p(this,"released",!1),p(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=null==e?void 0:e.id;let r=this.sessions.get(t);return!r||null!=e&&e.directMediaStream?this.mixinDevice.getVideoStream(e):(r.ensurePrebufferSession(),await r.parserSessionPromise,r=this.sessions.get(t),r?r.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),r=this.getEnabledMediaStreamOptions(e),i=r?r.map((e=>e.id)):[void 0],n=(null==e?void 0:e.map((e=>e.id)))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){(null==e?void 0:e.find((e=>"cloud"===e.source)))&&(this.storage.setItem("warnedCloud","true"),h.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const s=this.mixinDeviceInterfaces.includes(t.ScryptedInterface.Battery);let a=0;const c=n.length;for(const t of n){let r=this.sessions.get(t);if(!r){var d;const n=null==e?void 0:e.find((e=>e.id===t));null!=n&&n.prebuffer&&h.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const u=null==n?void 0:n.name,l=!i.includes(t);if(r=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,r),t===(null==e||null===(d=e[0])||void 0===d?void 0:d.id)&&this.sessions.set(void 0,r),s){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(l){this.console.log("stream",u,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(t)===r&&!this.released;){r.ensurePrebufferSession();try{const e=await r.parserSessionPromise;a++,this.online=a==c,await(0,o.once)(e,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{a--,this.online=a==c}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}g.onMixinEvent(this.id,this.mixinProviderNativeId,t.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),r=this.getEnabledMediaStreamOptions(t);(null==t?void 0:t.length)>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:r.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:y,value:this.storage.getItem(y)||v.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:S,value:("false"!==this.storage.getItem(S)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const r=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of r.values()){var i;null==e||null===(i=e.parserSessionPromise)||void 0===i||i.then((e=>e.kill()))}this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const r=parseInt(this.storage.getItem(y))||v;if(t)for(const e of t)e.prebuffer=r;else e.push({id:"default",name:"Default",prebuffer:r});return e}release(){this.console.log("prebuffer releasing if started"),this.released=!0;for(const t of this.sessions.values()){var e;t&&(t.clearPrebuffers(),null===(e=t.parserSessionPromise)||void 0===e||e.then((e=>{this.console.log("prebuffer released"),e.kill(),t.clearPrebuffers()})))}}}class PrebufferProvider extends c.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(f.getSystemState())){var t;const r=f.getDeviceById(e);null!==(t=r.mixins)&&void 0!==t&&t.includes(this.id)&&r.getVideoStreamOptions()}const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout((()=>g.requestRestart()),r)}async canMixin(e,r){return r.includes(t.ScryptedInterface.VideoCamera)?[t.ScryptedInterface.VideoCamera,t.ScryptedInterface.Settings,t.ScryptedInterface.Online]:null}async getMixin(e,t,r){return this.setHasEnabledMixin(r.id),new PrebufferMixin(e,t,r,this.nativeId)}async releaseMixin(e,t){t.online=!0,t.release()}}var A=new PrebufferProvider;e.default=A})();var o=exports="undefined"==typeof exports?{}:exports;for(var n in i)o[n]=i[n];i.__esModule&&Object.defineProperty(o,"__esModule",{value:!0})})();
|
|
1
|
+
(()=>{var e={454:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=n(t);if(i&&i.has(e))return i.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(n=function(e){return e?i:t})(e)}const{systemManager:o}=r.default,s="v4";class AutoenableMixinProvider extends r.ScryptedDeviceBase{constructor(e){var t,i,n;super(e),n={},(i="hasEnabledMixin")in(t=this)?Object.defineProperty(t,i,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[i]=n;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=o.getComponent("plugins"),o.listen((async(e,t,i)=>{t.eventInterface!==r.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)}));for(const e of Object.keys(o.getSystemState())){const t=o.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id]===s)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const i=(e.mixins||[]).slice();i.push(this.id);const r=await this.pluginsComponent;await r.setMixins(e.id,i),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==s&&(this.hasEnabledMixin[e]=s,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=AutoenableMixinProvider},201:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createParserRebroadcaster=async function(e,t,i){return await g(Object.assign({},i,{connect:(i,r)=>(e.on(t,i),()=>{r()})}))},t.createRebroadcaster=g,t.handleRebroadcasterClient=v,t.parseAudioCodec=f,t.parseResolution=p,t.parseVideoCodec=h,t.startParserSession=async function(e,t){const{console:i}=t;let r,a=!0;const u=new s.EventEmitter;let m,g,v,y,S;u.on("error",(e=>i.error("rebroadcast error",e)));const b=new Promise(((e,t)=>{y=e,S=t}));function P(){var e;a&&(u.emit("killed"),u.emit("error",new Error("killed"))),a=!1,null==D||D.kill(),null==D||D.kill("SIGKILL"),null===(e=S)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r)}const M=e.inputArguments.slice();r=setTimeout(P,3e4);const w=e=>{let r;function n(){i.error("timeout waiting for data, killing parser session",e),P()}function o(){t.timeout&&(clearTimeout(r),r=setTimeout(n,t.timeout))}return u.once("killed",(()=>clearTimeout(r))),o(),{resetActivityTimer:o}};let O=!1;const x=["pipe","pipe","pipe"];let I=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){O=!0;const t=d.default.createSocket("udp4"),i=await(0,o.bindZero)(t),n=d.default.createSocket("udp4");await(0,o.bind)(n,i.port+1),u.once("killed",(()=>{t.close(),n.close()})),M.push(...r.outputArguments,i.url.replace("udp://","rtp://"));const{resetActivityTimer:s}=w(e);(async()=>{for await(const a of r.parseDatagram(t,parseInt(null===(i=v)||void 0===i?void 0:i[2]),parseInt(null===(n=v)||void 0===n?void 0:n[3]))){var i,n,o;null===(o=y)||void 0===o||o(void 0),u.emit(e,a),s()}})(),(async()=>{for await(const a of r.parseDatagram(n,parseInt(null===(t=v)||void 0===t?void 0:t[2]),parseInt(null===(i=v)||void 0===i?void 0:i[3]),"rtcp")){var t,i,o;null===(o=y)||void 0===o||o(void 0),u.emit(e,a),s()}})()}else if(r.tcpProtocol){const t=await(0,o.listenZeroSingleClient)(),n=new URL(r.tcpProtocol);n.port=t.port.toString(),M.push(...r.outputArguments,n.toString());const{resetActivityTimer:s}=w(e);(async()=>{const n=await t.clientPromise;try{for await(const t of r.parse(n,parseInt(null===(o=v)||void 0===o?void 0:o[2]),parseInt(null===(a=v)||void 0===a?void 0:a[3]))){var o,a,c;null===(c=y)||void 0===c||c(void 0),u.emit(e,t),s()}}catch(e){i.error("rebroadcast parse error",e),P()}})()}else M.push(...r.outputArguments,"pipe:"+I++),x.push("pipe")}O&&(M.push("-sdp_file","pipe:"+I++),x.push("pipe"));M.unshift("-hide_banner"),(0,c.safePrintFFmpegArguments)(i,M);const D=n.default.spawn(await l.getFFmpegPath(),M,{stdio:x});let C;(0,c.ffmpegLogInitialOutput)(i,D,void 0,null==t?void 0:t.storage),D.on("exit",P),C=O?new Promise((e=>{const t=[];D.stdio[I-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let A=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const n=D.stdio[3+A];A++;try{const{resetActivityTimer:t}=w(e);for await(const i of r.parse(n,parseInt(null===(o=v)||void 0===o?void 0:o[2]),parseInt(null===(s=v)||void 0===s?void 0:s[3]))){var o,s,a;null===(a=y)||void 0===a||a(void 0),u.emit(e,i),t()}}catch(e){i.error("rebroadcast parse error",e),P()}})),f(D).then((e=>m=e)),h(D).then((e=>g=e)),p(D).then((e=>v=e)),await b,y=void 0,S=void 0,clearTimeout(r),{sdp:C,inputAudioCodec:m,inputVideoCodec:g,inputVideoResolution:v,isActive:()=>a,kill:P,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},on(e,t){return u.on(e,t),this},once(e,t){return u.once(e,t),this},removeListener(e,t){return u.removeListener(e,t),this}}};var r=i(808),n=u(i(81)),o=i(769),s=i(361),a=u(i(510)),c=i(833),d=u(i(891));function u(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:l}=a.default;async function p(e){return new Promise((t=>{const i=r=>{const n=r.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);o&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(o))};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function m(e,t){return new Promise((i=>{const r=n=>{const o=n.toString(),s=o.indexOf(`${t}: `);if(-1!==s){const n=o.substring(s+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(n.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function h(e){return m(e,"Video")}async function f(e){return m(e,"Audio")}async function g(e){let t,i=0;const n=()=>{null!=e&&e.idle&&(clearTimeout(t),t=setTimeout((()=>{0!==i?n():e.idle.callback()}),e.idle.timeout))};n();const s=(0,r.createServer)((t=>{v(t,e),t.once("close",(()=>{n(),i--})),n(),i++})),a=await(0,o.listenZero)(s);return{server:s,port:a,url:`tcp://127.0.0.1:${a}`,get clients(){return i}}}async function v(e,t){const i=await e;let r=!0;const n=()=>{i.removeAllListeners(),i.destroy();const e=o;o=void 0,null==e||e()};let o=null==t?void 0:t.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),n);i.once("close",(()=>{n()})),i.on("error",(e=>{var i;return null==t||null===(i=t.console)||void 0===i?void 0:i.log("client stream ended")}))}},769:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bind=async function(e,t){return e.bind(t),await(0,n.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}},t.bindUdp=c,t.bindZero=async function(e){return c(e,0)},t.createBindUdp=d,t.createBindZero=async function(){return d(0)},t.listenZero=a,t.listenZeroSingleClient=async function(){const e=new r.default.Server,t=await a(e),i=new Promise(((t,i)=>{const r=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(r),t(i)}))}));return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:i}};var r=s(i(808)),n=i(361),o=s(i(891));function s(e){return e&&e.__esModule?e:{default:e}}async function a(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}async function c(e,t){e.bind(t),await(0,n.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function d(e){const t=o.default.createSocket("udp4"),{port:i,url:r}=await c(t,e);return{server:t,port:i,url:r}}},833:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=i(568);Object.keys(r).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===r[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}}))}))},961:(e,t)=>{"use strict";async function i(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const n=()=>{const r=e.read(t);r&&(s(),i(r))},o=()=>{s(),r(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",n),e.removeListener("end",o)};e.on("readable",n),e.on("end",o)}))}Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=i,t.readLine=async function(e){return n(e,r)},t.readUntil=n;const r="\n".charCodeAt(0);async function n(e,t){const r=[];let n=0;for(;;){const o=await i(e,1);if(!o)throw new Error("end of stream");if(o[0]===t)break;r[n++]=o[0]}return Buffer.from(r).toString()}},175:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RtspServer=void 0,t.createRtspParser=function(){let e;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,n.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp","-vcodec","copy","-acodec","copy","-f","rtsp"],findSyncFrame:s,sdp:new Promise((t=>e=t)),async*parse(t,i,r){const n=new RtspServer(t);await n.handleSetup(),e(n.sdp);for await(const{type:e,rtcp:t,header:o,packet:s}of n.handleRecord())yield{chunks:[o,s],type:e,width:i,height:r}}}};var r=i(961),n=i(113);function o(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function s(e){return e}class RtspServer{constructor(e,t,i){o(this,"videoChannel",0),o(this,"audioChannel",2),o(this,"udpPorts",{video:0,audio:0}),this.client=e,this.sdp=t,this.udp=i,this.session=(0,n.randomBytes)(4).toString("hex")}async handleSetup(){let e=[];for(;;){let i=await(0,r.readLine)(this.client);if(i=i.trim(),i)e.push(i);else{var t;if(null===(t=this.console)||void 0===t||t.log(e.join("\n")),!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await(0,r.readLength)(this.client,4),t=e.readUInt16BE(2),i=await(0,r.readLength)(this.client,t),n=e.readUInt8(1);yield{type:n-n%2===this.videoChannel?"video":"audio",rtcp:n%2==1,header:e,packet:i}}}send(e,t){const i=Buffer.alloc(4);i.writeUInt8(36,0),i.writeUInt8(t,1),i.writeUInt16BE(e.length,2),this.client.write(i),this.client.write(Buffer.from(e))}sendUdp(e,t,i){this.udp.send(t,i?e+1:e,"127.0.0.1")}sendVideo(e,t){this.udp?this.sendUdp(this.udpPorts.video,e,t):this.send(e,t?this.videoChannel+1:this.videoChannel)}sendAudio(e,t){this.udp?this.sendUdp(this.udpPorts.audio,e,t):this.send(e,t?this.audioChannel+1:this.audioChannel)}options(e,t){const i={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,i)}describe(e,t){const i={};i["Content-Base"]=e,i["Content-Type"]="application/sdp",this.respond(200,"OK",t,i,Buffer.from(this.sdp))}setup(e,t){const i={},r=t.transport;if(i.Transport=t.transport,i.Session=this.session,r.includes("UDP")){const t=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[i,n,o]=t;e.includes("audio")?this.udpPorts.audio=parseInt(n):this.udpPorts.video=parseInt(n)}this.respond(200,"OK",t,i)}play(e,t){const i={};i["RTP-Info"]=`url=${e}/trackID=0;seq=0;rtptime=0,url=${e}/trackID=1;seq=0;rtptime=0`,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),n=await(0,r.readLength)(this.client,i);this.sdp=n.toString();const o={};o.Session=this.session,this.respond(200,"OK",t,o)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=function(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim()),t[i.substring(0,e).toLowerCase()]=r}return t}(e);if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,n){let o=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),n&&(r["Content-Length"]=n.length.toString());for(const[e,t]of Object.entries(r))o+=`${e}: ${t}\r\n`;o+="\r\n",this.client.write(o),n&&this.client.write(n)}}t.RtspServer=RtspServer},567:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=n(t);if(i&&i.has(e))return i.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(n=function(e){return e?i:t})(e)}const{deviceManager:o}=r.default;class SettingsMixinDeviceBase extends r.MixinDeviceBase{constructor(e,t,i){super(e,i.mixinDeviceInterfaces,t,i.providerNativeId,i.mixinStorageSuffix),this.settingsGroup=i.group,this.settingsGroupKey=i.groupKey,process.nextTick((()=>o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)))}async getSettings(){const e=this.mixinDeviceInterfaces.includes(r.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,t=this.getMixinSettings(),i=[];try{const t=await e||[];i.push(...t)}catch(e){const t=this.name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=o.getDeviceState(this.mixinProviderNativeId).name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}return i}async putSetting(e,t){const i=this.settingsGroupKey+":";if(null==e||!e.startsWith(i))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(i.length),t),o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.createDgramParser=a,t.createFragmentedMp4Parser=function(e){return{container:"mp4",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=c(e);let i,r,n;for await(const e of t)i?r||(r=e):i=e,yield{startStream:n,chunks:[e.header,e.data],type:e.type},i&&r&&!n&&(n=Buffer.concat([i.header,i.data,r.header,r.data]))},findSyncFrame:o}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,(e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")})),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const n=i.chunks[r];let o=0;for(;o+188<n.length;){const i=n.subarray(o,o+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);o+=188}}}return e}}},t.createRawVideoParser=function(e){var t;const i=(null===(t=e)||void 0===t?void 0:t.pixelFormat)||d;let r;e=e||{};const{size:s,everyNFrames:a}=e;s&&(r=`scale=${s.width}:${s.height}`);a&&a>1&&(r?r+=",":r="",r+=`select=not(mod(n\\,${a}))`);return{container:"rawvideo",outputArguments:[...r?["-vf",r]:[],"-an","-vcodec","rawvideo","-pix_fmt",i.name,"-f","rawvideo"],async*parse(e,t,r){if(!t||!r)throw new Error("error parsing rawvideo, unknown width and height");t=(null==s?void 0:s.width)||t,r=(null==s?void 0:s.height)||r;const o=i.computeLength(t,r);for(;;){const i=await(0,n.readLength)(e,o);yield{chunks:[i],width:t,height:r}}},findSyncFrame:o}},t.createRtpParser=function(...e){return{container:"rtsp",inputArguments:["-v","verbose","-rtsp_transport","tcp"],outputArguments:[...e,"-f","rtp"],parseDatagram:a(),findSyncFrame:o}},t.parseFragmentedMP4=c;var r=i(361),n=i(961);function o(e){return e}function s(e,t){return async function*(i){let n=[],o=0;for(;;){const s=i.read();if(!s){await(0,r.once)(i,"readable");continue}if(n.push(s),o+=s.length,o<e)continue;const a=Buffer.concat(n);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);n=[u],o=u.length,yield{chunks:[d]}}}}function a(){return async function*(e,t,i,n){for(;;){const[t]=await(0,r.once)(e,"message");yield{chunks:[t],type:n}}}}async function*c(e){for(;;){const t=await(0,n.readLength)(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),o=await(0,n.readLength)(e,i);yield{header:t,length:i,type:r,data:o}}}const d={name:"yuv420p",computeLength:(e,t)=>e*t*1.5};t.PIXEL_FORMAT_YUV420P=d;t.PIXEL_FORMAT_RGB24={name:"rgb24",computeLength:(e,t)=>e*t*3}},510:(e,t,i)=>{"use strict";var r=Object.create?function(e,t,i,r){void 0===r&&(r=i),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,r){void 0===r&&(r=i),e[r]=t[i]},n=function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(i(393),t);const o=i(393);class ScryptedDeviceBase extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=ScryptedDeviceBase;class MixinDeviceBase extends o.DeviceBase{constructor(e,t,i,r,n){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=r,this._mixinStorageSuffix=n,this._listeners=new Set,this._deviceState=i}get storage(){if(!this._storage){const e=this._mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=MixinDeviceBase,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var i of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(ScryptedDeviceBase.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(MixinDeviceBase.prototype,i,{set:t(i),get:e(i)})}();let s={};try{s=Object.assign(s,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=s},393:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ThermostatMode=t.TemperatureUnit=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterfaceDescriptors=t.ScryptedInterface=t.ScryptedDeviceType=t.MediaPlayerState=t.LockState=t.HumidityMode=t.FanMode=t.DeviceBase=void 0;let i;t.DeviceBase=class DeviceBase{},t.ScryptedInterfaceProperty=i,function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(i||(t.ScryptedInterfaceProperty=i={}));let r,n,o,s,a,c,d,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=r,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(r||(t.ScryptedDeviceType=r={})),t.HumidityMode=n,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(n||(t.HumidityMode=n={})),t.FanMode=o,function(e){e.Auto="Auto",e.Manual="Manual"}(o||(t.FanMode=o={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=c,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(c||(t.LockState=c={})),t.MediaPlayerState=d,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(d||(t.MediaPlayerState=d={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan"}(u||(t.ScryptedInterface=u={})),t.ScryptedMimeTypes=l,function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer",e.RTCAVSignalingOfferSetup="x-scrypted/x-rtc-av-signalling-offer-setup",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(l||(t.ScryptedMimeTypes=l={}))},568:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,i,r){var s,a;const c=!!n.default.env.SCRYPTED_FFMPEG_NOISY||!(null==r||!r.getItem("SCRYPTED_FFMPEG_NOISY"));function d(e){const r=n=>{const s=n.toString();for(const e of o)if(-1!==s.indexOf(e))return;if(!(c||i||-1===s.indexOf("frame=")&&-1===s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(s)};return r}null===(s=t.stdout)||void 0===s||s.on("data",d(e.log)),null===(a=t.stderr)||void 0===a||a.on("data",d(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))},t.safePrintFFmpegArguments=function(e,t){const i=[];for(const e of t)try{const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}catch(t){i.push(e)}e.log(i.join(" "))};var r,n=(r=i(282))&&r.__esModule?r:{default:r};const o=["decode_slice_header error","no frame!","non-existing PPS"]},81:e=>{"use strict";e.exports=require("child_process")},113:e=>{"use strict";e.exports=require("crypto")},891:e=>{"use strict";e.exports=require("dgram")},361:e=>{"use strict";e.exports=require("events")},808:e=>{"use strict";e.exports=require("net")},282:e=>{"use strict";e.exports=require("process")}},t={};function i(r){var n=t[r];if(void 0!==n)return n.exports;var o=t[r]={exports:{}};return e[r](o,o.exports,i),o.exports}var r={};(()=>{"use strict";var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=l(t);if(i&&i.has(e))return i.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var s=n?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(r,o,s):r[o]=e[o]}r.default=e,i&&i.set(e,r);return r}(i(510)),n=i(361),o=i(567),s=i(201),a=i(129),c=i(454),d=i(769),u=i(175);function l(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(l=function(e){return e?i:t})(e)}function p(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const{mediaManager:m,log:h,systemManager:f,deviceManager:g}=t.default,v=1e4,y="prebufferDuration",S="sendKeyframe",b="Default",P="AAC or No Audio",M=`${P} (Copy)`,w="Compatible Audio",O="Other Audio",x=["aac","mp3","mp2","opus"],I="-fflags +genpts",D=[P,w,O],C=["mpegts","mp4","rtsp"];class PrebufferSession{constructor(e,t,i,r){p(this,"prebuffers",{mp4:[],mpegts:[],rtsp:[]}),p(this,"detectedIdrInterval",0),p(this,"prevIdr",0),p(this,"audioDisabled",!1),p(this,"activeClients",0),this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";D.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(P),i=-1!==e.indexOf(w),r=-1!==e.indexOf(O);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;for(const e of this.prebuffers.mp4){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const n=Date.now()-r,o=Math.round(i/n*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,c,d;(e.push({title:"Audio Codec Transcoding",group:s,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||b,choices:[b,M,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:s,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:I,choices:[I,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0},{title:"Rebroadcast Mode",group:s,description:"THIS FEATURE IS IN TESTING. DO NOT CHANGE THIS FROM MPEG-TS. The stream format to use when rebroadcasting.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||"MPEG-TS"}),t)?e.push({key:"detectedResolution",group:s,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==t||null===(a=t.inputVideoResolution)||void 0===a?void 0:a[0])||"unknown"} @ ${o||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:s,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==t||null===(c=t.inputVideoCodec)||void 0===c?void 0:c.toString())||"unknown")+"/"+((null==t||null===(d=t.inputAudioCodec)||void 0===d?void 0:d.toString())||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:s,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}):e.push({title:"Status",group:s,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}async startPrebufferSession(){var e,i,r,n,o,c,d;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const l=parseInt(this.storage.getItem(y))||v;let p;try{p=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const f=null===(null===(e=p)||void 0===e?void 0:e.audio),g=null===(i=p)||void 0===i||null===(r=i.audio)||void 0===r?void 0:r.codec,{isUsingDefaultAudioConfig:S,aacAudio:b,compatibleAudio:P,reencodeAudio:M}=this.getAudioConfig();let w=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===w&&(w=null);let O=!1;f||g||!S||void 0!==w||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),O=!0),!f&&g&&void 0!==w&&w!==g&&this.console.warn("Audio codec plugin reported vs detected mismatch",g,w);const D=void 0===w?null==g?void 0:g.toLowerCase():null===(n=w)||void 0===n?void 0:n.toLowerCase(),A=!x.includes(D);O||!1===(null===(o=p)||void 0===o?void 0:o.userConfigurable)||f||A&&(S&&h.a(`${this.mixin.name} is using the ${D} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",D));const k=await this.mixinDevice.getVideoStream(p),T=await m.convertMediaObjectToBuffer(k,t.ScryptedMimeTypes.FFmpegInput),_=JSON.parse(T.toString()),E=["-bsf:a","aac_adtstoasc"],R=[];let L;this.audioDisabled=!1;const B=null===w;let j=!1;var F;!O&&S&&A&&(!1===(null===(F=p)||void 0===F?void 0:F.userConfigurable)?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",D):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),j=!0);if(f||O)L=["-an"],this.audioDisabled=!0;else if(M||j)L=["-bsf:a","aac_adtstoasc","-acodec","libfdk_aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(b||B)L=["-acodec","copy"],L.push(...E);else if(P)L=["-acodec","copy"],L.push(...R);else{L=["-acodec","copy"];const e="aac"===D?E:R;L.push(...e)}const H=["-vcodec","copy"],V={console:this.console,timeout:6e4,parsers:{mp4:(0,a.createFragmentedMp4Parser)({vcodec:H,acodec:L})}};if("RTSP"===this.storage.getItem(this.rebroadcastModeKey)){const e=(0,u.createRtspParser)();this.sdp=e.sdp,V.parsers.rtsp=e}else V.parsers.mpegts=(0,a.createMpegTsParser)({vcodec:H,acodec:L});this.parsers=V.parsers;const N=this.storage.getItem(this.ffmpegInputArgumentsKey)||I;_.inputArguments.unshift(...N.split(" ")),this.storage.removeItem(this.lastDetectedAudioCodecKey);const U=await(0,s.startParserSession)(_,V);if(U.inputAudioCodec?x.includes(null===(c=U.inputAudioCodec)||void 0===c?void 0:c.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,U.inputAudioCodec||"null"),"h264"!==U.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),O)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),U.kill(),this.startPrebufferSession();if(this.parserSession=U,null!==(d=_.mediaStreamOptions)&&void 0!==d&&d.refreshAt){let e,i=_.mediaStreamOptions;const r=async()=>{if(!U.isActive)return;const e=await this.mixinDevice.getVideoStream(i),r=await m.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(r.toString());i=o.mediaStreamOptions,n(o)},n=t=>{const i=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),e=setTimeout(r,i)};n(_),U.once("killed",(()=>clearTimeout(e)))}U.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===U&&(this.parserSession=void 0)}));for(const e of C){let t=0;U.on(e,(i=>{const r=this.prebuffers[e],n=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=n-this.prevIdr),this.prevIdr=n),r.push({time:n,chunk:i});r.length&&r[0].time<n-l;)r.shift(),t++;t>1e3&&(this.prebuffers[e]=r.slice(),t=0)}))}return U}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e,t){this.printActiveClients(),this.stopInactive&&(this.activeClients||this.inactivityTimeout&&!t||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.activeClients||(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),e.kill())}),3e4)))}async getVideoStream(e){var t,i;this.ensurePrebufferSession();const r=await this.parserSessionPromise,n="false"!==this.storage.getItem(S),o=(null==e?void 0:e.prebuffer)||(n?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);this.console.log(this.streamName,"prebuffer request started");const a="RTSP"===this.storage.getItem(this.rebroadcastModeKey)?"rtsp":"mpegts",c=this.parsers[null==e?void 0:e.container]?null==e?void 0:e.container:a,l=Object.assign({},r.mediaStreamOptions);l.prebuffer=o;const{reencodeAudio:p}=this.getAudioConfig();this.audioDisabled?l.audio=null:l.audio=p?{codec:"aac",encoder:"libfdk_aac",profile:"aac_low"}:{codec:null==r?void 0:r.inputAudioCodec},l.video&&null!==(t=r.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(i=r.inputVideoResolution)&&void 0!==i&&i[3]&&Object.assign(l.video,{width:parseInt(r.inputVideoResolution[2]),height:parseInt(r.inputVideoResolution[3])});const h=Date.now();let f=0;const g=this.prebuffers[c];for(const e of g)if(!(e.time<h-o))for(const t of e.chunk.chunks)f+=t.length;const v=Math.max(5e5,f).toString(),y=await(async t=>{const i=this.prebuffers[t];let n,a;if("rtsp"===t){this.sdp.then((e=>console.log(e)));const e=await(0,d.listenZeroSingleClient)();n=e.clientPromise.then((async e=>{let t=await this.sdp;const i=new u.RtspServer(e,t);return await i.handlePlayback(),e})),a=e.url.replace("tcp://","rtsp://")}else{const e=await(0,d.listenZeroSingleClient)();n=e.clientPromise,a=`tcp://127.0.0.1:${e.port}`}return(0,s.handleRebroadcasterClient)(n,{console:this.console,connect:(n,s)=>{this.activeClients++,this.printActiveClients();const a=Date.now(),c=e=>{n(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{s(),this.console.log(this.streamName,"prebuffer request ended"),r.removeListener(t,c),r.removeListener("killed",d)};r.on(t,c),r.once("killed",d);for(const e of i)e.time<a-o||c(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(r,!1!==(null==e?void 0:e.refresh)),d()}}}),a})(c),b={url:y,container:c,inputArguments:["-analyzeduration","0","-probesize",v,...this.parsers[c].inputArguments||[],"-f",this.parsers[c].container,"-i",y],mediaStreamOptions:l};return m.createFFmpegMediaObject(b)}}class PrebufferMixin extends o.SettingsMixinDeviceBase{constructor(e,t,i,r){super(e,i,{providerNativeId:r,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),p(this,"released",!1),p(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=null==e?void 0:e.id;let i=this.sessions.get(t);return!i||null!=e&&e.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(e),r=i?i.map((e=>e.id)):[void 0],o=(null==e?void 0:e.map((e=>e.id)))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){(null==e?void 0:e.find((e=>"cloud"===e.source)))&&(this.storage.setItem("warnedCloud","true"),h.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const s=this.mixinDeviceInterfaces.includes(t.ScryptedInterface.Battery);let a=0;const c=o.length;for(const t of o){let i=this.sessions.get(t);if(!i){var d;const o=null==e?void 0:e.find((e=>e.id===t));null!=o&&o.prebuffer&&h.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const u=null==o?void 0:o.name,l=!r.includes(t);if(i=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,i),t===(null==e||null===(d=e[0])||void 0===d?void 0:d.id)&&this.sessions.set(void 0,i),s){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(l){this.console.log("stream",u,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(t)===i&&!this.released;){i.ensurePrebufferSession();try{const e=await i.parserSessionPromise;a++,this.online=a==c,await(0,n.once)(e,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{a--,this.online=a==c}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}g.onMixinEvent(this.id,this.mixinProviderNativeId,t.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);(null==t?void 0:t.length)>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:i.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:y,value:this.storage.getItem(y)||v.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:S,value:("false"!==this.storage.getItem(S)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const i=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of i.values()){var r;null==e||null===(r=e.parserSessionPromise)||void 0===r||r.then((e=>e.kill()))}this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const i=parseInt(this.storage.getItem(y))||v;for(const n of e){var r;(null!==(r=this.sessions.get(n.id))&&void 0!==r&&r.parserSession||t.includes(n))&&(n.prebuffer=i)}return e}release(){this.console.log("prebuffer releasing if started"),this.released=!0;for(const t of this.sessions.values()){var e;t&&(t.clearPrebuffers(),null===(e=t.parserSessionPromise)||void 0===e||e.then((e=>{this.console.log("prebuffer released"),e.kill(),t.clearPrebuffers()})))}}}class PrebufferProvider extends c.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(f.getSystemState())){var t;const i=f.getDeviceById(e);null!==(t=i.mixins)&&void 0!==t&&t.includes(this.id)&&i.getVideoStreamOptions()}const i=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(i/1e3/60)} minutes`),setTimeout((()=>g.requestRestart()),i)}async canMixin(e,i){return i.includes(t.ScryptedInterface.VideoCamera)?[t.ScryptedInterface.VideoCamera,t.ScryptedInterface.Settings,t.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new PrebufferMixin(e,t,i,this.nativeId)}async releaseMixin(e,t){t.online=!0,t.release()}}var A=new PrebufferProvider;e.default=A})();var n=exports="undefined"==typeof exports?{}:exports;for(var o in r)n[o]=r[o];r.__esModule&&Object.defineProperty(n,"__esModule",{value:!0})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, Vide
|
|
|
3
3
|
import sdk from '@scrypted/sdk';
|
|
4
4
|
import { once } from 'events';
|
|
5
5
|
import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
|
|
6
|
-
import {
|
|
7
|
-
import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, StreamParser
|
|
6
|
+
import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
|
|
7
|
+
import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
|
8
8
|
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
|
|
9
9
|
import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
|
10
|
-
import { createRtspParser, RtspServer } from '
|
|
10
|
+
import { createRtspParser, RtspServer } from '../../../common/src/rtsp-server';
|
|
11
11
|
import { Duplex } from 'stream';
|
|
12
12
|
|
|
13
13
|
const { mediaManager, log, systemManager, deviceManager } = sdk;
|
|
@@ -15,9 +15,6 @@ const { mediaManager, log, systemManager, deviceManager } = sdk;
|
|
|
15
15
|
const defaultPrebufferDuration = 10000;
|
|
16
16
|
const PREBUFFER_DURATION_MS = 'prebufferDuration';
|
|
17
17
|
const SEND_KEYFRAME = 'sendKeyframe';
|
|
18
|
-
const AUDIO_CONFIGURATION_KEY_PREFIX = 'audioConfiguration-';
|
|
19
|
-
const FFMPEG_INPUT_ARGUMENTS_KEY_PREFIX = 'ffmpegInputArguments-';
|
|
20
|
-
const REBROADCAST_MODE_KEY_PREFIX = 'rebroadcastMode-';
|
|
21
18
|
const DEFAULT_AUDIO = 'Default';
|
|
22
19
|
const AAC_AUDIO = 'AAC or No Audio';
|
|
23
20
|
const AAC_AUDIO_DESCRIPTION = `${AAC_AUDIO} (Copy)`;
|
|
@@ -72,15 +69,17 @@ class PrebufferSession {
|
|
|
72
69
|
inactivityTimeout: NodeJS.Timeout;
|
|
73
70
|
audioConfigurationKey: string;
|
|
74
71
|
ffmpegInputArgumentsKey: string;
|
|
72
|
+
lastDetectedAudioCodecKey: string;
|
|
75
73
|
rebroadcastModeKey: string;
|
|
76
74
|
|
|
77
75
|
constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string, public stopInactive: boolean) {
|
|
78
76
|
this.storage = mixin.storage;
|
|
79
77
|
this.console = mixin.console;
|
|
80
78
|
this.mixinDevice = mixin.mixinDevice;
|
|
81
|
-
this.audioConfigurationKey =
|
|
82
|
-
this.ffmpegInputArgumentsKey =
|
|
83
|
-
this.rebroadcastModeKey =
|
|
79
|
+
this.audioConfigurationKey = 'audioConfiguration-' + this.streamId;
|
|
80
|
+
this.ffmpegInputArgumentsKey = 'ffmpegInputArguments-' + this.streamId;
|
|
81
|
+
this.rebroadcastModeKey = 'rebroadcastMode-' + this.streamId;
|
|
82
|
+
this.lastDetectedAudioCodecKey = 'lastDetectedAudioCodec-' + this.streamId;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
clearPrebuffers() {
|
|
@@ -246,7 +245,7 @@ class PrebufferSession {
|
|
|
246
245
|
|
|
247
246
|
const { isUsingDefaultAudioConfig, aacAudio, compatibleAudio, reencodeAudio } = this.getAudioConfig();
|
|
248
247
|
|
|
249
|
-
let detectedAudioCodec = this.storage.getItem(
|
|
248
|
+
let detectedAudioCodec = this.storage.getItem(this.lastDetectedAudioCodecKey) || undefined;
|
|
250
249
|
if (detectedAudioCodec === 'null')
|
|
251
250
|
detectedAudioCodec = null;
|
|
252
251
|
|
|
@@ -403,7 +402,7 @@ class PrebufferSession {
|
|
|
403
402
|
|
|
404
403
|
// before launching the parser session, clear out the last detected codec.
|
|
405
404
|
// an erroneous cached codec could cause ffmpeg to fail to start.
|
|
406
|
-
this.storage.removeItem(
|
|
405
|
+
this.storage.removeItem(this.lastDetectedAudioCodecKey);
|
|
407
406
|
|
|
408
407
|
const session = await startParserSession(ffmpegInput, rbo);
|
|
409
408
|
|
|
@@ -418,7 +417,7 @@ class PrebufferSession {
|
|
|
418
417
|
}
|
|
419
418
|
|
|
420
419
|
// set/update the detected codec, set it to null if no audio was found.
|
|
421
|
-
this.storage.setItem(
|
|
420
|
+
this.storage.setItem(this.lastDetectedAudioCodecKey, session.inputAudioCodec || 'null');
|
|
422
421
|
|
|
423
422
|
if (session.inputVideoCodec !== 'h264') {
|
|
424
423
|
this.console.error(`Video codec is not h264. If there are errors, try changing your camera's encoder output.`);
|
|
@@ -503,13 +502,19 @@ class PrebufferSession {
|
|
|
503
502
|
this.console.log(this.streamName, 'active rebroadcast clients:', this.activeClients);
|
|
504
503
|
}
|
|
505
504
|
|
|
506
|
-
inactivityCheck(session: ParserSession<PrebufferParsers
|
|
505
|
+
inactivityCheck(session: ParserSession<PrebufferParsers>, refresh: boolean) {
|
|
507
506
|
this.printActiveClients();
|
|
508
507
|
if (!this.stopInactive)
|
|
509
508
|
return;
|
|
510
509
|
if (this.activeClients)
|
|
511
510
|
return;
|
|
512
511
|
|
|
512
|
+
// by default, clients disconnecting will reset the inactivity timeout.
|
|
513
|
+
// but in some cases, like optimistic prebuffer stream snapshots (google sdm)
|
|
514
|
+
// we do not want that behavior.
|
|
515
|
+
if (this.inactivityTimeout && !refresh)
|
|
516
|
+
return;
|
|
517
|
+
|
|
513
518
|
clearTimeout(this.inactivityTimeout)
|
|
514
519
|
this.inactivityTimeout = setTimeout(() => {
|
|
515
520
|
if (this.activeClients)
|
|
@@ -519,7 +524,7 @@ class PrebufferSession {
|
|
|
519
524
|
}, 30000);
|
|
520
525
|
}
|
|
521
526
|
|
|
522
|
-
async getVideoStream(options?:
|
|
527
|
+
async getVideoStream(options?: RequestMediaStreamOptions): Promise<MediaObject> {
|
|
523
528
|
this.ensurePrebufferSession();
|
|
524
529
|
|
|
525
530
|
const session = await this.parserSessionPromise;
|
|
@@ -597,7 +602,7 @@ class PrebufferSession {
|
|
|
597
602
|
|
|
598
603
|
return () => {
|
|
599
604
|
this.activeClients--;
|
|
600
|
-
this.inactivityCheck(session);
|
|
605
|
+
this.inactivityCheck(session, options?.refresh !== false);
|
|
601
606
|
cleanup();
|
|
602
607
|
};
|
|
603
608
|
}
|
|
@@ -872,18 +877,11 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
|
|
872
877
|
|
|
873
878
|
const prebuffer = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
|
|
874
879
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
name: 'Default',
|
|
879
|
-
prebuffer,
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
for (const enabledStream of enabledStreams) {
|
|
884
|
-
enabledStream.prebuffer = prebuffer;
|
|
885
|
-
}
|
|
880
|
+
for (const mso of ret) {
|
|
881
|
+
if (this.sessions.get(mso.id)?.parserSession || enabledStreams.includes(mso))
|
|
882
|
+
mso.prebuffer = prebuffer;
|
|
886
883
|
}
|
|
884
|
+
|
|
887
885
|
return ret;
|
|
888
886
|
}
|
|
889
887
|
|
package/src/rtsp-server.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { readLength, readLine } from '../../../common/src/read-stream';
|
|
2
|
-
import { Duplex } from 'stream';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
4
|
-
import { StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
|
5
|
-
|
|
6
|
-
interface Headers {
|
|
7
|
-
[header: string]: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function findSyncFrame(streamChunks: StreamChunk[]): StreamChunk[] {
|
|
11
|
-
return streamChunks;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface RtspStreamParser extends StreamParser {
|
|
15
|
-
sdp: Promise<string>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function createRtspParser(): RtspStreamParser {
|
|
19
|
-
let resolve: any;
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
container: 'rtsp',
|
|
23
|
-
tcpProtocol: 'rtsp://127.0.0.1/' + randomBytes(8).toString('hex'),
|
|
24
|
-
inputArguments: [
|
|
25
|
-
'-rtsp_transport',
|
|
26
|
-
'tcp',
|
|
27
|
-
],
|
|
28
|
-
outputArguments: [
|
|
29
|
-
'-rtsp_transport',
|
|
30
|
-
'tcp',
|
|
31
|
-
'-vcodec', 'copy',
|
|
32
|
-
'-acodec', 'copy',
|
|
33
|
-
'-f', 'rtsp',
|
|
34
|
-
],
|
|
35
|
-
findSyncFrame,
|
|
36
|
-
sdp: new Promise<string>(r => resolve = r),
|
|
37
|
-
async *parse(duplex, width, height) {
|
|
38
|
-
const server = new RtspServer(duplex);
|
|
39
|
-
await server.handleSetup();
|
|
40
|
-
resolve(server.sdp);
|
|
41
|
-
for await (const { type, rtcp, header, packet } of server.handleRecord()) {
|
|
42
|
-
yield {
|
|
43
|
-
chunks: [header, packet],
|
|
44
|
-
type,
|
|
45
|
-
width,
|
|
46
|
-
height,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function parseHeaders(headers: string[]): Headers {
|
|
54
|
-
const ret = {};
|
|
55
|
-
for (const header of headers.slice(1)) {
|
|
56
|
-
const index = header.indexOf(':');
|
|
57
|
-
let value = '';
|
|
58
|
-
if (index !== -1)
|
|
59
|
-
value = header.substring(index + 1).trim();
|
|
60
|
-
const key = header.substring(0, index).toLowerCase();
|
|
61
|
-
ret[key] = value;
|
|
62
|
-
}
|
|
63
|
-
return ret;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class RtspServer {
|
|
67
|
-
session: string;
|
|
68
|
-
constructor(public duplex: Duplex, public sdp?: string) {
|
|
69
|
-
this.session = randomBytes(4).toString('hex');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async handleSetup() {
|
|
73
|
-
let currentHeaders: string[] = [];
|
|
74
|
-
while (true) {
|
|
75
|
-
let line = await readLine(this.duplex);
|
|
76
|
-
line = line.trim();
|
|
77
|
-
if (!line) {
|
|
78
|
-
if (!await this.headers(currentHeaders))
|
|
79
|
-
break;
|
|
80
|
-
currentHeaders = [];
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
currentHeaders.push(line);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async handlePlayback() {
|
|
89
|
-
return this.handleSetup();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async *handleRecord(): AsyncGenerator<{
|
|
93
|
-
type: 'audio' | 'video',
|
|
94
|
-
rtcp: boolean,
|
|
95
|
-
header: Buffer,
|
|
96
|
-
packet: Buffer,
|
|
97
|
-
}> {
|
|
98
|
-
while (true) {
|
|
99
|
-
const header = await readLength(this.duplex, 4);
|
|
100
|
-
const length = header.readUInt16BE(2);
|
|
101
|
-
const packet = await readLength(this.duplex, length);
|
|
102
|
-
const id = header.readUInt8(1);
|
|
103
|
-
yield {
|
|
104
|
-
type: id < 2 ? 'video' : 'audio',
|
|
105
|
-
rtcp: id % 2 === 1,
|
|
106
|
-
header,
|
|
107
|
-
packet,
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
send(rtp: Buffer, channel: number) {
|
|
113
|
-
const header = Buffer.alloc(4);
|
|
114
|
-
header.writeUInt8(36, 0);
|
|
115
|
-
header.writeUInt8(channel, 1);
|
|
116
|
-
header.writeUInt16BE(rtp.length, 2);
|
|
117
|
-
|
|
118
|
-
this.duplex.write(header);
|
|
119
|
-
this.duplex.write(Buffer.from(rtp));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
sendVideo(packet: Buffer, rtcp: boolean) {
|
|
123
|
-
this.send(packet, rtcp ? 1 : 0);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
sendAudio(packet: Buffer, rtcp: boolean) {
|
|
127
|
-
this.send(packet, rtcp ? 3 : 2);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
options(url: string, requestHeaders: Headers) {
|
|
131
|
-
const headers: Headers = {};
|
|
132
|
-
headers['Public'] = 'DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD';
|
|
133
|
-
|
|
134
|
-
this.respond(200, 'OK', requestHeaders, headers);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
describe(url: string, requestHeaders: Headers) {
|
|
138
|
-
const headers: Headers = {};
|
|
139
|
-
headers['Content-Base'] = url;
|
|
140
|
-
headers['Content-Type'] = 'application/sdp';
|
|
141
|
-
this.respond(200, 'OK', requestHeaders, headers, Buffer.from(this.sdp))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
setup(url: string, requestHeaders: Headers) {
|
|
145
|
-
const headers: Headers = {};
|
|
146
|
-
headers['Transport'] = requestHeaders['transport'];
|
|
147
|
-
headers['Session'] = this.session;
|
|
148
|
-
this.respond(200, 'OK', requestHeaders, headers)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
play(url: string, requestHeaders: Headers) {
|
|
152
|
-
const headers: Headers = {};
|
|
153
|
-
headers['RTP-Info'] = `url=${url}/trackID=0;seq=0;rtptime=0,url=${url}/trackID=1;seq=0;rtptime=0`;
|
|
154
|
-
headers['Range'] = 'npt=now-';
|
|
155
|
-
headers['Session'] = this.session;
|
|
156
|
-
this.respond(200, 'OK', requestHeaders, headers);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async announce(url: string, requestHeaders: Headers) {
|
|
160
|
-
const contentLength = parseInt(requestHeaders['content-length']);
|
|
161
|
-
const sdpBuffer = await readLength(this.duplex, contentLength);
|
|
162
|
-
this.sdp = sdpBuffer.toString();
|
|
163
|
-
const headers: Headers = {};
|
|
164
|
-
headers['Session'] = this.session;
|
|
165
|
-
|
|
166
|
-
this.respond(200, 'OK', requestHeaders, headers);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async record(url: string, requestHeaders: Headers) {
|
|
170
|
-
const headers: Headers = {};
|
|
171
|
-
headers['Session'] = this.session;
|
|
172
|
-
this.respond(200, 'OK', requestHeaders, headers);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async headers(headers: string[]) {
|
|
176
|
-
let [method, url] = headers[0].split(' ', 2);
|
|
177
|
-
method = method.toLowerCase();
|
|
178
|
-
const requestHeaders = parseHeaders(headers);
|
|
179
|
-
if (!this[method]) {
|
|
180
|
-
this.respond(400, 'Bad Request', requestHeaders, {});
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
await this[method](url, requestHeaders);
|
|
185
|
-
return method !== 'play' && method !== 'record';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
respond(code: number, message: string, requestHeaders: Headers, headers: Headers, buffer?: Buffer) {
|
|
189
|
-
let response = `RTSP/1.0 ${code} ${message}\r\n`;
|
|
190
|
-
if (requestHeaders['cseq'])
|
|
191
|
-
headers['CSeq'] = requestHeaders['cseq'];
|
|
192
|
-
if (buffer)
|
|
193
|
-
headers['Content-Length'] = buffer.length.toString();
|
|
194
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
195
|
-
response += `${key}: ${value}\r\n`;
|
|
196
|
-
}
|
|
197
|
-
response += '\r\n';
|
|
198
|
-
this.duplex.write(response);
|
|
199
|
-
if (buffer)
|
|
200
|
-
this.duplex.write(buffer);
|
|
201
|
-
}
|
|
202
|
-
}
|