@scrypted/prebuffer-mixin 0.1.124 → 0.1.128

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,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var i={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=e[s]}i.default=e,r&&r.set(e,i);return i}(r(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}const{systemManager:o}=i.default,s="v4";class AutoenableMixinProvider extends i.ScryptedDeviceBase{constructor(e){var t,r,n;super(e),n={},(r="hasEnabledMixin")in(t=this)?Object.defineProperty(t,r,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[r]=n;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=o.getComponent("plugins"),o.listen((async(e,t,r)=>{t.eventInterface!==i.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)}));for(const e of Object.keys(o.getSystemState())){const t=o.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id]===s)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const r=(e.mixins||[]).slice();r.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,r),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==s&&(this.hasEnabledMixin[e]=s,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=AutoenableMixinProvider},201:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createRebroadcaster=g,t.parseAudioCodec=h,t.parseResolution=p,t.parseVideoCodec=f,t.safePrintFFmpegArguments=l,t.startRebroadcastSession=async function(e,t){let r,a,d=0,m=!0;const v=new s.EventEmitter;v.on("error",(e=>y.error("rebroadcast error",e)));const{console:y}=t;let S,b,M,P,O;const D=new Promise(((e,t)=>{P=e,O=t}));function x(){m&&(v.emit("killed"),v.emit("error",new Error("killed"))),m=!1,null==k||k.kill(),null==k||k.kill("SIGKILL");for(const e of A)null==e||e.close();O(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(a)}function w(){t.timeout&&(clearTimeout(r),r=setTimeout(x,t.timeout))}w();const I={},C=e.inputArguments.slice(),A=[];a=setTimeout(x,3e4);for(const n of Object.keys(t.parsers)){const s=t.parsers[n],a=n+"-data";if(t.parseOnly)I[n]={url:void 0,mediaStreamOptions:e.mediaStreamOptions};else{const{server:t,port:i}=await g({connect:(e,t)=>{d++,clearTimeout(r);const i=()=>{v.removeListener(a,e),v.removeListener("killed",t),d--,0===d&&w(),t()};return v.on(a,e),v.once("killed",i),i}});A.push(t);const o=`tcp://127.0.0.1:${i}`;I[n]={url:o,mediaStreamOptions:e.mediaStreamOptions,inputArguments:["-f",n,"-i",o]}}const c=(0,i.createServer)((async e=>{c.close(),P(e);try{const i=n+"-data";for await(const n of s.parse(e,parseInt(null===(t=M)||void 0===t?void 0:t[2]),parseInt(null===(r=M)||void 0===r?void 0:r[3]))){var t,r;v.emit(i,n)}}catch(e){y.error("rebroadcast parse error",e),x()}}));A.push(c);const u=await(0,o.listenZero)(c);C.push(...s.outputArguments,`tcp://127.0.0.1:${u}`)}C.unshift("-hide_banner"),l(y,C);const k=n.default.spawn(await u.getFFmpegPath(),C);return(0,c.ffmpegLogInitialOutput)(y,k),k.on("exit",x),h(k).then((e=>S=e)),f(k).then((e=>b=e)),p(k).then((e=>M=e)),await D,clearTimeout(a),{inputAudioCodec:S,inputVideoCodec:b,inputVideoResolution:M,events:v,resetActivityTimer:w,isActive:()=>m,kill:x,servers:A,cp:k,ffmpegInputs:I}};var i=r(808),n=d(r(81)),o=r(769),s=r(361),a=d(r(510)),c=r(833);function d(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;function l(e,t){const r=[];for(const e of t)try{const t=new URL(e);t.password?(t.password="REDACTED",r.push(t.toString())):r.push(e)}catch(t){r.push(e)}e.log(r.join(" "))}async function p(e){return new Promise((t=>{const r=i=>{const n=i.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);o&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(o))};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function m(e,t){return new Promise((r=>{const i=n=>{const o=n.toString(),s=o.indexOf(`${t}: `);if(-1!==s){const n=o.substring(s+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),r(n.substring(0,a)))}};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function f(e){return m(e,"Video")}async function h(e){return m(e,"Audio")}async function g(e){const t=(0,i.createServer)((t=>{let r=!0;const i=()=>{t.removeAllListeners(),t.destroy();const e=n;n=void 0,null==e||e()};let n=null==e?void 0:e.connect((e=>{r&&(r=!1,e.startStream&&t.write(e.startStream));for(const r of e.chunks)t.write(r);return t.writableLength}),i);t.on("end",i),t.on("close",i),t.on("error",i)}));return{server:t,port:await(0,o.listenZero)(t)}}},769:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bindZero=async function(e){return e.bind(0),await(0,o.once)(e,"listening"),e.address().port},t.listenZero=s,t.listenZeroSingleClient=async function(){const e=new n.default.Server,t=await s(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{r(new Error("timeout waiting for client"))}),3e4);e.on("connection",(r=>{e.close(),clearTimeout(i),t(r)}))}));return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:r}};var i,n=(i=r(808))&&i.__esModule?i:{default:i},o=r(361);async function s(e){return e.listen(0),await(0,o.once)(e,"listening"),e.address().port}},833:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(568);Object.keys(i).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===i[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return i[e]}}))}))},701:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise(((r,i)=>{const n=()=>{const i=e.read(t);i&&(s(),r(i))},o=()=>{s(),i(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",n),e.removeListener("end",o)};e.on("readable",n),e.on("end",o)}))}},567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var i={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=e[s]}i.default=e,r&&r.set(e,i);return i}(r(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}const{deviceManager:o}=i.default;class SettingsMixinDeviceBase extends i.MixinDeviceBase{constructor(e,t,r){super(e,r.mixinDeviceInterfaces,t,r.providerNativeId,r.mixinStorageSuffix),this.settingsGroup=r.group,this.settingsGroupKey=r.groupKey,process.nextTick((()=>o.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)))}async getSettings(){const e=this.mixinDeviceInterfaces.includes(i.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,t=this.getMixinSettings(),r=[];try{const t=await e||[];r.push(...t)}catch(e){const t=this.name;r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:`${t} Extension settings failed to load.`,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=o.getDeviceState(this.mixinProviderNativeId).name;r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:`${t} Extension settings failed to load.`,readonly:!0})}return r}async putSetting(e,t){const r=this.settingsGroupKey+":";if(null==e||!e.startsWith(r))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(r.length),t),o.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)}release(){o.onMixinEvent(this.id,this,i.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.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 r,i,n;for await(const e of t)r?i||(i=e):r=e,yield{startStream:n,chunks:[e.header,e.data],type:e.type},r&&i&&!n&&(n=Buffer.concat([r.header,r.data,i.header,i.data]))},findSyncFrame:o}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,(e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")})),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const n=r.chunks[i];let o=0;for(;o+188<n.length;){const r=n.subarray(o,o+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);o+=188}}}return e}}},t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:o}},t.createRawVideoParser=function(e){var t;const r=(null===(t=e)||void 0===t?void 0:t.pixelFormat)||c;let i;e=e||{};const{size:s,everyNFrames:a}=e;s&&(i=`scale=${s.width}:${s.height}`);a&&a>1&&(i?i+=",":i="",i+=`select=not(mod(n\\,${a}))`);return{container:"rawvideo",outputArguments:[...i?["-vf",i]:[],"-an","-vcodec","rawvideo","-pix_fmt",r.name,"-f","rawvideo"],async*parse(e,t,i){if(!t||!i)throw new Error("error parsing rawvideo, unknown width and height");t=(null==s?void 0:s.width)||t,i=(null==s?void 0:s.height)||i;const o=r.computeLength(t,i);for(;;){const r=await(0,n.readLength)(e,o);yield{chunks:[r],width:t,height:i}}},findSyncFrame:o}},t.parseFragmentedMP4=a;var i=r(361),n=r(701);function o(e){return e}function s(e,t){return async function*(r){let n=[],o=0;for(;;){const s=r.read();if(!s){await(0,i.once)(r,"readable");continue}if(n.push(s),o+=s.length,o<e)continue;const a=Buffer.concat(n);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);n=[u],o=u.length,yield{chunks:[d]}}}}async function*a(e){for(;;){const t=await(0,n.readLength)(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),o=await(0,n.readLength)(e,r);yield{header:t,length:r,type:i,data:o}}}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,r)=>{"use strict";var i=Object.create?function(e,t,r,i){void 0===i&&(i=r),Object.defineProperty(e,i,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,i){void 0===i&&(i=r),e[i]=t[r]},n=function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||i(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(r(393),t);const o=r(393);class ScryptedDeviceBase extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=ScryptedDeviceBase;class MixinDeviceBase extends o.DeviceBase{constructor(e,t,r,i,n){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=i,this._mixinStorageSuffix=n,this._listeners=new Set,this._deviceState=r}get storage(){if(!this._storage){const e=this._mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=MixinDeviceBase,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var r of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(ScryptedDeviceBase.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(MixinDeviceBase.prototype,r,{set:t(r),get:e(r)})}();let s={};try{s=Object.assign(s,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=s},393:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ThermostatMode=t.TemperatureUnit=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterfaceDescriptors=t.ScryptedInterface=t.ScryptedDeviceType=t.SCRYPTED_MEDIA_SCHEME=t.MediaPlayerState=t.LockState=t.HumidityMode=t.FanMode=t.DeviceBase=void 0;let r;t.DeviceBase=class DeviceBase{},t.ScryptedInterfaceProperty=r,function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(r||(t.ScryptedInterfaceProperty=r={}));let i,n,o,s,a,c,d,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},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"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=i,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(i||(t.ScryptedDeviceType=i={})),t.HumidityMode=n,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(n||(t.HumidityMode=n={})),t.FanMode=o,function(e){e.Auto="Auto",e.Manual="Manual"}(o||(t.FanMode=o={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=c,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(c||(t.LockState=c={})),t.MediaPlayerState=d,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(d||(t.MediaPlayerState=d={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.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.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan"}(u||(t.ScryptedInterface=u={})),t.ScryptedMimeTypes=l,function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(l||(t.ScryptedMimeTypes=l={}));t.SCRYPTED_MEDIA_SCHEME="scryped-media://"},568:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,i){var n,o;function s(e){const n=o=>{const s=o.toString();for(const e of r)if(-1!==s.indexOf(e))return;if(!i&&(-1!==s.indexOf("frame=")||-1!==s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",n),void t.stderr.removeListener("data",n);e(s)};return n}null===(n=t.stdout)||void 0===n||n.on("data",s(e.log)),null===(o=t.stderr)||void 0===o||o.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))},t.probeVideoCamera=async function(e){let t;try{t=await e.getVideoStreamOptions()||[]}catch(e){}const r=t&&t.length&&null===t[0].audio;return{options:t,noAudio:r}};const r=["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 r(i){var n=t[i];if(void 0!==n)return n.exports;var o=t[i]={exports:{}};return e[i](o,o.exports,r),o.exports}var i={};(()=>{"use strict";var e=i;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t=l(r(510)),n=l(r(361)),o=r(567),s=r(201),a=r(833),c=r(129),d=r(454);function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(u=function(e){return e?r:t})(e)}function l(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=u(t);if(r&&r.has(e))return r.get(e);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var s=n?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(i,o,s):i[o]=e[o]}return i.default=e,r&&r.set(e,i),i}function p(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}const{mediaManager:m,log:f,systemManager:h,deviceManager:g}=t.default,v=1e4,y="prebufferDuration",S="sendKeyframe",b="Default",M="MP2/MP3 Audio",P="Other Audio",O="PCM or G.711 Audio",D=`${O} (Copy, Unstable)`,x=["aac","mp3","mp2","AAC","MP3","MP2","opus","OPUS","",void 0,null];class PrebufferSession{constructor(e,t,r){p(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),p(this,"events",new n.default),p(this,"detectedIdrInterval",0),p(this,"prevIdr",0),p(this,"incompatibleDetected",!1),p(this,"legacyDetected",!1),p(this,"audioDisabled",!1),p(this,"AUDIO_CONFIGURATION","audioConfiguration-"+this.streamId),this.mixin=e,this.streamName=t,this.streamId=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log("prebuffer session started",this.streamId),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){const e=this.storage.getItem(this.AUDIO_CONFIGURATION)||"",t=-1!==e.indexOf(O),r=-1!==e.indexOf(M),i=-1!==e.indexOf(P);return{audioConfig:e,pcmAudio:t,legacyAudio:r,reencodeAudio:i}}async getMixinSettings(){var e,t,r,i,n;const o=[],s=this.parserSession;let a=0,c=0;for(const e of this.prebuffers.mp4){c=c||e.time;for(const t of e.chunk.chunks)a+=t.byteLength}const d=Date.now()-c,u=Math.round(a/d*8),l=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";return o.push({title:"Audio Codec Transcoding",group:l,description:"Configuring your camera to output AAC, MP3, or MP2 is recommended. PCM/G711 cameras should set this to Reencode.",type:"string",key:this.AUDIO_CONFIGURATION,value:this.storage.getItem(this.AUDIO_CONFIGURATION)||b,choices:[b,"AAC or No Audio (Copy)","MP2/MP3 Audio (Copy)","Other Audio (Transcode)",D]},{key:"detectedResolution",group:l,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==s||null===(e=s.inputVideoResolution)||void 0===e?void 0:e[0])||"unknown"} @ ${u||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:l,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==s||null===(t=s.inputVideoCodec)||void 0===t?void 0:t.toString())||"unknown")+"/"+((null==s||null===(r=s.inputAudioCodec)||void 0===r?void 0:r.toString())||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2 audio is recommended."},{key:"detectedKeyframe",group:l,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"},{group:l,key:"rebroadcastUrl",title:"Rebroadcast Url",readonly:!0,value:null===(i=this.parserSession)||void 0===i||null===(n=i.ffmpegInputs)||void 0===n?void 0:n.mpegts.url}),o}async startPrebufferSession(){var e,r,i,n,o;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const d=parseInt(this.storage.getItem(y))||v,u=await(0,a.probeVideoCamera)(this.mixinDevice);let l;u.options&&(l=u.options.find((e=>e.id===this.streamId)));const p=null==u||null===(e=u.options)||void 0===e||null===(r=e[0])||void 0===r||null===(i=r.audio)||void 0===i?void 0:i.codec;this.incompatibleDetected=this.incompatibleDetected||p&&!x.includes(p),this.incompatibleDetected&&this.console.warn("configure your camera to output aac, mp3, or mp2 audio. incompatible audio codec detected",p);const h=await this.mixinDevice.getVideoStream(l),g=await m.convertMediaObjectToBuffer(h,t.ScryptedMimeTypes.FFmpegInput),S=JSON.parse(g.toString()),{audioConfig:M,pcmAudio:P,reencodeAudio:O,legacyAudio:D}=this.getAudioConfig(),w=!M||M===b,I=this.incompatibleDetected&&w;let C;this.audioDisabled=!1,u.noAudio||I?(C=["-an"],this.audioDisabled=!0):C=P?["-an"]:O?["-bsf:a","aac_adtstoasc","-acodec","libfdk_aac","-profile:a","aac_low","-flags","+global_header","-ar","8k","-b:a","100k","-ac","1"]:["-acodec","copy",...D||this.legacyDetected?[]:["-bsf:a","aac_adtstoasc"]];const A=["-vcodec","copy"],k={console:this.console,parsers:{mp4:(0,c.createFragmentedMp4Parser)({vcodec:A,acodec:C}),mpegts:(0,c.createMpegTsParser)({vcodec:A,acodec:C})}};u.noAudio||I||!P||(k.parsers.s16le=(0,c.createPCMParser)()),this.parsers=k.parsers,S.inputArguments.unshift("-fflags","+genpts");const _=await(0,s.startRebroadcastSession)(S,k);if(this.parserSession=_,null!==(n=S.mediaStreamOptions)&&void 0!==n&&n.refreshAt){let e,r=S.mediaStreamOptions;const i=async()=>{if(!_.isActive)return;const e=await this.mixinDevice.getVideoStream(r),i=await m.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(i.toString());r=o.mediaStreamOptions,n(o)},n=t=>{const r=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),e=setTimeout(i,r)};n(S),_.events.on("killed",(()=>clearTimeout(e)))}let T;const E=()=>{clearTimeout(T),T=setTimeout((()=>{this.console.error("watchdog for mp4 parser timed out... killing ffmpeg session"),_.kill()}),6e4)};_.events.on("mp4-data",E),_.events.once("killed",(()=>{this.parserSessionPromise=void 0,_.events.removeListener("mp4-data",E),clearTimeout(T)})),E(),_.inputAudioCodec?x.includes(_.inputAudioCodec)?"aac"!==(null===(o=_.inputAudioCodec)||void 0===o?void 0:o.toLowerCase())&&(this.console.error("Detected audio codec was not AAC.",_.inputAudioCodec),D||(f.a(`${this.mixin.name} is using ${_.inputAudioCodec} audio. Enable MP2/MP3 Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`),this.legacyDetected=!0)):(this.console.error("Detected audio codec is not mp4/mpegts compatible.",_.inputAudioCodec),w&&!u.noAudio&&(f.a(`${this.mixin.name} is using the ${_.inputAudioCodec} audio codec and has had its audio disabled. Select Disable Audio on your Camera or select Reencode Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`),this.incompatibleDetected=!0)):this.console.warn("no audio detected."),"h264"!==_.inputVideoCodec&&this.console.error("video codec is not h264. If there are errors, try changing your camera's encoder output.");for(const e of["mpegts","mp4","s16le"]){const t=e+"-data";let r=0;_.events.on(t,(i=>{const n=this.prebuffers[e],o=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),n.push({time:o,chunk:i});n.length&&n[0].time<o-d;)n.shift(),r++;r>1e3&&(this.prebuffers[e]=n.slice(),r=0),this.events.emit(t,i)}))}return _}async getVideoStream(e){var t,r;this.ensurePrebufferSession();const i=await this.parserSessionPromise,n="false"!==this.storage.getItem(S),o=(null==e?void 0:e.prebuffer)||(n?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);if(!(null!=e&&e.prebuffer||n)){return m.createFFmpegMediaObject(i.ffmpegInputs.mpegts)}this.console.log("prebuffer request started",this.streamId);const a=async e=>{const t=e+"-data",r=this.prebuffers[e],{server:n,port:a}=await(0,s.createRebroadcaster)({connect:(e,s)=>{n.close();const a=Date.now(),c=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection."),d())},d=()=>{s(),this.console.log("prebuffer request ended"),this.events.removeListener(t,c),i.events.removeListener("killed",d)};this.events.on(t,c),i.events.once("killed",d);for(const e of r)e.time<a-o||c(e.chunk);return d}});return setTimeout((()=>n.close()),3e4),a},c=(null==e?void 0:e.container)||"mpegts",d=i.ffmpegInputs[c].mediaStreamOptions?Object.assign({},i.ffmpegInputs[c].mediaStreamOptions):{};d.prebuffer=o;const{audioConfig:u,pcmAudio:l,reencodeAudio:p}=this.getAudioConfig();this.audioDisabled?d.audio=null:d.audio=p?{codec:"aac",encoder:"libfdk_aac",profile:"aac_eld"}:{codec:null==i?void 0:i.inputAudioCodec},d.video&&null!==(t=i.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(r=i.inputVideoResolution)&&void 0!==r&&r[3]&&Object.assign(d.video,{width:parseInt(i.inputVideoResolution[2]),height:parseInt(i.inputVideoResolution[3])});const f=Date.now();let h=0;const g=this.prebuffers[c];for(const e of g)if(!(e.time<f-o))for(const t of e.chunk.chunks)h+=t.length;const v=Math.max(5e5,h).toString(),y=`tcp://127.0.0.1:${await a(c)}`,b={url:y,container:c,inputArguments:["-analyzeduration","0","-probesize",v,"-f",c,"-i",y],mediaStreamOptions:d};l&&b.inputArguments.push("-analyzeduration","0","-probesize",v,"-f","s16le","-i",`tcp://127.0.0.1:${await a("s16le")}`);return m.createFFmpegMediaObject(b)}}class PrebufferMixin extends o.SettingsMixinDeviceBase{constructor(e,t,r,i){super(e,r,{providerNativeId:i,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),p(this,"released",!1),p(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=null==e?void 0:e.id;let r=this.sessions.get(t);return r?(r.ensurePrebufferSession(),await r.parserSessionPromise,r=this.sessions.get(t),r?r.getVideoStream(e):this.mixinDevice.getVideoStream(e)):this.mixinDevice.getVideoStream(e)}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),r=this.getEnabledMediaStreamOptions(e),i=r?r.map((e=>e.id)):[void 0];let o=0;const s=i.length;for(const t of i){let r=this.sessions.get(t);if(!r){var a;const i=null==e?void 0:e.find((e=>e.id===t));null!=i&&i.prebuffer&&f.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const c=null==i?void 0:i.name;r=new PrebufferSession(this,c,t),this.sessions.set(t,r),t===(null==e||null===(a=e[0])||void 0===a?void 0:a.id)&&this.sessions.set(void 0,r),(async()=>{for(;this.sessions.get(t)===r&&!this.released;){r.ensurePrebufferSession();try{const e=await r.parserSessionPromise;o++,this.online=o==s,await(0,n.once)(e.events,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{o--,this.online=o==s}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}g.onMixinEvent(this.id,this.mixinProviderNativeId,t.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),r=this.getEnabledMediaStreamOptions(t);r&&(null==t?void 0:t.length)>1&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:r.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:y,value:this.storage.getItem(y)||v.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:S,value:("false"!==this.storage.getItem(S)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const r=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of r.values()){var i;null==e||null===(i=e.parserSessionPromise)||void 0===i||i.then((e=>e.kill()))}this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(e&&e.length){try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}return[e[0]]}}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const r=parseInt(this.storage.getItem(y))||v;if(t)for(const e of t)e.prebuffer=r;else e.push({prebuffer:r});return e}release(){this.console.log("prebuffer releasing if started"),this.released=!0;for(const t of this.sessions.values()){var e;t&&(t.clearPrebuffers(),null===(e=t.parserSessionPromise)||void 0===e||e.then((e=>{this.console.log("prebuffer released"),e.kill(),t.clearPrebuffers()})))}}}class PrebufferProvider extends d.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(h.getSystemState())){var t;const r=h.getDeviceById(e);null!==(t=r.mixins)&&void 0!==t&&t.includes(this.id)&&r.getVideoStreamOptions()}const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout((()=>g.requestRestart()),r)}async canMixin(e,r){return r.includes(t.ScryptedInterface.VideoCamera)?[t.ScryptedInterface.VideoCamera,t.ScryptedInterface.Settings,t.ScryptedInterface.Online]:null}async getMixin(e,t,r){return this.setHasEnabledMixin(r.id),new PrebufferMixin(e,t,r,this.nativeId)}async releaseMixin(e,t){t.online=!0,t.release()}}var w=new PrebufferProvider;e.default=w})();var n=exports="undefined"==typeof exports?{}:exports;for(var o in i)n[o]=i[o];i.__esModule&&Object.defineProperty(n,"__esModule",{value:!0})})();
1
+ (()=>{var e={454:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=n(t);if(i&&i.has(e))return i.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(n=function(e){return e?i:t})(e)}const{systemManager:o}=r.default,s="v4";class AutoenableMixinProvider extends r.ScryptedDeviceBase{constructor(e){var t,i,n;super(e),n={},(i="hasEnabledMixin")in(t=this)?Object.defineProperty(t,i,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[i]=n;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=o.getComponent("plugins"),o.listen((async(e,t,i)=>{t.eventInterface!==r.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)}));for(const e of Object.keys(o.getSystemState())){const t=o.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id]===s)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const i=(e.mixins||[]).slice();i.push(this.id);const r=await this.pluginsComponent;await r.setMixins(e.id,i),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==s&&(this.hasEnabledMixin[e]=s,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=AutoenableMixinProvider},201:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createRebroadcaster=async function(e){let t=0;const i=(0,r.createServer)((i=>{let r=!0;const n=()=>{i.removeAllListeners(),i.destroy();const e=o;o=void 0,null==e||e()};let o=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}),n);i.once("close",(()=>{t--,n()})),t++})),n=await(0,o.listenZero)(i);return{server:i,port:n,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,o,a=!0;const d=new s.EventEmitter;let p,h,g,v,y;d.on("error",(e=>i.error("rebroadcast error",e)));const S=new Promise(((e,t)=>{v=e,y=t}));function b(){var e;a&&(d.emit("killed"),d.emit("error",new Error("killed"))),a=!1,null==w||w.kill(),null==w||w.kill("SIGKILL"),null===(e=y)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(o)}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 D=e.inputArguments.slice();o=setTimeout(b,3e4);const O=["pipe","pipe","pipe"];let x=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];D.push(...i.outputArguments,`pipe:${x}`),O.push("pipe"),x++}D.unshift("-hide_banner"),(0,c.safePrintFFmpegArguments)(i,D);const w=n.default.spawn(await u.getFFmpegPath(),D,{stdio:O});return(0,c.ffmpegLogInitialOutput)(i,w),w.on("exit",b),Object.keys(t.parsers).forEach((async(e,r)=>{const n=w.stdio[3+r],o=t.parsers[e];try{for await(const t of o.parse(n,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,c;null===(c=v)||void 0===c||c(void 0),d.emit(e,t),P()}}catch(e){i.error("rebroadcast parse error",e),b()}})),f(w).then((e=>p=e)),m(w).then((e=>h=e)),l(w).then((e=>g=e)),await S,v=void 0,y=void 0,clearTimeout(o),{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 d.on(e,t),this},once(e,t){return d.once(e,t),this},removeListener(e,t){return d.removeListener(e,t),this}}};var r=i(808),n=d(i(81)),o=i(769),s=i(361),a=d(i(510)),c=i(833);function d(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 n=r.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);o&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(o))};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function p(e,t){return new Promise((i=>{const r=n=>{const o=n.toString(),s=o.indexOf(`${t}: `);if(-1!==s){const n=o.substring(s+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(n.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function 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,o.once)(e,"listening"),e.address().port},t.listenZero=s,t.listenZeroSingleClient=async function(){const e=new n.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,n=(r=i(808))&&r.__esModule?r:{default:r},o=i(361);async function s(e){return e.listen(0),await(0,o.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 n=()=>{const r=e.read(t);r&&(s(),i(r))},o=()=>{s(),r(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",n),e.removeListener("end",o)};e.on("readable",n),e.on("end",o)}))}},567:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=n(t);if(i&&i.has(e))return i.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=o?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(n=function(e){return e?i:t})(e)}const{deviceManager:o}=r.default;class SettingsMixinDeviceBase extends r.MixinDeviceBase{constructor(e,t,i){super(e,i.mixinDeviceInterfaces,t,i.providerNativeId,i.mixinStorageSuffix),this.settingsGroup=i.group,this.settingsGroupKey=i.groupKey,process.nextTick((()=>o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)))}async getSettings(){const e=this.mixinDeviceInterfaces.includes(r.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,t=this.getMixinSettings(),i=[];try{const t=await e||[];i.push(...t)}catch(e){const t=this.name;i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:`${t} Extension settings failed to load.`,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=o.getDeviceState(this.mixinProviderNativeId).name;i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:`${t} Extension settings failed to load.`,readonly:!0})}return i}async putSetting(e,t){const i=this.settingsGroupKey+":";if(null==e||!e.startsWith(i))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(i.length),t),o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.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,n;for await(const e of t)i?r||(r=e):i=e,yield{startStream:n,chunks:[e.header,e.data],type:e.type},i&&r&&!n&&(n=Buffer.concat([i.header,i.data,r.header,r.data]))},findSyncFrame:o}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,(e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")})),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const n=i.chunks[r];let o=0;for(;o+188<n.length;){const i=n.subarray(o,o+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);o+=188}}}return e}}},t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:o}},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 o=i.computeLength(t,r);for(;;){const i=await(0,n.readLength)(e,o);yield{chunks:[i],width:t,height:r}}},findSyncFrame:o}},t.parseFragmentedMP4=a;var r=i(361),n=i(701);function o(e){return e}function s(e,t){return async function*(i){let n=[],o=0;for(;;){const s=i.read();if(!s){await(0,r.once)(i,"readable");continue}if(n.push(s),o+=s.length,o<e)continue;const a=Buffer.concat(n);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);n=[u],o=u.length,yield{chunks:[d]}}}}async function*a(e){for(;;){const t=await(0,n.readLength)(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),o=await(0,n.readLength)(e,i);yield{header:t,length:i,type:r,data:o}}}const 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]},n=function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(i(393),t);const o=i(393);class ScryptedDeviceBase extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=ScryptedDeviceBase;class MixinDeviceBase extends o.DeviceBase{constructor(e,t,i,r,n){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=r,this._mixinStorageSuffix=n,this._listeners=new Set,this._deviceState=i}get storage(){if(!this._storage){const e=this._mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=MixinDeviceBase,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var i of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(ScryptedDeviceBase.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(MixinDeviceBase.prototype,i,{set:t(i),get:e(i)})}();let s={};try{s=Object.assign(s,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=s},393:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ThermostatMode=t.TemperatureUnit=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterfaceDescriptors=t.ScryptedInterface=t.ScryptedDeviceType=t.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.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(i||(t.ScryptedInterfaceProperty=i={}));let r,n,o,s,a,c,d,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},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"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=r,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(r||(t.ScryptedDeviceType=r={})),t.HumidityMode=n,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(n||(t.HumidityMode=n={})),t.FanMode=o,function(e){e.Auto="Auto",e.Manual="Manual"}(o||(t.FanMode=o={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=c,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(c||(t.LockState=c={})),t.MediaPlayerState=d,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(d||(t.MediaPlayerState=d={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.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.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 n,o;function s(e){const n=o=>{const s=o.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",n),void t.stderr.removeListener("data",n);e(s)};return n}null===(n=t.stdout)||void 0===n||n.on("data",s(e.log)),null===(o=t.stderr)||void 0===o||o.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))},t.probeVideoCamera=async function(e){let t;try{t=await e.getVideoStreamOptions()||[]}catch(e){}const i=t&&t.length&&null===t[0].audio;return{options:t,noAudio:i}},t.safePrintFFmpegArguments=function(e,t){const i=[];for(const e of t)try{const t=new URL(e);t.password?(t.password="REDACTED",i.push(t.toString())):i.push(e)}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 n=t[r];if(void 0!==n)return n.exports;var o=t[r]={exports:{}};return e[r](o,o.exports,i),o.exports}var r={};(()=>{"use strict";var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=u(t);if(i&&i.has(e))return i.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var s=n?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(r,o,s):r[o]=e[o]}r.default=e,i&&i.set(e,r);return r}(i(510)),n=i(361),o=i(567),s=i(201),a=i(833),c=i(129),d=i(454);function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(u=function(e){return e?i:t})(e)}function l(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:p,log:m,systemManager:f,deviceManager:h}=t.default,g=1e4,v="prebufferDuration",y="sendKeyframe",S="Default",b="MP2/MP3 Audio",M="Other Audio",P="PCM or G.711 Audio",D=`${P} (Copy, Unstable)`,O=["aac","mp3","mp2","AAC","MP3","MP2","opus","OPUS","",void 0,null],x=["mpegts","mp4","s16le"];class PrebufferSession{constructor(e,t,i,r){l(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),l(this,"detectedIdrInterval",0),l(this,"prevIdr",0),l(this,"incompatibleDetected",!1),l(this,"legacyDetected",!1),l(this,"audioDisabled",!1),l(this,"activeClients",0),l(this,"AUDIO_CONFIGURATION","audioConfiguration-"+this.streamId),this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice}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(){const e=this.storage.getItem(this.AUDIO_CONFIGURATION)||"",t=-1!==e.indexOf(P),i=-1!==e.indexOf(b),r=-1!==e.indexOf(M);return{audioConfig:e,pcmAudio:t,legacyAudio:i,reencodeAudio:r}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;for(const e of this.prebuffers.mp4){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const n=Date.now()-r,o=Math.round(i/n*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,c,d;(e.push({title:"Audio Codec Transcoding",group:s,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Reencode.",type:"string",key:this.AUDIO_CONFIGURATION,value:this.storage.getItem(this.AUDIO_CONFIGURATION)||S,choices:[S,"AAC or No Audio (Copy)","MP2/MP3 Audio (Copy)","Other Audio (Transcode)",D]}),t)?e.push({key:"detectedResolution",group:s,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==t||null===(a=t.inputVideoResolution)||void 0===a?void 0:a[0])||"unknown"} @ ${o||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:s,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==t||null===(c=t.inputVideoCodec)||void 0===c?void 0:c.toString())||"unknown")+"/"+((null==t||null===(d=t.inputAudioCodec)||void 0===d?void 0:d.toString())||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:s,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}):e.push({title:"Status",group:s,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}async startPrebufferSession(){var e,i,r,n;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const o=parseInt(this.storage.getItem(v))||g,d=await(0,a.probeVideoCamera)(this.mixinDevice);let u;d.options&&(u=d.options.find((e=>e.id===this.streamId)));const l=null===(e=u)||void 0===e||null===(i=e.audio)||void 0===i?void 0:i.codec;this.incompatibleDetected=this.incompatibleDetected||l&&!O.includes(l),this.incompatibleDetected&&this.console.warn("configure your camera to output aac, mp3, mp2, or opus audio. incompatible audio codec detected",l);const f=await this.mixinDevice.getVideoStream(u),h=await p.convertMediaObjectToBuffer(f,t.ScryptedMimeTypes.FFmpegInput),y=JSON.parse(h.toString()),{audioConfig:b,pcmAudio:M,reencodeAudio:P,legacyAudio:D}=this.getAudioConfig(),w=!b||b===S,I=this.incompatibleDetected&&w;let C;this.audioDisabled=!1,d.noAudio||I?(C=["-an"],this.audioDisabled=!0):C=M?["-an"]:P?["-bsf:a","aac_adtstoasc","-acodec","libfdk_aac","-profile:a","aac_low","-flags","+global_header","-ar","8k","-b:a","100k","-ac","1"]:["-acodec","copy",...D||this.legacyDetected?[]:["-bsf:a","aac_adtstoasc"]];const A=["-vcodec","copy"],k={console:this.console,timeout:6e4,parsers:{mp4:(0,c.createFragmentedMp4Parser)({vcodec:A,acodec:C}),mpegts:(0,c.createMpegTsParser)({vcodec:A,acodec:C})}};d.noAudio||I||!M||(k.parsers.s16le=(0,c.createPCMParser)()),this.parsers=k.parsers,y.inputArguments.unshift("-fflags","+genpts");const _=await(0,s.startParserSession)(y,k);if(this.parserSession=_,null!==(r=y.mediaStreamOptions)&&void 0!==r&&r.refreshAt){let e,i=y.mediaStreamOptions;const r=async()=>{if(!_.isActive)return;const e=await this.mixinDevice.getVideoStream(i),r=await p.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(r.toString());i=o.mediaStreamOptions,n(o)},n=t=>{const i=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),e=setTimeout(r,i)};n(y),_.once("killed",(()=>clearTimeout(e)))}_.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===_&&(this.parserSession=void 0)})),_.inputAudioCodec?O.includes(_.inputAudioCodec)?"aac"!==(null===(n=_.inputAudioCodec)||void 0===n?void 0:n.toLowerCase())&&(this.console.error("Detected audio codec was not AAC.",_.inputAudioCodec),D||(m.a(`${this.mixin.name} is using ${_.inputAudioCodec} audio. Enable MP2/MP3 Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`),this.legacyDetected=!0)):(this.console.error("Detected audio codec is not mp4/mpegts compatible.",_.inputAudioCodec),w&&!d.noAudio&&(m.a(`${this.mixin.name} is using the ${_.inputAudioCodec} audio codec and has had its audio disabled. Select Disable Audio on your Camera or select Reencode Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`),this.incompatibleDetected=!0)):this.console.warn("no audio detected."),"h264"!==_.inputVideoCodec&&this.console.error("video codec is not h264. If there are errors, try changing your camera's encoder output.");for(const e of x){let t=0;_.on(e,(i=>{const r=this.prebuffers[e],n=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=n-this.prevIdr),this.prevIdr=n),r.push({time:n,chunk:i});r.length&&r[0].time<n-o;)r.shift(),t++;t>1e3&&(this.prebuffers[e]=r.slice(),t=0)}))}return _}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,n="false"!==this.storage.getItem(y),o=(null==e?void 0:e.prebuffer)||(n?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);this.console.log(this.streamName,"prebuffer request started");const a=async e=>{const t=this.prebuffers[e],{server:i,port:n}=await(0,s.createRebroadcaster)({connect:(n,s)=>{this.activeClients++,this.printActiveClients(),i.close();const a=Date.now(),c=e=>{n(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{s(),this.console.log(this.streamName,"prebuffer request ended"),r.removeListener(e,c),r.removeListener("killed",d)};r.on(e,c),r.once("killed",d);for(const e of t)e.time<a-o||c(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(r),d()}}});return setTimeout((()=>i.close()),3e4),n},c=x.find((t=>t===(null==e?void 0:e.container)))||"mpegts",d=Object.assign({},r.mediaStreamOptions);d.prebuffer=o;const{audioConfig:u,pcmAudio:l,reencodeAudio:m}=this.getAudioConfig();this.audioDisabled?d.audio=null:d.audio=m?{codec:"aac",encoder:"libfdk_aac",profile:"aac_eld"}:{codec:null==r?void 0:r.inputAudioCodec},d.video&&null!==(t=r.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(i=r.inputVideoResolution)&&void 0!==i&&i[3]&&Object.assign(d.video,{width:parseInt(r.inputVideoResolution[2]),height:parseInt(r.inputVideoResolution[3])});const f=Date.now();let h=0;const g=this.prebuffers[c];for(const e of g)if(!(e.time<f-o))for(const t of e.chunk.chunks)h+=t.length;const v=Math.max(5e5,h).toString(),S=`tcp://127.0.0.1:${await a(c)}`,b={url:S,container:c,inputArguments:["-analyzeduration","0","-probesize",v,"-f",c,"-i",S],mediaStreamOptions:d};l&&b.inputArguments.push("-analyzeduration","0","-probesize",v,"-f","s16le","-i",`tcp://127.0.0.1:${await a("s16le")}`);return p.createFFmpegMediaObject(b)}}class PrebufferMixin extends o.SettingsMixinDeviceBase{constructor(e,t,i,r){super(e,i,{providerNativeId:r,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),l(this,"released",!1),l(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=null==e?void 0:e.id;let i=this.sessions.get(t);return!i||null!=e&&e.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(e),r=i?i.map((e=>e.id)):[void 0],o=(null==e?void 0:e.map((e=>e.id)))||[void 0],s=this.mixinDeviceInterfaces.includes(t.ScryptedInterface.Battery);let a=0;const c=o.length;for(const t of o){let i=this.sessions.get(t);if(!i){var d;const o=null==e?void 0:e.find((e=>e.id===t));null!=o&&o.prebuffer&&m.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const u=null==o?void 0:o.name,l=!r.includes(t);if(i=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,i),t===(null==e||null===(d=e[0])||void 0===d?void 0:d.id)&&this.sessions.set(void 0,i),s){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(l){this.console.log("stream",u,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(t)===i&&!this.released;){i.ensurePrebufferSession();try{const e=await i.parserSessionPromise;a++,this.online=a==c,await(0,n.once)(e,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{a--,this.online=a==c}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}h.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:v,value:this.storage.getItem(v)||g.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:y,value:("false"!==this.storage.getItem(y)).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(v))||g;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(f.getSystemState())){var t;const i=f.getDeviceById(e);null!==(t=i.mixins)&&void 0!==t&&t.includes(this.id)&&i.getVideoStreamOptions()}const i=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(i/1e3/60)} minutes`),setTimeout((()=>h.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 w=new PrebufferProvider;e.default=w})();var n=exports="undefined"==typeof exports?{}:exports;for(var o in r)n[o]=r[o];r.__esModule&&Object.defineProperty(n,"__esModule",{value:!0})})();
2
2
  //# sourceMappingURL=main.nodejs.js.map
package/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.1.124",
3
+ "version": "0.1.128",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
- import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput } from '@scrypted/sdk';
2
+ import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput, RequestMediaStreamOptions } from '@scrypted/sdk';
3
3
  import sdk from '@scrypted/sdk';
4
- import EventEmitter, { once } from 'events';
4
+ import { once } from 'events';
5
5
  import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
6
- import { createRebroadcaster, FFMpegRebroadcastOptions, FFMpegRebroadcastSession, startRebroadcastSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
6
+ import { createRebroadcaster, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
7
7
  import { probeVideoCamera } from '@scrypted/common/src/media-helpers';
8
8
  import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, createPCMParser, StreamParser } from '@scrypted/common/src/stream-parser';
9
9
  import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
@@ -37,11 +37,12 @@ interface Prebuffers {
37
37
  }
38
38
 
39
39
  type PrebufferParsers = "mpegts" | "mp4" | "s16le";
40
+ const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 's16le'];
40
41
 
41
42
  class PrebufferSession {
42
43
 
43
- parserSessionPromise: Promise<FFMpegRebroadcastSession<PrebufferParsers>>;
44
- parserSession: FFMpegRebroadcastSession<PrebufferParsers>;
44
+ parserSessionPromise: Promise<ParserSession<PrebufferParsers>>;
45
+ parserSession: ParserSession<PrebufferParsers>;
45
46
  prebuffers: Prebuffers = {
46
47
  mp4: [],
47
48
  mpegts: [],
@@ -49,7 +50,6 @@ class PrebufferSession {
49
50
  };
50
51
  parsers: { [container: string]: StreamParser };
51
52
 
52
- events = new EventEmitter();
53
53
  detectedIdrInterval = 0;
54
54
  prevIdr = 0;
55
55
  incompatibleDetected = false;
@@ -60,9 +60,12 @@ class PrebufferSession {
60
60
  console: Console;
61
61
  storage: Storage;
62
62
 
63
+ activeClients = 0;
64
+ inactivityTimeout: NodeJS.Timeout;
65
+
63
66
  AUDIO_CONFIGURATION = AUDIO_CONFIGURATION_TEMPLATE + '-' + this.streamId;
64
67
 
65
- constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string) {
68
+ constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string, public stopInactive: boolean) {
66
69
  this.storage = mixin.storage;
67
70
  this.console = mixin.console;
68
71
  this.mixinDevice = mixin.mixinDevice;
@@ -77,7 +80,7 @@ class PrebufferSession {
77
80
  ensurePrebufferSession() {
78
81
  if (this.parserSessionPromise || this.mixin.released)
79
82
  return;
80
- this.console.log('prebuffer session started', this.streamId);
83
+ this.console.log(this.streamName, 'prebuffer session started');
81
84
  this.parserSessionPromise = this.startPrebufferSession();
82
85
  this.parserSessionPromise.catch(() => this.parserSessionPromise = undefined);
83
86
  }
@@ -124,7 +127,7 @@ class PrebufferSession {
124
127
  {
125
128
  title: 'Audio Codec Transcoding',
126
129
  group,
127
- description: 'Configuring your camera to output AAC, MP3, or MP2 is recommended. PCM/G711 cameras should set this to Reencode.',
130
+ description: 'Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Reencode.',
128
131
  type: 'string',
129
132
  key: this.AUDIO_CONFIGURATION,
130
133
  value: this.storage.getItem(this.AUDIO_CONFIGURATION) || DEFAULT_AUDIO,
@@ -136,38 +139,49 @@ class PrebufferSession {
136
139
  PCM_AUDIO_DESCRIPTION,
137
140
  ],
138
141
  },
139
- {
140
- key: 'detectedResolution',
141
- group,
142
- title: 'Detected Resolution and Bitrate',
143
- readonly: true,
144
- value: `${session?.inputVideoResolution?.[0] || "unknown"} @ ${bitrate || "unknown"} Kb/s`,
145
- description: 'Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended.',
146
- },
147
- {
148
- key: 'detectedCodec',
149
- group,
150
- title: 'Detected Video/Audio Codecs',
151
- readonly: true,
152
- value: (session?.inputVideoCodec?.toString() || 'unknown') + '/' + (session?.inputAudioCodec?.toString() || 'unknown'),
153
- description: 'Configuring your camera to H264 video and AAC/MP3/MP2 audio is recommended.'
154
- },
155
- {
156
- key: 'detectedKeyframe',
157
- group,
158
- title: 'Detected Keyframe Interval',
159
- description: "Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",
160
- readonly: true,
161
- value: ((this.detectedIdrInterval || 0) / 1000).toString() || 'none',
162
- },
163
- {
164
- group,
165
- key: 'rebroadcastUrl',
166
- title: 'Rebroadcast Url',
167
- readonly: true,
168
- value: this.parserSession?.ffmpegInputs?.mpegts.url,
169
- }
170
142
  );
143
+
144
+ if (session) {
145
+ settings.push(
146
+ {
147
+ key: 'detectedResolution',
148
+ group,
149
+ title: 'Detected Resolution and Bitrate',
150
+ readonly: true,
151
+ value: `${session?.inputVideoResolution?.[0] || "unknown"} @ ${bitrate || "unknown"} Kb/s`,
152
+ description: 'Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended.',
153
+ },
154
+ {
155
+ key: 'detectedCodec',
156
+ group,
157
+ title: 'Detected Video/Audio Codecs',
158
+ readonly: true,
159
+ value: (session?.inputVideoCodec?.toString() || 'unknown') + '/' + (session?.inputAudioCodec?.toString() || 'unknown'),
160
+ description: 'Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended.'
161
+ },
162
+ {
163
+ key: 'detectedKeyframe',
164
+ group,
165
+ title: 'Detected Keyframe Interval',
166
+ description: "Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",
167
+ readonly: true,
168
+ value: ((this.detectedIdrInterval || 0) / 1000).toString() || 'none',
169
+ },
170
+ );
171
+ }
172
+ else {
173
+ settings.push(
174
+ {
175
+ title: 'Status',
176
+ group,
177
+ key: 'status',
178
+ description: 'Rebroadcast is currently idle and will be started automatically on demand.',
179
+ value: 'Idle',
180
+ readonly: true,
181
+ },
182
+ )
183
+ }
184
+
171
185
  return settings;
172
186
  }
173
187
 
@@ -177,15 +191,16 @@ class PrebufferSession {
177
191
  this.prebuffers.s16le = [];
178
192
  const prebufferDurationMs = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
179
193
 
194
+ // todo: there's a bug here where probe's noAudio check looks at the first media stream option entry
180
195
  const probe = await probeVideoCamera(this.mixinDevice);
181
196
  let mso: MediaStreamOptions;
182
197
  if (probe.options) {
183
198
  mso = probe.options.find(mso => mso.id === this.streamId);
184
199
  }
185
- const probeAudioCodec = probe?.options?.[0]?.audio?.codec;
200
+ const probeAudioCodec = mso?.audio?.codec;
186
201
  this.incompatibleDetected = this.incompatibleDetected || (probeAudioCodec && !compatibleAudio.includes(probeAudioCodec));
187
202
  if (this.incompatibleDetected)
188
- this.console.warn('configure your camera to output aac, mp3, or mp2 audio. incompatible audio codec detected', probeAudioCodec);
203
+ this.console.warn('configure your camera to output aac, mp3, mp2, or opus audio. incompatible audio codec detected', probeAudioCodec);
189
204
 
190
205
  const mo = await this.mixinDevice.getVideoStream(mso);
191
206
  const moBuffer = await mediaManager.convertMediaObjectToBuffer(mo, ScryptedMimeTypes.FFmpegInput);
@@ -231,8 +246,9 @@ class PrebufferSession {
231
246
  'copy',
232
247
  ];
233
248
 
234
- const rbo: FFMpegRebroadcastOptions<PrebufferParsers> = {
249
+ const rbo: ParserOptions<PrebufferParsers> = {
235
250
  console: this.console,
251
+ timeout: 60000,
236
252
  parsers: {
237
253
  mp4: createFragmentedMp4Parser({
238
254
  vcodec,
@@ -256,7 +272,7 @@ class PrebufferSession {
256
272
  // create missing pts from dts so mpegts and mp4 muxing does not fail
257
273
  ffmpegInput.inputArguments.unshift('-fflags', '+genpts');
258
274
 
259
- const session = await startRebroadcastSession(ffmpegInput, rbo);
275
+ const session = await startParserSession(ffmpegInput, rbo);
260
276
  this.parserSession = session;
261
277
 
262
278
  // cloud streams need a periodic token refresh.
@@ -282,27 +298,15 @@ class PrebufferSession {
282
298
  }
283
299
 
284
300
  scheduleRefresh(ffmpegInput);
285
- session.events.on('killed', () => clearTimeout(refreshTimeout));
286
- }
287
-
288
- let watchdog: NodeJS.Timeout;
289
- const restartWatchdog = () => {
290
- clearTimeout(watchdog);
291
- watchdog = setTimeout(() => {
292
- this.console.error('watchdog for mp4 parser timed out... killing ffmpeg session');
293
- session.kill();
294
- }, 60000);
301
+ session.once('killed', () => clearTimeout(refreshTimeout));
295
302
  }
296
- session.events.on('mp4-data', restartWatchdog);
297
303
 
298
- session.events.once('killed', () => {
304
+ session.once('killed', () => {
299
305
  this.parserSessionPromise = undefined;
300
- session.events.removeListener('mp4-data', restartWatchdog);
301
- clearTimeout(watchdog);
306
+ if (this.parserSession === session)
307
+ this.parserSession = undefined;
302
308
  });
303
309
 
304
- restartWatchdog();
305
-
306
310
  if (!session.inputAudioCodec) {
307
311
  this.console.warn('no audio detected.');
308
312
  }
@@ -321,7 +325,7 @@ class PrebufferSession {
321
325
  if (!legacyAudio) {
322
326
  log.a(`${this.mixin.name} is using ${session.inputAudioCodec} audio. Enable MP2/MP3 Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`);
323
327
  this.legacyDetected = true;
324
- // this will probably crash ffmpeg due to mp2/mp3 not supporting the aac bit stream filters,
328
+ // this will probably crash ffmpeg due to mp2/mp3/opus not supporting the aac bit stream filters,
325
329
  // and then it will automatically restart with legacy handling.
326
330
  }
327
331
  }
@@ -331,14 +335,15 @@ class PrebufferSession {
331
335
  }
332
336
 
333
337
  // s16le will be a no-op if there's no pcm, no harm.
334
- for (const container of ['mpegts', 'mp4', 's16le']) {
335
- const eventName = container + '-data';
338
+ for (const container of PrebufferParserValues) {
336
339
  let shifts = 0;
337
340
 
338
- session.events.on(eventName, (chunk: StreamChunk) => {
341
+ session.on(container, (chunk: StreamChunk) => {
339
342
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
340
343
  const now = Date.now();
341
344
 
345
+ // this is only valid for mp4, so its no op for everything else
346
+ // used to detect idr interval.
342
347
  if (chunk.type === 'mdat') {
343
348
  if (this.prevIdr)
344
349
  this.detectedIdrInterval = now - this.prevIdr;
@@ -359,14 +364,32 @@ class PrebufferSession {
359
364
  this.prebuffers[container] = prebufferContainer.slice();
360
365
  shifts = 0;
361
366
  }
362
-
363
- this.events.emit(eventName, chunk);
364
367
  });
365
368
  }
366
369
 
367
370
  return session;
368
371
  }
369
372
 
373
+ printActiveClients() {
374
+ this.console.log(this.streamName, 'active rebroadcast clients:', this.activeClients);
375
+ }
376
+
377
+ inactivityCheck(session: ParserSession<PrebufferParsers>) {
378
+ this.printActiveClients();
379
+ if (!this.stopInactive)
380
+ return;
381
+ if (this.activeClients)
382
+ return;
383
+
384
+ clearTimeout(this.inactivityTimeout)
385
+ this.inactivityTimeout = setTimeout(() => {
386
+ if (this.activeClients)
387
+ return;
388
+ this.console.log(this.streamName, 'terminating rebroadcast due to inactivity');
389
+ session.kill();
390
+ }, 30000);
391
+ }
392
+
370
393
  async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
371
394
  this.ensurePrebufferSession();
372
395
 
@@ -375,40 +398,36 @@ class PrebufferSession {
375
398
  const sendKeyframe = this.storage.getItem(SEND_KEYFRAME) !== 'false';
376
399
  const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? Math.max(4000, (this.detectedIdrInterval || 4000)) * 1.5 : 0);
377
400
 
378
- if (!options?.prebuffer && !sendKeyframe) {
379
- const mo = mediaManager.createFFmpegMediaObject(session.ffmpegInputs['mpegts']);
380
- return mo;
381
- }
382
-
383
- this.console.log('prebuffer request started', this.streamId);
401
+ this.console.log(this.streamName, 'prebuffer request started');
384
402
 
385
- const createContainerServer = async (container: string) => {
386
- const eventName = container + '-data';
403
+ const createContainerServer = async (container: PrebufferParsers) => {
387
404
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
388
405
 
389
406
  const { server, port } = await createRebroadcaster({
390
407
  connect: (writeData, destroy) => {
408
+ this.activeClients++;
409
+ this.printActiveClients();
410
+
391
411
  server.close();
392
412
  const now = Date.now();
393
413
 
394
-
395
414
  const safeWriteData = (chunk: StreamChunk) => {
396
415
  const buffered = writeData(chunk);
397
416
  if (buffered > 100000000) {
398
- this.console.log('more than 100MB has been buffered, did downstream die? killing connection.');
417
+ this.console.log('more than 100MB has been buffered, did downstream die? killing connection.', this.streamName);
399
418
  cleanup();
400
419
  }
401
420
  }
402
421
 
403
422
  const cleanup = () => {
404
423
  destroy();
405
- this.console.log('prebuffer request ended');
406
- this.events.removeListener(eventName, safeWriteData);
407
- session.events.removeListener('killed', cleanup);
424
+ this.console.log(this.streamName, 'prebuffer request ended');
425
+ session.removeListener(container, safeWriteData);
426
+ session.removeListener('killed', cleanup);
408
427
  }
409
428
 
410
- this.events.on(eventName, safeWriteData);
411
- session.events.once('killed', cleanup);
429
+ session.on(container, safeWriteData);
430
+ session.once('killed', cleanup);
412
431
 
413
432
  if (true) {
414
433
  for (const prebuffer of prebufferContainer) {
@@ -427,7 +446,11 @@ class PrebufferSession {
427
446
  }
428
447
  }
429
448
 
430
- return cleanup;
449
+ return () => {
450
+ this.activeClients--;
451
+ this.inactivityCheck(session);
452
+ cleanup();
453
+ };
431
454
  }
432
455
  })
433
456
 
@@ -436,11 +459,9 @@ class PrebufferSession {
436
459
  return port;
437
460
  }
438
461
 
439
- const container = options?.container || 'mpegts';
462
+ const container: PrebufferParsers = PrebufferParserValues.find(parser => parser === options?.container) || 'mpegts';
440
463
 
441
- const mediaStreamOptions = session.ffmpegInputs[container].mediaStreamOptions
442
- ? Object.assign({}, session.ffmpegInputs[container].mediaStreamOptions)
443
- : {};
464
+ const mediaStreamOptions: MediaStreamOptions = Object.assign({}, session.mediaStreamOptions);
444
465
 
445
466
  mediaStreamOptions.prebuffer = requestedPrebuffer;
446
467
 
@@ -528,12 +549,12 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
528
549
  setTimeout(() => this.ensurePrebufferSessions(), 5000);
529
550
  }
530
551
 
531
- async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
552
+ async getVideoStream(options?: RequestMediaStreamOptions): Promise<MediaObject> {
532
553
  await this.ensurePrebufferSessions();
533
554
 
534
555
  const id = options?.id;
535
556
  let session = this.sessions.get(id);
536
- if (!session)
557
+ if (!session || options?.directMediaStream)
537
558
  return this.mixinDevice.getVideoStream(options);
538
559
  session.ensurePrebufferSession();
539
560
  await session.parserSessionPromise;
@@ -546,7 +567,10 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
546
567
  async ensurePrebufferSessions() {
547
568
  const msos = await this.mixinDevice.getVideoStreamOptions();
548
569
  const enabled = this.getEnabledMediaStreamOptions(msos);
549
- const ids = enabled ? enabled.map(mso => mso.id) : [undefined];
570
+ const enabledIds = enabled ? enabled.map(mso => mso.id) : [undefined];
571
+ const ids = msos?.map(mso => mso.id) || [undefined];
572
+
573
+ const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
550
574
 
551
575
  let active = 0;
552
576
  const total = ids.length;
@@ -558,11 +582,23 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
558
582
  log.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`)
559
583
  }
560
584
  const name = mso?.name;
561
- session = new PrebufferSession(this, name, id);
585
+ const notEnabled = !enabledIds.includes(id)
586
+ const stopInactive = isBatteryPowered || notEnabled;
587
+ session = new PrebufferSession(this, name, id, stopInactive);
562
588
  this.sessions.set(id, session);
563
589
  if (id === msos?.[0]?.id)
564
590
  this.sessions.set(undefined, session);
565
591
 
592
+ if (isBatteryPowered) {
593
+ this.console.log('camera is battery powered, prebuffering and rebroadcasting will only work on demand.');
594
+ continue;
595
+ }
596
+
597
+ if (notEnabled) {
598
+ this.console.log('stream', name, 'will be rebroadcast on demand.');
599
+ continue;
600
+ }
601
+
566
602
  (async () => {
567
603
  while (this.sessions.get(id) === session && !this.released) {
568
604
  session.ensurePrebufferSession();
@@ -570,7 +606,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
570
606
  const ps = await session.parserSessionPromise;
571
607
  active++;
572
608
  this.online = active == total;
573
- await once(ps.events, 'killed');
609
+ await once(ps, 'killed');
574
610
  this.console.error('prebuffer session ended');
575
611
  }
576
612
  catch (e) {
@@ -596,7 +632,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
596
632
  try {
597
633
  const msos = await this.mixinDevice.getVideoStreamOptions();
598
634
  const enabledStreams = this.getEnabledMediaStreamOptions(msos);
599
- if (enabledStreams && msos?.length > 1) {
635
+ if (msos?.length > 0) {
600
636
  settings.push(
601
637
  {
602
638
  title: 'Prebuffered Streams',
@@ -664,7 +700,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
664
700
  }
665
701
 
666
702
  getEnabledMediaStreamOptions(msos?: MediaStreamOptions[]) {
667
- if (!msos || !msos.length)
703
+ if (!msos)
668
704
  return;
669
705
 
670
706
  try {
@@ -674,7 +710,9 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
674
710
  }
675
711
  catch (e) {
676
712
  }
677
- return [msos[0]];
713
+ // do not enable rebroadcast on cloud streams by default.
714
+ const firstNonCloudStream = msos.find(mso => mso.source !== 'cloud');
715
+ return firstNonCloudStream ? [firstNonCloudStream] : [];
678
716
  }
679
717
 
680
718
  async getVideoStreamOptions(): Promise<MediaStreamOptions[]> {
@@ -685,6 +723,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
685
723
 
686
724
  if (!enabledStreams) {
687
725
  ret.push({
726
+ id: 'default',
727
+ name: 'Default',
688
728
  prebuffer,
689
729
  });
690
730
  }