@scrypted/prebuffer-mixin 0.1.140 → 0.1.144

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.
@@ -1,2 +1,2 @@
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=o(t);if(i&&i.has(e))return i.get(e);var r={},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(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{systemManager:n}=r.default,s="v4";class AutoenableMixinProvider extends r.ScryptedDeviceBase{constructor(e){var t,i,o;super(e),o={},(i="hasEnabledMixin")in(t=this)?Object.defineProperty(t,i,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[i]=o;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=n.getComponent("plugins"),n.listen((async(e,t,i)=>{t.eventInterface!==r.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 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.createRebroadcaster=async function(e){let t=0;const i=(0,r.createServer)((i=>{let r=!0;const o=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,null==e||e()};let n=null==e?void 0:e.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),o);i.once("close",(()=>{t--,o()})),i.on("error",(t=>{var i;return null===(i=e.console)||void 0===i?void 0:i.log("client stream ended")})),t++})),o=await(0,n.listenZero)(i);return{server:i,port:o,get clients(){return t}}},t.parseAudioCodec=f,t.parseResolution=l,t.parseVideoCodec=m,t.startParserSession=async function(e,t){const{console:i}=t;let r,n,a=!0;const c=new s.EventEmitter;let p,h,g,v,y;c.on("error",(e=>i.error("rebroadcast error",e)));const S=new Promise(((e,t)=>{v=e,y=t}));function b(){var e;a&&(c.emit("killed"),c.emit("error",new Error("killed"))),a=!1,null==O||O.kill(),null==O||O.kill("SIGKILL"),null===(e=y)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(n)}function M(){i.error("timeout waiting for data, killing parser session"),b()}function P(){t.timeout&&(clearTimeout(r),r=setTimeout(M,t.timeout))}P();const w=e.inputArguments.slice();n=setTimeout(b,3e4);const x=["pipe","pipe","pipe"];let D=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];w.push(...i.outputArguments,`pipe:${D}`),x.push("pipe"),D++}w.unshift("-hide_banner"),(0,d.safePrintFFmpegArguments)(i,w);const O=o.default.spawn(await u.getFFmpegPath(),w,{stdio:x});return(0,d.ffmpegLogInitialOutput)(i,O),O.on("exit",b),Object.keys(t.parsers).forEach((async(e,r)=>{const o=O.stdio[3+r],n=t.parsers[e];try{for await(const t of n.parse(o,parseInt(null===(s=g)||void 0===s?void 0:s[2]),parseInt(null===(a=g)||void 0===a?void 0:a[3]))){var s,a,d;null===(d=v)||void 0===d||d(void 0),c.emit(e,t),P()}}catch(e){i.error("rebroadcast parse error",e),b()}})),f(O).then((e=>p=e)),m(O).then((e=>h=e)),l(O).then((e=>g=e)),await S,v=void 0,y=void 0,clearTimeout(n),{inputAudioCodec:p,inputVideoCodec:h,inputVideoResolution:g,resetActivityTimer:P,isActive:()=>a,kill:b,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},on(e,t){return c.on(e,t),this},once(e,t){return c.once(e,t),this},removeListener(e,t){return c.removeListener(e,t),this}}};var r=i(808),o=c(i(81)),n=i(769),s=i(361),a=c(i(510)),d=i(833);function c(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;async function l(e){return new Promise((t=>{const i=r=>{const o=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(o);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function p(e,t){return new Promise((i=>{const r=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 d=o.indexOf(",");-1!==a&&d<a&&(a=d),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(o.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function m(e){return p(e,"Video")}async function f(e){return p(e,"Audio")}},769:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bindZero=async function(e){return e.bind(0),await(0,n.once)(e,"listening"),e.address().port},t.listenZero=s,t.listenZeroSingleClient=async function(){const e=new o.default.Server,t=await s(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,o=(r=i(808))&&r.__esModule?r:{default:r},n=i(361);async function s(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}},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]}}))}))},701:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const o=()=>{const r=e.read(t);r&&(s(),i(r))},n=()=>{s(),r(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)}))}},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=o(t);if(i&&i.has(e))return i.get(e);var r={},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(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{deviceManager:n}=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((()=>n.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=n.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),n.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){n.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.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=a(e);let i,r,o;for await(const e of t)i?r||(r=e):i=e,yield{startStream:o,chunks:[e.header,e.data],type:e.type},i&&r&&!o&&(o=Buffer.concat([i.header,i.data,r.header,r.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 i=e[t];for(let r=0;r<i.chunks.length;r++){const o=i.chunks[r];let n=0;for(;n+188<o.length;){const i=o.subarray(n,n+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);n+=188}}}return e}}},t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:n}},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 n=i.computeLength(t,r);for(;;){const i=await(0,o.readLength)(e,n);yield{chunks:[i],width:t,height:r}}},findSyncFrame:n}},t.parseFragmentedMP4=a;var r=i(361),o=i(701);function n(e){return e}function s(e,t){return async function*(i){let o=[],n=0;for(;;){const s=i.read();if(!s){await(0,r.once)(i,"readable");continue}if(o.push(s),n+=s.length,n<e)continue;const a=Buffer.concat(o);null==t||t(a);const d=a.length%e,c=a.slice(0,a.length-d),u=a.slice(a.length-d);o=[u],n=u.length,yield{chunks:[c]}}}}async function*a(e){for(;;){const t=await(0,o.readLength)(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await(0,o.readLength)(e,i);yield{header:t,length:i,type:r,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}},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]},o=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,o(i(393),t);const n=i(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,i,r,o){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=r,this._mixinStorageSuffix=o,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(n.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.SCRYPTED_MEDIA_SCHEME=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,o,n,s,a,d,c,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=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=d,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(d||(t.LockState=d={})),t.MediaPlayerState=c,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(c||(t.MediaPlayerState=c={})),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,r){var o,n;function s(e){const o=n=>{const s=n.toString();for(const e of i)if(-1!==s.indexOf(e))return;if(!r&&(-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 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(" "))};const i=["decode_slice_header error","no frame!","non-existing PPS"]},81:e=>{"use strict";e.exports=require("child_process")},361:e=>{"use strict";e.exports=require("events")},808:e=>{"use strict";e.exports=require("net")}},t={};function i(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.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=c(t);if(i&&i.has(e))return i.get(e);var r={},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(r,n,s):r[n]=e[n]}r.default=e,i&&i.set(e,r);return r}(i(510)),o=i(361),n=i(567),s=i(201),a=i(129),d=i(454);function c(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(c=function(e){return e?i:t})(e)}function u(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:l,log:p,systemManager:m,deviceManager:f}=t.default,h=1e4,g="prebufferDuration",v="sendKeyframe",y="Default",S="AAC or No Audio",b=`${S} (Copy)`,M="Compatible Audio",P="Other Audio",w="PCM or G.711 Audio",x=`${w} (Copy, Unstable)`,D=["aac","mp3","mp2","opus"],O="-fflags +genpts",C=[S,M,P,w],I=["mpegts","mp4","s16le"];class PrebufferSession{constructor(e,t,i,r){u(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),u(this,"detectedIdrInterval",0),u(this,"prevIdr",0),u(this,"audioDisabled",!1),u(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}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[]}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)||"";C.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(S),i=-1!==e.indexOf(M),r=-1!==e.indexOf(P),o=-1!==e.indexOf(w);return{isUsingDefaultAudioConfig:!(t||i||r||o),aacAudio:t,pcmAudio:o,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 o=Date.now()-r,n=Math.round(i/o*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,d,c;(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)||y,choices:[y,b,"Compatible Audio (Copy)","Other Audio (Transcode)",x]},{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:O,choices:[O,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0}),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===(d=t.inputVideoCodec)||void 0===d?void 0:d.toString())||"unknown")+"/"+((null==t||null===(c=t.inputAudioCodec)||void 0===c?void 0:c.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,o,n,d,c;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const u=parseInt(this.storage.getItem(g))||h;let m;try{m=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const f=null===(null===(e=m)||void 0===e||null===(i=e.audio)||void 0===i?void 0:i.codec),v=null===(r=m)||void 0===r||null===(o=r.audio)||void 0===o?void 0:o.codec,{isUsingDefaultAudioConfig:y,aacAudio:b,compatibleAudio:M,reencodeAudio:P,pcmAudio:w}=this.getAudioConfig();let x=!1;f||v||!y||void 0!==this.detectedAudioCodec||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),x=!0),!f&&v&&void 0!==this.detectedAudioCodec&&this.detectedAudioCodec!==v&&this.console.warn("Audio codec plugin reported vs detected mismatch",v,this.detectedAudioCodec);const C=void 0===this.detectedAudioCodec?null==v?void 0:v.toLowerCase():null===(n=this.detectedAudioCodec)||void 0===n?void 0:n.toLowerCase();if(!x){!D.includes(C)?(y&&p.a(`${this.mixin.name} is using the ${this.detectedAudioCodec} audio codec and has had its audio disabled. Select 'Disable Audio' or '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:",this.detectedAudioCodec||v)):!f&&y&&void 0===v&&void 0!==this.detectedAudioCodec&&("aac"===this.detectedAudioCodec?p.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select '${S}' in the camera stream's Rebroadcast settings to suppress this alert.`):p.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select 'Compatible Audio' in the camera stream's Rebroadcast settings to suppress this alert.`))}const A=await this.mixinDevice.getVideoStream(m),k=await l.convertMediaObjectToBuffer(A,t.ScryptedMimeTypes.FFmpegInput),_=JSON.parse(k.toString()),T=["-bsf:a","aac_adtstoasc"],E=[];let L;this.audioDisabled=!1;const j=null===this.detectedAudioCodec;if(f||x||j)L=["-an"],this.audioDisabled=!0;else if(w)L=["-an"];else if(P||v&&!D.includes(v))L=["-bsf:a","aac_adtstoasc","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-acodec","libfdk_aac","-profile:a","aac_low","-flags","+global_header"];else if(b)L=["-acodec","copy"],L.push(...T);else if(M)L=["-acodec","copy"],L.push(...E);else{L=["-acodec","copy"];const e="aac"===C?T:E;L.push(...e)}const R=["-vcodec","copy"],B={console:this.console,timeout:6e4,parsers:{mp4:(0,a.createFragmentedMp4Parser)({vcodec:R,acodec:L}),mpegts:(0,a.createMpegTsParser)({vcodec:R,acodec:L})}};!w||f||j||(B.parsers.s16le=(0,a.createPCMParser)()),this.parsers=B.parsers;const V=this.storage.getItem(this.ffmpegInputArgumentsKey)||O;_.inputArguments.unshift(...V.split(" "));const H=await(0,s.startParserSession)(_,B);if(H.inputAudioCodec?D.includes(null===(d=H.inputAudioCodec)||void 0===d?void 0:d.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",H.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",H.inputAudioCodec):this.console.log("No audio stream detected."),this.detectedAudioCodec=H.inputAudioCodec||null,this.detectedVideoCodec=H.inputVideoCodec||null,"h264"!==H.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."),H.kill(),this.startPrebufferSession();if(this.parserSession=H,null!==(c=_.mediaStreamOptions)&&void 0!==c&&c.refreshAt){let e,i=_.mediaStreamOptions;const r=async()=>{if(!H.isActive)return;const e=await this.mixinDevice.getVideoStream(i),r=await l.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,o(n)},o=t=>{const i=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),e=setTimeout(r,i)};o(_),H.once("killed",(()=>clearTimeout(e)))}H.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===H&&(this.parserSession=void 0)}));for(const e of I){let t=0;H.on(e,(i=>{const r=this.prebuffers[e],o=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),r.push({time:o,chunk:i});r.length&&r[0].time<o-u;)r.shift(),t++;t>1e3&&(this.prebuffers[e]=r.slice(),t=0)}))}return H}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,i;this.ensurePrebufferSession();const r=await this.parserSessionPromise,o="false"!==this.storage.getItem(v),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=async e=>{const t=this.prebuffers[e],{server:i,port:o}=await(0,s.createRebroadcaster)({console:this.console,connect:(o,s)=>{this.activeClients++,this.printActiveClients(),i.close();const a=Date.now(),d=e=>{o(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),this.console.log(this.streamName,"prebuffer request ended"),r.removeListener(e,d),r.removeListener("killed",c)};r.on(e,d),r.once("killed",c);for(const e of t)e.time<a-n||d(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(r),c()}}});return setTimeout((()=>i.close()),3e4),o},d=I.find((t=>t===(null==e?void 0:e.container)))||"mpegts",c=Object.assign({},r.mediaStreamOptions);c.prebuffer=n;const{pcmAudio:u,reencodeAudio:p}=this.getAudioConfig();this.audioDisabled?c.audio=null:c.audio=p?{codec:"aac",encoder:"libfdk_aac",profile:"aac_low"}:{codec:null==r?void 0:r.inputAudioCodec},c.video&&null!==(t=r.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(i=r.inputVideoResolution)&&void 0!==i&&i[3]&&Object.assign(c.video,{width:parseInt(r.inputVideoResolution[2]),height:parseInt(r.inputVideoResolution[3])});const m=Date.now();let f=0;const h=this.prebuffers[d];for(const e of h)if(!(e.time<m-n))for(const t of e.chunk.chunks)f+=t.length;const g=Math.max(5e5,f).toString(),y=`tcp://127.0.0.1:${await a(d)}`,S={url:y,container:d,inputArguments:["-analyzeduration","0","-probesize",g,"-f",d,"-i",y],mediaStreamOptions:c};u&&S.inputArguments.push("-analyzeduration","0","-probesize",g,"-f","s16le","-i",`tcp://127.0.0.1:${await a("s16le")}`);return l.createFFmpegMediaObject(S)}}class PrebufferMixin extends n.SettingsMixinDeviceBase{constructor(e,t,i,r){super(e,i,{providerNativeId:r,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),u(this,"released",!1),u(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],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"),p.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 d=n.length;for(const t of n){let i=this.sessions.get(t);if(!i){var c;const n=null==e?void 0:e.find((e=>e.id===t));null!=n&&n.prebuffer&&p.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=!r.includes(t);if(i=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,i),t===(null==e||null===(c=e[0])||void 0===c?void 0:c.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==d,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==d}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)")})()}}f.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:g,value:this.storage.getItem(g)||h.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:v,value:("false"!==this.storage.getItem(v)).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(g))||h;if(t)for(const e of t)e.prebuffer=i;else e.push({id:"default",name:"Default",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 d.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(m.getSystemState())){var t;const i=m.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((()=>f.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 o=exports="undefined"==typeof exports?{}:exports;for(var n in r)o[n]=r[n];r.__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=o(t);if(i&&i.has(e))return i.get(e);var r={},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(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{systemManager:n}=r.default,s="v4";class AutoenableMixinProvider extends r.ScryptedDeviceBase{constructor(e){var t,i,o;super(e),o={},(i="hasEnabledMixin")in(t=this)?Object.defineProperty(t,i,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[i]=o;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=n.getComponent("plugins"),n.listen((async(e,t,i)=>{t.eventInterface!==r.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 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.createRebroadcaster=async function(e){let t=0;const i=(0,r.createServer)((i=>{let r=!0;const o=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,null==e||e()};let n=null==e?void 0:e.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),o);i.once("close",(()=>{t--,o()})),i.on("error",(t=>{var i;return null==e||null===(i=e.console)||void 0===i?void 0:i.log("client stream ended")})),t++})),o=await(0,n.listenZero)(i);return{server:i,port:o,get clients(){return t}}},t.parseAudioCodec=f,t.parseResolution=p,t.parseVideoCodec=h,t.startParserSession=async function(e,t){const{console:i}=t;let r,a,u=!0;const m=new s.EventEmitter;let g,v,y,S,b;m.on("error",(e=>i.error("rebroadcast error",e)));const M=new Promise(((e,t)=>{S=e,b=t}));function P(){var e;u&&(m.emit("killed"),m.emit("error",new Error("killed"))),u=!1,null==I||I.kill(),null==I||I.kill("SIGKILL"),null===(e=b)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(a)}function w(){i.error("timeout waiting for data, killing parser session"),P()}function x(){t.timeout&&(clearTimeout(r),r=setTimeout(w,t.timeout))}x();const D=e.inputArguments.slice();a=setTimeout(P,3e4);const O=["pipe","pipe","pipe"];let C=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){const t=c.default.createSocket("udp4"),r=await(0,n.bindZero)(t);D.push(...i.outputArguments,r.url),(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()}})()}else D.push(...i.outputArguments,"pipe:"+C++),O.push("pipe")}D.push("-sdp_file","pipe:"+C++),O.push("pipe"),D.unshift("-hide_banner"),(0,d.safePrintFFmpegArguments)(i,D);const I=o.default.spawn(await l.getFFmpegPath(),D,{stdio:O});(0,d.ffmpegLogInitialOutput)(i,I),I.on("exit",P);const A=new Promise((e=>{const t=[];I.stdio[C-1].on("data",(i=>{t.push(i),e(t)}))}));let k=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse)return;const o=I.stdio[3+k];k++;try{for await(const t of r.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){i.error("rebroadcast parse error",e),P()}})),f(I).then((e=>g=e)),h(I).then((e=>v=e)),p(I).then((e=>y=e)),await M,S=void 0,b=void 0,clearTimeout(a),{sdp:A,inputAudioCodec:g,inputVideoCodec:v,inputVideoResolution:y,resetActivityTimer:x,isActive:()=>u,kill:P,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 r=i(808),o=u(i(81)),n=i(769),s=i(361),a=u(i(510)),d=i(833),c=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 o=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(o);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function m(e,t){return new Promise((i=>{const r=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 d=o.indexOf(",");-1!==a&&d<a&&(a=d),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(o.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")}},769:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),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),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,o=(r=i(808))&&r.__esModule?r:{default:r},n=i(361);async function s(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}},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]}}))}))},701:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const o=()=>{const r=e.read(t);r&&(s(),i(r))},n=()=>{s(),r(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)}))}},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=o(t);if(i&&i.has(e))return i.get(e);var r={},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(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{deviceManager:n}=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((()=>n.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=n.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),n.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){n.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=d(e);let i,r,o;for await(const e of t)i?r||(r=e):i=e,yield{startStream:o,chunks:[e.header,e.data],type:e.type},i&&r&&!o&&(o=Buffer.concat([i.header,i.data,r.header,r.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 i=e[t];for(let r=0;r<i.chunks.length;r++){const o=i.chunks[r];let n=0;for(;n+188<o.length;){const i=o.subarray(n,n+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);n+=188}}}return e}}},t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:n}},t.createRawVideoParser=function(e){var t;const i=(null===(t=e)||void 0===t?void 0:t.pixelFormat)||c;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 n=i.computeLength(t,r);for(;;){const i=await(0,o.readLength)(e,n);yield{chunks:[i],width:t,height:r}}},findSyncFrame:n}},t.createRtpParser=function(...e){return{container:"sdp",outputArguments:[...e,"-f","rtp"],parseDatagram:a(),findSyncFrame:n}},t.parseFragmentedMP4=d;var r=i(361),o=i(701);function n(e){return e}function s(e,t){return async function*(i){let o=[],n=0;for(;;){const s=i.read();if(!s){await(0,r.once)(i,"readable");continue}if(o.push(s),n+=s.length,n<e)continue;const a=Buffer.concat(o);null==t||t(a);const d=a.length%e,c=a.slice(0,a.length-d),u=a.slice(a.length-d);o=[u],n=u.length,yield{chunks:[c]}}}}function a(){return async function*(e){for(;;){const[t]=await(0,r.once)(e,"message");yield{chunks:[t]}}}}async function*d(e){for(;;){const t=await(0,o.readLength)(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await(0,o.readLength)(e,i);yield{header:t,length:i,type:r,data:n}}}const c={name:"yuv420p",computeLength:(e,t)=>e*t*1.5};t.PIXEL_FORMAT_YUV420P=c;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]},o=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,o(i(393),t);const n=i(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,i,r,o){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=r,this._mixinStorageSuffix=o,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(n.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.SCRYPTED_MEDIA_SCHEME=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,o,n,s,a,d,c,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=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=d,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(d||(t.LockState=d={})),t.MediaPlayerState=c,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(c||(t.MediaPlayerState=c={})),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,r){var o,n;function s(e){const o=n=>{const s=n.toString();for(const e of i)if(-1!==s.indexOf(e))return;if(!r&&(-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 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(" "))};const i=["decode_slice_header error","no frame!","non-existing PPS"]},81:e=>{"use strict";e.exports=require("child_process")},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 i(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}var r={};(()=>{"use strict";var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t,o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=p(t);if(i&&i.has(e))return i.get(e);var r={},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(r,n,s):r[n]=e[n]}r.default=e,i&&i.set(e,r);return r}(i(510)),n=i(361),s=i(567),a=i(201),d=i(129),c=i(454),u=(t=i(891))&&t.__esModule?t:{default:t},l=i(769);function p(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(p=function(e){return e?i:t})(e)}function m(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:h,log:f,systemManager:g,deviceManager:v}=o.default,y=1e4,S="prebufferDuration",b="sendKeyframe",M="Default",P="AAC or No Audio",w=`${P} (Copy)`,x="Compatible Audio",D="Other Audio",O="PCM or G.711 Audio",C=`${O} (Copy, Unstable)`,I=["aac","mp3","mp2","opus"],A="-fflags +genpts",k=[P,x,D],_=["mpegts","mp4","s16le","rtpvideo","rtpaudio"];class PrebufferSession{constructor(e,t,i,r){m(this,"prebuffers",{mp4:[],mpegts:[],s16le:[],rtpvideo:[],rtpaudio:[]}),m(this,"detectedIdrInterval",0),m(this,"prevIdr",0),m(this,"audioDisabled",!1),m(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}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[],this.prebuffers.rtpaudio=[],this.prebuffers.rtpvideo=[]}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)||"";k.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(P),i=-1!==e.indexOf(x),r=-1!==e.indexOf(D),o=-1!==e.indexOf(O);return{isUsingDefaultAudioConfig:!(t||i||r||o),aacAudio:t,pcmAudio:o,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 o=Date.now()-r,n=Math.round(i/o*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,d,c;(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)||M,choices:[M,w,"Compatible Audio (Copy)","Other Audio (Transcode)",C]},{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:A,choices:[A,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0},{title:"Rebroadcast Mode",group:s,description:"The stream format to use when rebroadcasting. RTP will increase startup time but may resolve PCM audio issues.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTP"],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===(d=t.inputVideoCodec)||void 0===d?void 0:d.toString())||"unknown")+"/"+((null==t||null===(c=t.inputAudioCodec)||void 0===c?void 0:c.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,t,i,r,n,s,c;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[],this.prebuffers.rtpvideo=[],this.prebuffers.rtpaudio=[];const u=parseInt(this.storage.getItem(S))||y;let l;try{l=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const p=null===(null===(e=l)||void 0===e||null===(t=e.audio)||void 0===t?void 0:t.codec),m=null===(i=l)||void 0===i||null===(r=i.audio)||void 0===r?void 0:r.codec,{isUsingDefaultAudioConfig:g,aacAudio:v,compatibleAudio:b,reencodeAudio:M,pcmAudio:w}=this.getAudioConfig();let x=!1;p||m||!g||void 0!==this.detectedAudioCodec||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),x=!0),!p&&m&&void 0!==this.detectedAudioCodec&&this.detectedAudioCodec!==m&&this.console.warn("Audio codec plugin reported vs detected mismatch",m,this.detectedAudioCodec);const D=void 0===this.detectedAudioCodec?null==m?void 0:m.toLowerCase():null===(n=this.detectedAudioCodec)||void 0===n?void 0:n.toLowerCase();if(!x){!I.includes(D)?(g&&f.a(`${this.mixin.name} is using the ${D} audio codec and has had its audio disabled. Select 'Disable Audio' or '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)):!p&&g&&void 0===m&&void 0!==this.detectedAudioCodec&&("aac"===this.detectedAudioCodec?f.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select '${P}' in the camera stream's Rebroadcast settings to suppress this alert.`):f.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select 'Compatible Audio' in the camera stream's Rebroadcast settings to suppress this alert.`))}const O=await this.mixinDevice.getVideoStream(l),C=await h.convertMediaObjectToBuffer(O,o.ScryptedMimeTypes.FFmpegInput),k=JSON.parse(C.toString()),T=["-bsf:a","aac_adtstoasc"],E=[];let L;this.audioDisabled=!1;const R=null===this.detectedAudioCodec;if(p||x||R)L=["-an"],this.audioDisabled=!0;else if(w)L=["-an"];else if(M||m&&!I.includes(m))L=["-bsf:a","aac_adtstoasc","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-acodec","libfdk_aac","-profile:a","aac_low","-flags","+global_header"];else if(v)L=["-acodec","copy"],L.push(...T);else if(b)L=["-acodec","copy"],L.push(...E);else{L=["-acodec","copy"];const e="aac"===D?T:E;L.push(...e)}const j=["-vcodec","copy"],B={console:this.console,timeout:6e4,parsers:{mp4:(0,d.createFragmentedMp4Parser)({vcodec:j,acodec:L})}};"RTP"===this.storage.getItem(this.rebroadcastModeKey)?(B.parsers.rtpvideo=(0,d.createRtpParser)("-an","-vcodec","copy"),B.parsers.rtpaudio=(0,d.createRtpParser)("-vn","-acodec","copy")):B.parsers.mpegts=(0,d.createMpegTsParser)({vcodec:j,acodec:L}),!w||p||R||(B.parsers.s16le=(0,d.createPCMParser)()),this.parsers=B.parsers;const V=this.storage.getItem(this.ffmpegInputArgumentsKey)||A;k.inputArguments.unshift(...V.split(" "));const F=await(0,a.startParserSession)(k,B);if(F.inputAudioCodec?I.includes(null===(s=F.inputAudioCodec)||void 0===s?void 0:s.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",F.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",F.inputAudioCodec):this.console.log("No audio stream detected."),this.detectedAudioCodec=F.inputAudioCodec||null,this.detectedVideoCodec=F.inputVideoCodec||null,"h264"!==F.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."),F.kill(),this.startPrebufferSession();if(this.parserSession=F,null!==(c=k.mediaStreamOptions)&&void 0!==c&&c.refreshAt){let e,t=k.mediaStreamOptions;const i=async()=>{if(!F.isActive)return;const e=await this.mixinDevice.getVideoStream(t),i=await h.convertMediaObjectToBuffer(e,o.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());t=n.mediaStreamOptions,r(n)},r=t=>{const r=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),e=setTimeout(i,r)};r(k),F.once("killed",(()=>clearTimeout(e)))}F.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===F&&(this.parserSession=void 0)}));for(const e of _){var H;if(null!==(H=this.parsers[e])&&void 0!==H&&H.parseDatagram)continue;let t=0;F.on(e,(i=>{const r=this.prebuffers[e],o=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),r.push({time:o,chunk:i});r.length&&r[0].time<o-u;)r.shift(),t++;t>1e3&&(this.prebuffers[e]=r.slice(),t=0)}))}return F}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,i;this.ensurePrebufferSession();const r=await this.parserSessionPromise,o="false"!==this.storage.getItem(b),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 s=async e=>{const t=this.prebuffers[e];if(this.parsers[e].parseDatagram){let e=Buffer.concat(await r.sdp).toString();const t=Math.round(4e4*Math.random()+1e4),i=Math.round(4e4*Math.random()+1e4);e=e.replace("m=audio 0","m=audio "+t),e=e.replace("m=video 0","m=video "+i);const o=u.default.createSocket("udp4");o.bind();const n=(e,t)=>{for(const i of e.chunks)o.send(i,t)},s=e=>n(e,i),a=e=>n(e,t),d=()=>{o.close(),r.removeListener("rtpvideo",s),r.removeListener("rtpaudio",a),r.removeListener("killed",d)};r.once("killed",d);const c=await(0,l.listenZeroSingleClient)();return c.clientPromise.then((async t=>{this.activeClients++,this.printActiveClients(),t.once("close",(()=>{this.activeClients--,this.inactivityCheck(r),d()})),t.write(e),t.end()})).catch(d),r.on("rtpvideo",s),r.on("rtpaudio",a),c.url}const{server:i,port:o}=await(0,a.createRebroadcaster)({console:this.console,connect:(o,s)=>{this.activeClients++,this.printActiveClients(),i.close();const a=Date.now(),d=e=>{o(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),this.console.log(this.streamName,"prebuffer request ended"),r.removeListener(e,d),r.removeListener("killed",c)};r.on(e,d),r.once("killed",c);for(const e of t)e.time<a-n||d(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(r),c()}}});return setTimeout((()=>i.close()),3e4),`tcp://127.0.0.1:${o}`},d="RTP"===this.storage.getItem(this.rebroadcastModeKey)?"rtpvideo":"mpegts",c=this.parsers[null==e?void 0:e.container]?null==e?void 0:e.container:d,p=Object.assign({},r.mediaStreamOptions);p.prebuffer=n;const{pcmAudio:m,reencodeAudio:f}=this.getAudioConfig();this.audioDisabled?p.audio=null:p.audio=f?{codec:"aac",encoder:"libfdk_aac",profile:"aac_low"}:{codec:null==r?void 0:r.inputAudioCodec},p.video&&null!==(t=r.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(i=r.inputVideoResolution)&&void 0!==i&&i[3]&&Object.assign(p.video,{width:parseInt(r.inputVideoResolution[2]),height:parseInt(r.inputVideoResolution[3])});const g=Date.now();let v=0;const y=this.prebuffers[c];for(const e of y)if(!(e.time<g-n))for(const t of e.chunk.chunks)v+=t.length;const S=Math.max(5e5,v).toString(),M=await s(c),P={url:M,container:c,inputArguments:["-analyzeduration","0","-probesize",S,"-f",this.parsers[c].container,"-i",M],mediaStreamOptions:p};m&&P.inputArguments.push("-analyzeduration","0","-probesize",S,"-f","s16le","-i",await s("s16le"));return h.createFFmpegMediaObject(P)}}class PrebufferMixin extends s.SettingsMixinDeviceBase{constructor(e,t,i,r){super(e,i,{providerNativeId:r,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),m(this,"released",!1),m(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(),t=this.getEnabledMediaStreamOptions(e),i=t?t.map((e=>e.id)):[void 0],r=(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"),f.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(o.ScryptedInterface.Battery);let a=0;const d=r.length;for(const t of r){let r=this.sessions.get(t);if(!r){var c;const o=null==e?void 0:e.find((e=>e.id===t));null!=o&&o.prebuffer&&f.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=!i.includes(t);if(r=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,r),t===(null==e||null===(c=e[0])||void 0===c?void 0:c.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==d,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==d}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)")})()}}v.onMixinEvent(this.id,this.mixinProviderNativeId,o.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:S,value:this.storage.getItem(S)||y.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:b,value:("false"!==this.storage.getItem(b)).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(S))||y;if(t)for(const e of t)e.prebuffer=i;else e.push({id:"default",name:"Default",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(g.getSystemState())){var t;const i=g.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((()=>v.requestRestart()),i)}async canMixin(e,t){return t.includes(o.ScryptedInterface.VideoCamera)?[o.ScryptedInterface.VideoCamera,o.ScryptedInterface.Settings,o.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 T=new PrebufferProvider;e.default=T})();var o=exports="undefined"==typeof exports?{}:exports;for(var n in r)o[n]=r[n];r.__esModule&&Object.defineProperty(o,"__esModule",{value:!0})})();
2
2
  //# sourceMappingURL=main.nodejs.js.map
package/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.1.140",
3
+ "version": "0.1.144",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -4,8 +4,10 @@ import sdk from '@scrypted/sdk';
4
4
  import { once } from 'events';
5
5
  import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
6
6
  import { createRebroadcaster, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
7
- import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, createPCMParser, StreamParser } from '@scrypted/common/src/stream-parser';
7
+ import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, createPCMParser, StreamParser, createRtpParser } from '@scrypted/common/src/stream-parser';
8
8
  import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
9
+ import dgram from 'dgram';
10
+ import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
9
11
 
10
12
  const { mediaManager, log, systemManager, deviceManager } = sdk;
11
13
 
@@ -14,6 +16,7 @@ const PREBUFFER_DURATION_MS = 'prebufferDuration';
14
16
  const SEND_KEYFRAME = 'sendKeyframe';
15
17
  const AUDIO_CONFIGURATION_KEY_PREFIX = 'audioConfiguration-';
16
18
  const FFMPEG_INPUT_ARGUMENTS_KEY_PREFIX = 'ffmpegInputArguments-';
19
+ const REBROADCAST_MODE_KEY_PREFIX = 'rebroadcastMode-';
17
20
  const DEFAULT_AUDIO = 'Default';
18
21
  const AAC_AUDIO = 'AAC or No Audio';
19
22
  const AAC_AUDIO_DESCRIPTION = `${AAC_AUDIO} (Copy)`;
@@ -30,7 +33,7 @@ const VALID_AUDIO_CONFIGS = [
30
33
  AAC_AUDIO,
31
34
  COMPATIBLE_AUDIO,
32
35
  TRANSCODE_AUDIO,
33
- PCM_AUDIO,
36
+ // PCM_AUDIO,
34
37
  ];
35
38
 
36
39
  interface PrebufferStreamChunk {
@@ -42,10 +45,12 @@ interface Prebuffers {
42
45
  mp4: PrebufferStreamChunk[];
43
46
  mpegts: PrebufferStreamChunk[];
44
47
  s16le: PrebufferStreamChunk[];
48
+ rtpvideo: PrebufferStreamChunk[];
49
+ rtpaudio: PrebufferStreamChunk[];
45
50
  }
46
51
 
47
- type PrebufferParsers = "mpegts" | "mp4" | "s16le";
48
- const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 's16le'];
52
+ type PrebufferParsers = "mpegts" | "mp4" | "s16le" | "rtpvideo" | "rtpaudio";
53
+ const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 's16le', 'rtpvideo', 'rtpaudio'];
49
54
 
50
55
  class PrebufferSession {
51
56
 
@@ -55,6 +60,8 @@ class PrebufferSession {
55
60
  mp4: [],
56
61
  mpegts: [],
57
62
  s16le: [],
63
+ rtpvideo: [],
64
+ rtpaudio: [],
58
65
  };
59
66
  parsers: { [container: string]: StreamParser };
60
67
 
@@ -72,6 +79,7 @@ class PrebufferSession {
72
79
  inactivityTimeout: NodeJS.Timeout;
73
80
  audioConfigurationKey: string;
74
81
  ffmpegInputArgumentsKey: string;
82
+ rebroadcastModeKey: string;
75
83
 
76
84
  constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string, public stopInactive: boolean) {
77
85
  this.storage = mixin.storage;
@@ -79,12 +87,15 @@ class PrebufferSession {
79
87
  this.mixinDevice = mixin.mixinDevice;
80
88
  this.audioConfigurationKey = AUDIO_CONFIGURATION_KEY_PREFIX + this.streamId;
81
89
  this.ffmpegInputArgumentsKey = FFMPEG_INPUT_ARGUMENTS_KEY_PREFIX + this.streamId;
90
+ this.rebroadcastModeKey = REBROADCAST_MODE_KEY_PREFIX + this.streamId;
82
91
  }
83
92
 
84
93
  clearPrebuffers() {
85
94
  this.prebuffers.mp4 = [];
86
95
  this.prebuffers.mpegts = [];
87
96
  this.prebuffers.s16le = [];
97
+ this.prebuffers.rtpaudio = [];
98
+ this.prebuffers.rtpvideo = [];
88
99
  }
89
100
 
90
101
  ensurePrebufferSession() {
@@ -167,6 +178,18 @@ class PrebufferSession {
167
178
  '-v verbose',
168
179
  ],
169
180
  combobox: true,
181
+ },
182
+ {
183
+ title: 'Rebroadcast Mode',
184
+ group,
185
+ description: 'The stream format to use when rebroadcasting. RTP will increase startup time but may resolve PCM audio issues.',
186
+ placeholder: 'MPEG-TS',
187
+ choices: [
188
+ 'MPEG-TS',
189
+ 'RTP',
190
+ ],
191
+ key: this.rebroadcastModeKey,
192
+ value: this.storage.getItem(this.rebroadcastModeKey) || 'MPEG-TS',
170
193
  }
171
194
  );
172
195
 
@@ -218,6 +241,8 @@ class PrebufferSession {
218
241
  this.prebuffers.mp4 = [];
219
242
  this.prebuffers.mpegts = [];
220
243
  this.prebuffers.s16le = [];
244
+ this.prebuffers.rtpvideo = [];
245
+ this.prebuffers.rtpaudio = [];
221
246
  const prebufferDurationMs = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
222
247
 
223
248
  let mso: MediaStreamOptions;
@@ -261,9 +286,9 @@ class PrebufferSession {
261
286
  if (audioIncompatible) {
262
287
  // show an alert that rebroadcast needs an explicit setting by the user.
263
288
  if (isUsingDefaultAudioConfig) {
264
- log.a(`${this.mixin.name} is using the ${this.detectedAudioCodec} audio codec and has had its audio disabled. Select 'Disable Audio' or 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`);
289
+ log.a(`${this.mixin.name} is using the ${assumedAudioCodec} audio codec and has had its audio disabled. Select 'Disable Audio' or 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`);
265
290
  }
266
- this.console.warn('Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:', this.detectedAudioCodec || advertisedAudioCodec);
291
+ this.console.warn('Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:', assumedAudioCodec);
267
292
  }
268
293
  else if (!audioSoftMuted && isUsingDefaultAudioConfig && advertisedAudioCodec === undefined && this.detectedAudioCodec !== undefined) {
269
294
  // handling compatible codecs that were unspecified...
@@ -354,13 +379,21 @@ class PrebufferSession {
354
379
  vcodec,
355
380
  acodec,
356
381
  }),
357
- mpegts: createMpegTsParser({
358
- vcodec,
359
- acodec,
360
- }),
361
382
  },
362
383
  };
363
384
 
385
+ const rtpMode = this.storage.getItem(this.rebroadcastModeKey) === 'RTP';
386
+ if (!rtpMode) {
387
+ rbo.parsers.mpegts = createMpegTsParser({
388
+ vcodec,
389
+ acodec,
390
+ });
391
+ }
392
+ else {
393
+ rbo.parsers.rtpvideo = createRtpParser('-an', '-vcodec', 'copy');
394
+ rbo.parsers.rtpaudio = createRtpParser('-vn', '-acodec', 'copy');
395
+ }
396
+
364
397
  // if pcm prebuffer is requested, create the the parser. don't do it if
365
398
  // the camera wants to mute the audio though, or no audio was detected
366
399
  // in a prior attempt.
@@ -436,6 +469,9 @@ class PrebufferSession {
436
469
 
437
470
  // s16le will be a no-op if there's no pcm, no harm.
438
471
  for (const container of PrebufferParserValues) {
472
+ if (this.parsers[container]?.parseDatagram)
473
+ continue;
474
+
439
475
  let shifts = 0;
440
476
 
441
477
  session.on(container, (chunk: StreamChunk) => {
@@ -503,6 +539,65 @@ class PrebufferSession {
503
539
  const createContainerServer = async (container: PrebufferParsers) => {
504
540
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
505
541
 
542
+ if (this.parsers[container].parseDatagram) {
543
+ let sdp = Buffer.concat(await session.sdp).toString();
544
+ const audioPort = Math.round(Math.random() * 40000 + 10000);
545
+ const videoPort = Math.round(Math.random() * 40000 + 10000);
546
+ sdp = sdp.replace('m=audio 0', 'm=audio ' + audioPort);
547
+ sdp = sdp.replace('m=video 0', 'm=video ' + videoPort);
548
+
549
+ const d = dgram.createSocket('udp4');
550
+ d.bind();
551
+
552
+ const safeWriteData = (chunk: StreamChunk, port: number) => {
553
+ for (const c of chunk.chunks) {
554
+ d.send(c, port);
555
+ }
556
+ }
557
+
558
+ const wv = (chunk: StreamChunk) => safeWriteData(chunk, videoPort);
559
+ const wa = (chunk: StreamChunk) => safeWriteData(chunk, audioPort);
560
+ const cleanup = () => {
561
+ d.close();
562
+ session.removeListener('rtpvideo', wv);
563
+ session.removeListener('rtpaudio', wa);
564
+ session.removeListener('killed', cleanup);
565
+ }
566
+
567
+ session.once('killed', cleanup);
568
+
569
+ const sdpClient = await listenZeroSingleClient();
570
+ sdpClient.clientPromise.then(async (c) => {
571
+ this.activeClients++;
572
+ this.printActiveClients();
573
+
574
+ c.once('close', () => {
575
+ this.activeClients--;
576
+ this.inactivityCheck(session);
577
+ cleanup();
578
+ });
579
+ c.write(sdp);
580
+ c.end();
581
+
582
+ // await new Promise(resolve => setTimeout(resolve, 500));
583
+ // for (const prebuffer of this.prebuffers.rtpvideo) {
584
+ // if (prebuffer.time < now - requestedPrebuffer)
585
+ // continue;
586
+ // safeWriteData(prebuffer.chunk, videoPort);
587
+ // }
588
+ // for (const prebuffer of this.prebuffers.rtpaudio) {
589
+ // if (prebuffer.time < now - requestedPrebuffer)
590
+ // continue;
591
+ // safeWriteData(prebuffer.chunk, audioPort);
592
+ // }
593
+ })
594
+ .catch(cleanup);
595
+
596
+ session.on('rtpvideo', wv)
597
+ session.on('rtpaudio', wa);
598
+ return sdpClient.url;
599
+ }
600
+
506
601
  const { server, port } = await createRebroadcaster({
507
602
  console: this.console,
508
603
  connect: (writeData, destroy) => {
@@ -557,10 +652,13 @@ class PrebufferSession {
557
652
 
558
653
  setTimeout(() => server.close(), 30000);
559
654
 
560
- return port;
655
+ return `tcp://127.0.0.1:${port}`;
561
656
  }
562
657
 
563
- const container: PrebufferParsers = PrebufferParserValues.find(parser => parser === options?.container) || 'mpegts';
658
+ const rtpMode = this.storage.getItem(this.rebroadcastModeKey) === 'RTP';
659
+ const defaultContainer = rtpMode ? 'rtpvideo' : 'mpegts';
660
+
661
+ const container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
564
662
 
565
663
  const mediaStreamOptions: MediaStreamOptions = Object.assign({}, session.mediaStreamOptions);
566
664
 
@@ -604,13 +702,13 @@ class PrebufferSession {
604
702
 
605
703
  const length = Math.max(500000, available).toString();
606
704
 
607
- const url = `tcp://127.0.0.1:${await createContainerServer(container)}`;
705
+ const url = await createContainerServer(container);
608
706
  const ffmpegInput: FFMpegInput = {
609
707
  url,
610
708
  container,
611
709
  inputArguments: [
612
710
  '-analyzeduration', '0', '-probesize', length,
613
- '-f', container,
711
+ '-f', this.parsers[container].container,
614
712
  '-i', url,
615
713
  ],
616
714
  mediaStreamOptions,
@@ -620,7 +718,7 @@ class PrebufferSession {
620
718
  ffmpegInput.inputArguments.push(
621
719
  '-analyzeduration', '0', '-probesize', length,
622
720
  '-f', 's16le',
623
- '-i', `tcp://127.0.0.1:${await createContainerServer('s16le')}`,
721
+ '-i', await createContainerServer('s16le'),
624
722
  )
625
723
  }
626
724