@scrypted/prebuffer-mixin 0.1.131 → 0.1.135
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +170 -66
package/dist/main.nodejs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{var e={454:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=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==O||O.kill(),null==O||O.kill("SIGKILL"),null===(e=y)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(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 x=["pipe","pipe","pipe"];let w=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];D.push(...i.outputArguments,`pipe:${w}`),x.push("pipe"),w++}D.unshift("-hide_banner"),(0,c.safePrintFFmpegArguments)(i,D);const O=n.default.spawn(await u.getFFmpegPath(),D,{stdio:x});return(0,c.ffmpegLogInitialOutput)(i,O),O.on("exit",b),Object.keys(t.parsers).forEach((async(e,r)=>{const n=O.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(O).then((e=>p=e)),m(O).then((e=>h=e)),l(O).then((e=>g=e)),await S,v=void 0,y=void 0,clearTimeout(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,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=o.getDeviceState(this.mixinProviderNativeId).name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}return i}async putSetting(e,t){const i=this.settingsGroupKey+":";if(null==e||!e.startsWith(i))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(i.length),t),o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){o.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.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.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(i||(t.ScryptedInterfaceProperty=i={}));let r,n,o,s,a,c,d,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=r,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(r||(t.ScryptedDeviceType=r={})),t.HumidityMode=n,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(n||(t.HumidityMode=n={})),t.FanMode=o,function(e){e.Auto="Auto",e.Manual="Manual"}(o||(t.FanMode=o={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=c,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(c||(t.LockState=c={})),t.MediaPlayerState=d,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(d||(t.MediaPlayerState=d={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan"}(u||(t.ScryptedInterface=u={})),t.ScryptedMimeTypes=l,function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(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)`,x=["aac","mp3","mp2","AAC","MP3","MP2","opus","OPUS","",void 0,null],w=["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),this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId}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.audioConfigurationKey)||"",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 Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||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&&!x.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(),O=!b||b===S,C=this.incompatibleDetected&&O;let I;this.audioDisabled=!1,d.noAudio||C?(I=["-an"],this.audioDisabled=!0):I=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:I}),mpegts:(0,c.createMpegTsParser)({vcodec:A,acodec:I})}};d.noAudio||C||!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?x.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),O&&!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 w){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=w.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 O=new PrebufferProvider;e.default=O})();var n=exports="undefined"==typeof exports?{}:exports;for(var o in r)n[o]=r[o];r.__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=o(t);if(i&&i.has(e))return i.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=n?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{systemManager:n}=r.default,s="v4";class AutoenableMixinProvider extends r.ScryptedDeviceBase{constructor(e){var t,i,o;super(e),o={},(i="hasEnabledMixin")in(t=this)?Object.defineProperty(t,i,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[i]=o;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=n.getComponent("plugins"),n.listen((async(e,t,i)=>{t.eventInterface!==r.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)}));for(const e of Object.keys(n.getSystemState())){const t=n.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id]===s)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const i=(e.mixins||[]).slice();i.push(this.id);const r=await this.pluginsComponent;await r.setMixins(e.id,i),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==s&&(this.hasEnabledMixin[e]=s,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=AutoenableMixinProvider},201:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createRebroadcaster=async function(e){let t=0;const i=(0,r.createServer)((i=>{let r=!0;const o=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,null==e||e()};let n=null==e?void 0:e.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),o);i.once("close",(()=>{t--,o()})),t++})),o=await(0,n.listenZero)(i);return{server:i,port:o,get clients(){return t}}},t.parseAudioCodec=f,t.parseResolution=l,t.parseVideoCodec=m,t.startParserSession=async function(e,t){const{console:i}=t;let r,n,a=!0;const c=new s.EventEmitter;let p,h,g,v,y;c.on("error",(e=>i.error("rebroadcast error",e)));const S=new Promise(((e,t)=>{v=e,y=t}));function b(){var e;a&&(c.emit("killed"),c.emit("error",new Error("killed"))),a=!1,null==O||O.kill(),null==O||O.kill("SIGKILL"),null===(e=y)||void 0===e||e(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r),clearTimeout(n)}function M(){i.error("timeout waiting for data, killing parser session"),b()}function P(){t.timeout&&(clearTimeout(r),r=setTimeout(M,t.timeout))}P();const w=e.inputArguments.slice();n=setTimeout(b,3e4);const x=["pipe","pipe","pipe"];let D=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];w.push(...i.outputArguments,`pipe:${D}`),x.push("pipe"),D++}w.unshift("-hide_banner"),(0,d.safePrintFFmpegArguments)(i,w);const O=o.default.spawn(await u.getFFmpegPath(),w,{stdio:x});return(0,d.ffmpegLogInitialOutput)(i,O),O.on("exit",b),Object.keys(t.parsers).forEach((async(e,r)=>{const o=O.stdio[3+r],n=t.parsers[e];try{for await(const t of n.parse(o,parseInt(null===(s=g)||void 0===s?void 0:s[2]),parseInt(null===(a=g)||void 0===a?void 0:a[3]))){var s,a,d;null===(d=v)||void 0===d||d(void 0),c.emit(e,t),P()}}catch(e){i.error("rebroadcast parse error",e),b()}})),f(O).then((e=>p=e)),m(O).then((e=>h=e)),l(O).then((e=>g=e)),await S,v=void 0,y=void 0,clearTimeout(n),{inputAudioCodec:p,inputVideoCodec:h,inputVideoResolution:g,resetActivityTimer:P,isActive:()=>a,kill:b,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},on(e,t){return c.on(e,t),this},once(e,t){return c.once(e,t),this},removeListener(e,t){return c.removeListener(e,t),this}}};var r=i(808),o=c(i(81)),n=i(769),s=i(361),a=c(i(510)),d=i(833);function c(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;async function l(e){return new Promise((t=>{const i=r=>{const o=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(o);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}async function p(e,t){return new Promise((i=>{const r=o=>{const n=o.toString(),s=n.indexOf(`${t}: `);if(-1!==s){const o=n.substring(s+t.length+1).trim();let a=o.indexOf(" ");const d=o.indexOf(",");-1!==a&&d<a&&(a=d),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(o.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}async function m(e){return p(e,"Video")}async function f(e){return p(e,"Audio")}},769:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bindZero=async function(e){return e.bind(0),await(0,n.once)(e,"listening"),e.address().port},t.listenZero=s,t.listenZeroSingleClient=async function(){const e=new o.default.Server,t=await s(e),i=new Promise(((t,i)=>{const r=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(r),t(i)}))}));return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:i}};var r,o=(r=i(808))&&r.__esModule?r:{default:r},n=i(361);async function s(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}},833:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=i(568);Object.keys(r).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===r[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}}))}))},701:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const o=()=>{const r=e.read(t);r&&(s(),i(r))},n=()=>{s(),r(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",o),e.removeListener("end",n)};e.on("readable",o),e.on("end",n)}))}},567:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=o(t);if(i&&i.has(e))return i.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var a=n?Object.getOwnPropertyDescriptor(e,s):null;a&&(a.get||a.set)?Object.defineProperty(r,s,a):r[s]=e[s]}r.default=e,i&&i.set(e,r);return r}(i(510));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(o=function(e){return e?i:t})(e)}const{deviceManager:n}=r.default;class SettingsMixinDeviceBase extends r.MixinDeviceBase{constructor(e,t,i){super(e,i.mixinDeviceInterfaces,t,i.providerNativeId,i.mixinStorageSuffix),this.settingsGroup=i.group,this.settingsGroupKey=i.groupKey,process.nextTick((()=>n.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)))}async getSettings(){const e=this.mixinDeviceInterfaces.includes(r.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,t=this.getMixinSettings(),i=[];try{const t=await e||[];i.push(...t)}catch(e){const t=this.name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}try{const e=await t||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=n.getDeviceState(this.mixinProviderNativeId).name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}return i}async putSetting(e,t){const i=this.settingsGroupKey+":";if(null==e||!e.startsWith(i))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(i.length),t),n.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}release(){n.onMixinEvent(this.id,this,r.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=SettingsMixinDeviceBase},129:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PIXEL_FORMAT_YUV420P=t.PIXEL_FORMAT_RGB24=void 0,t.createFragmentedMp4Parser=function(e){return{container:"mp4",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=a(e);let i,r,o;for await(const e of t)i?r||(r=e):i=e,yield{startStream:o,chunks:[e.header,e.data],type:e.type},i&&r&&!o&&(o=Buffer.concat([i.header,i.data,r.header,r.data]))},findSyncFrame:n}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,(e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")})),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const o=i.chunks[r];let n=0;for(;n+188<o.length;){const i=o.subarray(n,n+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);n+=188}}}return e}}},t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:n}},t.createRawVideoParser=function(e){var t;const i=(null===(t=e)||void 0===t?void 0:t.pixelFormat)||d;let r;e=e||{};const{size:s,everyNFrames:a}=e;s&&(r=`scale=${s.width}:${s.height}`);a&&a>1&&(r?r+=",":r="",r+=`select=not(mod(n\\,${a}))`);return{container:"rawvideo",outputArguments:[...r?["-vf",r]:[],"-an","-vcodec","rawvideo","-pix_fmt",i.name,"-f","rawvideo"],async*parse(e,t,r){if(!t||!r)throw new Error("error parsing rawvideo, unknown width and height");t=(null==s?void 0:s.width)||t,r=(null==s?void 0:s.height)||r;const n=i.computeLength(t,r);for(;;){const i=await(0,o.readLength)(e,n);yield{chunks:[i],width:t,height:r}}},findSyncFrame:n}},t.parseFragmentedMP4=a;var r=i(361),o=i(701);function n(e){return e}function s(e,t){return async function*(i){let o=[],n=0;for(;;){const s=i.read();if(!s){await(0,r.once)(i,"readable");continue}if(o.push(s),n+=s.length,n<e)continue;const a=Buffer.concat(o);null==t||t(a);const d=a.length%e,c=a.slice(0,a.length-d),u=a.slice(a.length-d);o=[u],n=u.length,yield{chunks:[c]}}}}async function*a(e){for(;;){const t=await(0,o.readLength)(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await(0,o.readLength)(e,i);yield{header:t,length:i,type:r,data:n}}}const d={name:"yuv420p",computeLength:(e,t)=>e*t*1.5};t.PIXEL_FORMAT_YUV420P=d;t.PIXEL_FORMAT_RGB24={name:"rgb24",computeLength:(e,t)=>e*t*3}},510:(e,t,i)=>{"use strict";var r=Object.create?function(e,t,i,r){void 0===r&&(r=i),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,r){void 0===r&&(r=i),e[r]=t[i]},o=function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,o(i(393),t);const n=i(393);class ScryptedDeviceBase extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=ScryptedDeviceBase;class MixinDeviceBase extends n.DeviceBase{constructor(e,t,i,r,o){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=r,this._mixinStorageSuffix=o,this._listeners=new Set,this._deviceState=i}get storage(){if(!this._storage){const e=this._mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=MixinDeviceBase,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var i of Object.values(n.ScryptedInterfaceProperty))Object.defineProperty(ScryptedDeviceBase.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(MixinDeviceBase.prototype,i,{set:t(i),get:e(i)})}();let s={};try{s=Object.assign(s,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=s},393:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ThermostatMode=t.TemperatureUnit=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterfaceDescriptors=t.ScryptedInterface=t.ScryptedDeviceType=t.SCRYPTED_MEDIA_SCHEME=t.MediaPlayerState=t.LockState=t.HumidityMode=t.FanMode=t.DeviceBase=void 0;let i;t.DeviceBase=class DeviceBase{},t.ScryptedInterfaceProperty=i,function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(i||(t.ScryptedInterfaceProperty=i={}));let r,o,n,s,a,d,c,u,l;t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]}},t.ScryptedDeviceType=r,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(r||(t.ScryptedDeviceType=r={})),t.HumidityMode=o,function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(o||(t.HumidityMode=o={})),t.FanMode=n,function(e){e.Auto="Auto",e.Manual="Manual"}(n||(t.FanMode=n={})),t.TemperatureUnit=s,function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),t.ThermostatMode=a,function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(a||(t.ThermostatMode=a={})),t.LockState=d,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(d||(t.LockState=d={})),t.MediaPlayerState=c,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(c||(t.MediaPlayerState=c={})),t.ScryptedInterface=u,function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan"}(u||(t.ScryptedInterface=u={})),t.ScryptedMimeTypes=l,function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(l||(t.ScryptedMimeTypes=l={}));t.SCRYPTED_MEDIA_SCHEME="scryped-media://"},568:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,r){var o,n;function s(e){const o=n=>{const s=n.toString();for(const e of i)if(-1!==s.indexOf(e))return;if(!r&&(-1!==s.indexOf("frame=")||-1!==s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",o),void t.stderr.removeListener("data",o);e(s)};return o}null===(o=t.stdout)||void 0===o||o.on("data",s(e.log)),null===(n=t.stderr)||void 0===n||n.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))},t.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 o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}var r={};(()=>{"use strict";var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var t=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var i=c(t);if(i&&i.has(e))return i.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var n in e)if("default"!==n&&Object.prototype.hasOwnProperty.call(e,n)){var s=o?Object.getOwnPropertyDescriptor(e,n):null;s&&(s.get||s.set)?Object.defineProperty(r,n,s):r[n]=e[n]}r.default=e,i&&i.set(e,r);return r}(i(510)),o=i(361),n=i(567),s=i(201),a=i(129),d=i(454);function c(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,i=new WeakMap;return(c=function(e){return e?i:t})(e)}function u(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const{mediaManager:l,log:p,systemManager:m,deviceManager:f}=t.default,h=1e4,g="prebufferDuration",v="sendKeyframe",y="Default",S="AAC or No Audio",b=`${S} (Copy)`,M="Compatible Audio",P="Other Audio",w="PCM or G.711 Audio",x=`${w} (Copy, Unstable)`,D=["aac","mp3","mp2","opus"],O="-fflags +genpts",C=[S,M,P,w],I=["mpegts","mp4","s16le"];class PrebufferSession{constructor(e,t,i,r){u(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),u(this,"detectedIdrInterval",0),u(this,"prevIdr",0),u(this,"audioDisabled",!1),u(this,"activeClients",0),this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";C.includes(e)||(e="");const t=-1!==e.indexOf(S),i=-1!==e.indexOf(M),r=-1!==e.indexOf(P),o=-1!==e.indexOf(w);return{isUsingDefaultAudioConfig:!(t||i||r||o),aacAudio:t,pcmAudio:o,compatibleAudio:i,reencodeAudio:r}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;for(const e of this.prebuffers.mp4){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const o=Date.now()-r,n=Math.round(i/o*8),s=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";var a,d,c;(e.push({title:"Audio Codec Transcoding",group:s,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||y,choices:[y,b,"Compatible Audio (Copy)","Other Audio (Transcode)",x]},{title:"Additional FFmpeg Input Arguments",group:s,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:O,choices:[O,"-use_wallclock_as_timestamps 1"],combobox:!0}),t)?e.push({key:"detectedResolution",group:s,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==t||null===(a=t.inputVideoResolution)||void 0===a?void 0:a[0])||"unknown"} @ ${n||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:s,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==t||null===(d=t.inputVideoCodec)||void 0===d?void 0:d.toString())||"unknown")+"/"+((null==t||null===(c=t.inputAudioCodec)||void 0===c?void 0:c.toString())||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:s,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}):e.push({title:"Status",group:s,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}async startPrebufferSession(){var e,i,r,o,n,d,c;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const u=parseInt(this.storage.getItem(g))||h;let m;try{m=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const f=null===(null===(e=m)||void 0===e||null===(i=e.audio)||void 0===i?void 0:i.codec),v=null===(r=m)||void 0===r||null===(o=r.audio)||void 0===o?void 0:o.codec,{isUsingDefaultAudioConfig:y,aacAudio:b,compatibleAudio:M,reencodeAudio:P,pcmAudio:w}=this.getAudioConfig();let x=!1;f||v||!y||void 0!==this.detectedAudioCodec||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),x=!0),!f&&v&&void 0!==this.detectedAudioCodec&&this.detectedAudioCodec!==v&&this.console.warn("Audio codec plugin reported vs detected mismatch",v,this.detectedAudioCodec);const C=void 0===this.detectedAudioCodec?null==v?void 0:v.toLowerCase():null===(n=this.detectedAudioCodec)||void 0===n?void 0:n.toLowerCase();if(!x){!D.includes(C)?(y&&p.a(`${this.mixin.name} is using the ${this.detectedAudioCodec} audio codec and has had its audio disabled. Select 'Disable Audio' or 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",this.detectedAudioCodec||v)):!f&&y&&void 0===v&&void 0!==this.detectedAudioCodec&&("aac"===this.detectedAudioCodec?p.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select '${S}' in the camera stream's Rebroadcast settings to suppress this alert.`):p.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select 'Compatible Audio' in the camera stream's Rebroadcast settings to suppress this alert.`))}const A=await this.mixinDevice.getVideoStream(m),k=await l.convertMediaObjectToBuffer(A,t.ScryptedMimeTypes.FFmpegInput),_=JSON.parse(k.toString()),T=["-bsf:a","aac_adtstoasc"],E=[];let L;this.audioDisabled=!1;const j=null===this.detectedAudioCodec;if(f||x||j)L=["-an"],this.audioDisabled=!0;else if(w)L=["-an"];else if(P)L=["-bsf:a","aac_adtstoasc","-acodec","libfdk_aac","-profile:a","aac_low","-flags","+global_header","-ar","8k","-b:a","100k","-ac","1"];else if(b)L=["-acodec","copy"],L.push(...T);else if(M)L=["-acodec","copy"],L.push(...E);else{L=["-acodec","copy"];const e="aac"===C?T:E;L.push(...e)}const R=["-vcodec","copy"],B={console:this.console,timeout:6e4,parsers:{mp4:(0,a.createFragmentedMp4Parser)({vcodec:R,acodec:L}),mpegts:(0,a.createMpegTsParser)({vcodec:R,acodec:L})}};!w||f||j||(B.parsers.s16le=(0,a.createPCMParser)()),this.parsers=B.parsers;const V=this.storage.getItem(this.ffmpegInputArgumentsKey)||O;_.inputArguments.unshift(...V.split(" "));const H=await(0,s.startParserSession)(_,B);if(H.inputAudioCodec?D.includes(null===(d=H.inputAudioCodec)||void 0===d?void 0:d.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",H.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",H.inputAudioCodec):this.console.log("No audio stream detected."),this.detectedAudioCodec=H.inputAudioCodec||null,"h264"!==H.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),x)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),H.kill(),this.startPrebufferSession();if(this.parserSession=H,null!==(c=_.mediaStreamOptions)&&void 0!==c&&c.refreshAt){let e,i=_.mediaStreamOptions;const r=async()=>{if(!H.isActive)return;const e=await this.mixinDevice.getVideoStream(i),r=await l.convertMediaObjectToBuffer(e,t.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,o(n)},o=t=>{const i=t.mediaStreamOptions.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),e=setTimeout(r,i)};o(_),H.once("killed",(()=>clearTimeout(e)))}H.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===H&&(this.parserSession=void 0)}));for(const e of I){let t=0;H.on(e,(i=>{const r=this.prebuffers[e],o=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),r.push({time:o,chunk:i});r.length&&r[0].time<o-u;)r.shift(),t++;t>1e3&&(this.prebuffers[e]=r.slice(),t=0)}))}return H}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e){this.printActiveClients(),this.stopInactive&&(this.activeClients||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.activeClients||(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),e.kill())}),3e4)))}async getVideoStream(e){var t,i;this.ensurePrebufferSession();const r=await this.parserSessionPromise,o="false"!==this.storage.getItem(v),n=(null==e?void 0:e.prebuffer)||(o?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);this.console.log(this.streamName,"prebuffer request started");const a=async e=>{const t=this.prebuffers[e],{server:i,port:o}=await(0,s.createRebroadcaster)({connect:(o,s)=>{this.activeClients++,this.printActiveClients(),i.close();const a=Date.now(),d=e=>{o(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),this.console.log(this.streamName,"prebuffer request ended"),r.removeListener(e,d),r.removeListener("killed",c)};r.on(e,d),r.once("killed",c);for(const e of t)e.time<a-n||d(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(r),c()}}});return setTimeout((()=>i.close()),3e4),o},d=I.find((t=>t===(null==e?void 0:e.container)))||"mpegts",c=Object.assign({},r.mediaStreamOptions);c.prebuffer=n;const{pcmAudio:u,reencodeAudio:p}=this.getAudioConfig();this.audioDisabled?c.audio=null:c.audio=p?{codec:"aac",encoder:"libfdk_aac",profile:"aac_eld"}:{codec:null==r?void 0:r.inputAudioCodec},c.video&&null!==(t=r.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(i=r.inputVideoResolution)&&void 0!==i&&i[3]&&Object.assign(c.video,{width:parseInt(r.inputVideoResolution[2]),height:parseInt(r.inputVideoResolution[3])});const m=Date.now();let f=0;const h=this.prebuffers[d];for(const e of h)if(!(e.time<m-n))for(const t of e.chunk.chunks)f+=t.length;const g=Math.max(5e5,f).toString(),y=`tcp://127.0.0.1:${await a(d)}`,S={url:y,container:d,inputArguments:["-analyzeduration","0","-probesize",g,"-f",d,"-i",y],mediaStreamOptions:c};u&&S.inputArguments.push("-analyzeduration","0","-probesize",g,"-f","s16le","-i",`tcp://127.0.0.1:${await a("s16le")}`);return l.createFFmpegMediaObject(S)}}class PrebufferMixin extends n.SettingsMixinDeviceBase{constructor(e,t,i,r){super(e,i,{providerNativeId:r,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),u(this,"released",!1),u(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=null==e?void 0:e.id;let i=this.sessions.get(t);return!i||null!=e&&e.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(e),r=i?i.map((e=>e.id)):[void 0],n=(null==e?void 0:e.map((e=>e.id)))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){(null==e?void 0:e.find((e=>"cloud"===e.source)))&&(this.storage.setItem("warnedCloud","true"),p.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const s=this.mixinDeviceInterfaces.includes(t.ScryptedInterface.Battery);let a=0;const d=n.length;for(const t of n){let i=this.sessions.get(t);if(!i){var c;const n=null==e?void 0:e.find((e=>e.id===t));null!=n&&n.prebuffer&&p.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const u=null==n?void 0:n.name,l=!r.includes(t);if(i=new PrebufferSession(this,u,t,s||l),this.sessions.set(t,i),t===(null==e||null===(c=e[0])||void 0===c?void 0:c.id)&&this.sessions.set(void 0,i),s){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(l){this.console.log("stream",u,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(t)===i&&!this.released;){i.ensurePrebufferSession();try{const e=await i.parserSessionPromise;a++,this.online=a==d,await(0,o.once)(e,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{a--,this.online=a==d}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}f.onMixinEvent(this.id,this.mixinProviderNativeId,t.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);(null==t?void 0:t.length)>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:i.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:g,value:this.storage.getItem(g)||h.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:v,value:("false"!==this.storage.getItem(v)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const i=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of i.values()){var r;null==e||null===(r=e.parserSessionPromise)||void 0===r||r.then((e=>e.kill()))}this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const i=parseInt(this.storage.getItem(g))||h;if(t)for(const e of t)e.prebuffer=i;else e.push({id:"default",name:"Default",prebuffer:i});return e}release(){this.console.log("prebuffer releasing if started"),this.released=!0;for(const t of this.sessions.values()){var e;t&&(t.clearPrebuffers(),null===(e=t.parserSessionPromise)||void 0===e||e.then((e=>{this.console.log("prebuffer released"),e.kill(),t.clearPrebuffers()})))}}}class PrebufferProvider extends d.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(m.getSystemState())){var t;const i=m.getDeviceById(e);null!==(t=i.mixins)&&void 0!==t&&t.includes(this.id)&&i.getVideoStreamOptions()}const i=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(i/1e3/60)} minutes`),setTimeout((()=>f.requestRestart()),i)}async canMixin(e,i){return i.includes(t.ScryptedInterface.VideoCamera)?[t.ScryptedInterface.VideoCamera,t.ScryptedInterface.Settings,t.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new PrebufferMixin(e,t,i,this.nativeId)}async releaseMixin(e,t){t.online=!0,t.release()}}var A=new PrebufferProvider;e.default=A})();var o=exports="undefined"==typeof exports?{}:exports;for(var n in r)o[n]=r[n];r.__esModule&&Object.defineProperty(o,"__esModule",{value:!0})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -14,16 +14,25 @@ const defaultPrebufferDuration = 10000;
|
|
|
14
14
|
const PREBUFFER_DURATION_MS = 'prebufferDuration';
|
|
15
15
|
const SEND_KEYFRAME = 'sendKeyframe';
|
|
16
16
|
const AUDIO_CONFIGURATION_KEY_PREFIX = 'audioConfiguration-';
|
|
17
|
+
const FFMPEG_INPUT_ARGUMENTS_KEY_PREFIX = 'ffmpegInputArguments-';
|
|
17
18
|
const DEFAULT_AUDIO = 'Default';
|
|
18
|
-
const
|
|
19
|
+
const AAC_AUDIO = 'AAC or No Audio';
|
|
20
|
+
const AAC_AUDIO_DESCRIPTION = `${AAC_AUDIO} (Copy)`;
|
|
21
|
+
const COMPATIBLE_AUDIO = 'Compatible Audio'
|
|
19
22
|
const COMPATIBLE_AUDIO_DESCRIPTION = `${COMPATIBLE_AUDIO} (Copy)`;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const OTHER_AUDIO = 'Other Audio';
|
|
23
|
-
const OTHER_AUDIO_DESCRIPTION = `${OTHER_AUDIO} (Transcode)`;
|
|
23
|
+
const TRANSCODE_AUDIO = 'Other Audio';
|
|
24
|
+
const TRANSCODE_AUDIO_DESCRIPTION = `${TRANSCODE_AUDIO} (Transcode)`;
|
|
24
25
|
const PCM_AUDIO = 'PCM or G.711 Audio';
|
|
25
26
|
const PCM_AUDIO_DESCRIPTION = `${PCM_AUDIO} (Copy, Unstable)`;
|
|
26
|
-
const
|
|
27
|
+
const COMPATIBLE_AUDIO_CODECS = ['aac', 'mp3', 'mp2', 'opus'];
|
|
28
|
+
const DEFAULT_FFMPEG_INPUT_ARGUMENTS = '-fflags +genpts';
|
|
29
|
+
|
|
30
|
+
const VALID_AUDIO_CONFIGS = [
|
|
31
|
+
AAC_AUDIO,
|
|
32
|
+
COMPATIBLE_AUDIO,
|
|
33
|
+
TRANSCODE_AUDIO,
|
|
34
|
+
PCM_AUDIO,
|
|
35
|
+
];
|
|
27
36
|
|
|
28
37
|
interface PrebufferStreamChunk {
|
|
29
38
|
chunk: StreamChunk;
|
|
@@ -52,8 +61,7 @@ class PrebufferSession {
|
|
|
52
61
|
|
|
53
62
|
detectedIdrInterval = 0;
|
|
54
63
|
prevIdr = 0;
|
|
55
|
-
|
|
56
|
-
legacyDetected = false;
|
|
64
|
+
detectedAudioCodec: string;
|
|
57
65
|
audioDisabled = false;
|
|
58
66
|
|
|
59
67
|
mixinDevice: VideoCamera;
|
|
@@ -63,12 +71,14 @@ class PrebufferSession {
|
|
|
63
71
|
activeClients = 0;
|
|
64
72
|
inactivityTimeout: NodeJS.Timeout;
|
|
65
73
|
audioConfigurationKey: string;
|
|
74
|
+
ffmpegInputArgumentsKey: string;
|
|
66
75
|
|
|
67
76
|
constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string, public stopInactive: boolean) {
|
|
68
77
|
this.storage = mixin.storage;
|
|
69
78
|
this.console = mixin.console;
|
|
70
79
|
this.mixinDevice = mixin.mixinDevice;
|
|
71
80
|
this.audioConfigurationKey = AUDIO_CONFIGURATION_KEY_PREFIX + this.streamId;
|
|
81
|
+
this.ffmpegInputArgumentsKey = FFMPEG_INPUT_ARGUMENTS_KEY_PREFIX + this.streamId;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
clearPrebuffers() {
|
|
@@ -86,21 +96,26 @@ class PrebufferSession {
|
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
getAudioConfig(): {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
isUsingDefaultAudioConfig: boolean,
|
|
100
|
+
aacAudio: boolean,
|
|
101
|
+
compatibleAudio: boolean,
|
|
92
102
|
reencodeAudio: boolean,
|
|
103
|
+
pcmAudio: boolean,
|
|
93
104
|
} {
|
|
94
|
-
|
|
105
|
+
let audioConfig = this.storage.getItem(this.audioConfigurationKey) || '';
|
|
106
|
+
if (!VALID_AUDIO_CONFIGS.includes(audioConfig))
|
|
107
|
+
audioConfig = '';
|
|
108
|
+
const aacAudio = audioConfig.indexOf(AAC_AUDIO) !== -1;
|
|
109
|
+
const compatibleAudio = audioConfig.indexOf(COMPATIBLE_AUDIO) !== -1;
|
|
110
|
+
// reencode audio will be used if explicitly set.
|
|
111
|
+
const reencodeAudio = audioConfig.indexOf(TRANSCODE_AUDIO) !== -1;
|
|
95
112
|
// pcm audio only used when explicitly set.
|
|
96
113
|
const pcmAudio = audioConfig.indexOf(PCM_AUDIO) !== -1;
|
|
97
|
-
const legacyAudio = audioConfig.indexOf(LEGACY_AUDIO) !== -1;
|
|
98
|
-
// reencode audio will be used if explicitly set.
|
|
99
|
-
const reencodeAudio = audioConfig.indexOf(OTHER_AUDIO) !== -1;
|
|
100
114
|
return {
|
|
101
|
-
|
|
115
|
+
isUsingDefaultAudioConfig: !(aacAudio || compatibleAudio || reencodeAudio || pcmAudio),
|
|
116
|
+
aacAudio,
|
|
102
117
|
pcmAudio,
|
|
103
|
-
|
|
118
|
+
compatibleAudio,
|
|
104
119
|
reencodeAudio,
|
|
105
120
|
}
|
|
106
121
|
}
|
|
@@ -133,12 +148,25 @@ class PrebufferSession {
|
|
|
133
148
|
value: this.storage.getItem(this.audioConfigurationKey) || DEFAULT_AUDIO,
|
|
134
149
|
choices: [
|
|
135
150
|
DEFAULT_AUDIO,
|
|
151
|
+
AAC_AUDIO_DESCRIPTION,
|
|
136
152
|
COMPATIBLE_AUDIO_DESCRIPTION,
|
|
137
|
-
|
|
138
|
-
OTHER_AUDIO_DESCRIPTION,
|
|
153
|
+
TRANSCODE_AUDIO_DESCRIPTION,
|
|
139
154
|
PCM_AUDIO_DESCRIPTION,
|
|
140
155
|
],
|
|
141
156
|
},
|
|
157
|
+
{
|
|
158
|
+
title: 'Additional FFmpeg Input Arguments',
|
|
159
|
+
group,
|
|
160
|
+
description: 'Optional/Advanced: Additional input arguments to pass to the ffmpeg command.',
|
|
161
|
+
key: this.ffmpegInputArgumentsKey,
|
|
162
|
+
value: this.storage.getItem(this.ffmpegInputArgumentsKey),
|
|
163
|
+
placeholder: DEFAULT_FFMPEG_INPUT_ARGUMENTS,
|
|
164
|
+
choices: [
|
|
165
|
+
DEFAULT_FFMPEG_INPUT_ARGUMENTS,
|
|
166
|
+
'-use_wallclock_as_timestamps 1',
|
|
167
|
+
],
|
|
168
|
+
combobox: true,
|
|
169
|
+
}
|
|
142
170
|
);
|
|
143
171
|
|
|
144
172
|
if (session) {
|
|
@@ -191,28 +219,80 @@ class PrebufferSession {
|
|
|
191
219
|
this.prebuffers.s16le = [];
|
|
192
220
|
const prebufferDurationMs = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
|
|
193
221
|
|
|
194
|
-
// todo: there's a bug here where probe's noAudio check looks at the first media stream option entry
|
|
195
|
-
const probe = await probeVideoCamera(this.mixinDevice);
|
|
196
222
|
let mso: MediaStreamOptions;
|
|
197
|
-
|
|
198
|
-
mso =
|
|
223
|
+
try {
|
|
224
|
+
mso = (await this.mixinDevice.getVideoStreamOptions()).find(o => o.id === this.streamId);
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// audio codecs are determined by probing the camera to see what it reports.
|
|
230
|
+
// if the camera does not specify a codec, rebroadcast will force audio off
|
|
231
|
+
// to determine the codec without causing a parse failure.
|
|
232
|
+
// camera may explicity request that its audio stream be muted via a null.
|
|
233
|
+
// respect that setting.
|
|
234
|
+
const audioSoftMuted = mso?.audio?.codec === null;
|
|
235
|
+
const advertisedAudioCodec = mso?.audio?.codec;
|
|
236
|
+
|
|
237
|
+
const { isUsingDefaultAudioConfig, aacAudio, compatibleAudio, reencodeAudio, pcmAudio } = this.getAudioConfig();
|
|
238
|
+
|
|
239
|
+
let probingAudioCodec = false;
|
|
240
|
+
if (!audioSoftMuted && !advertisedAudioCodec && isUsingDefaultAudioConfig && this.detectedAudioCodec === undefined) {
|
|
241
|
+
this.console.warn('Camera did not report an audio codec, muting the audio stream and probing the codec.');
|
|
242
|
+
probingAudioCodec = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// complain to the user about the codec if necessary. upstream may send a audio
|
|
246
|
+
// stream but report none exists (to request muting).
|
|
247
|
+
if (!audioSoftMuted && advertisedAudioCodec && this.detectedAudioCodec !== undefined
|
|
248
|
+
&& this.detectedAudioCodec !== advertisedAudioCodec) {
|
|
249
|
+
this.console.warn('Audio codec plugin reported vs detected mismatch', advertisedAudioCodec, this.detectedAudioCodec);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// the assumed audio codec is the detected codec first and the reported codec otherwise.
|
|
253
|
+
const assumedAudioCodec = this.detectedAudioCodec === undefined
|
|
254
|
+
? advertisedAudioCodec?.toLowerCase()
|
|
255
|
+
: this.detectedAudioCodec?.toLowerCase();
|
|
256
|
+
|
|
257
|
+
if (!probingAudioCodec) {
|
|
258
|
+
const audioIncompatible = !COMPATIBLE_AUDIO_CODECS.includes(assumedAudioCodec);
|
|
259
|
+
|
|
260
|
+
if (audioIncompatible) {
|
|
261
|
+
// show an alert that rebroadcast needs an explicit setting by the user.
|
|
262
|
+
if (isUsingDefaultAudioConfig) {
|
|
263
|
+
log.a(`${this.mixin.name} is using the ${this.detectedAudioCodec} audio codec and has had its audio disabled. Select 'Disable Audio' or 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`);
|
|
264
|
+
}
|
|
265
|
+
this.console.warn('Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:', this.detectedAudioCodec || advertisedAudioCodec);
|
|
266
|
+
}
|
|
267
|
+
else if (!audioSoftMuted && isUsingDefaultAudioConfig && advertisedAudioCodec === undefined && this.detectedAudioCodec !== undefined) {
|
|
268
|
+
// handling compatible codecs that were unspecified...
|
|
269
|
+
if (this.detectedAudioCodec === 'aac') {
|
|
270
|
+
log.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select '${AAC_AUDIO}' in the camera stream's Rebroadcast settings to suppress this alert.`);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
log.a(`${this.mixin.name} did not report a codec and ${this.detectedAudioCodec} was found during probe. Select '${COMPATIBLE_AUDIO}' in the camera stream's Rebroadcast settings to suppress this alert.`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
199
276
|
}
|
|
200
|
-
const probeAudioCodec = mso?.audio?.codec;
|
|
201
|
-
this.incompatibleDetected = this.incompatibleDetected || (probeAudioCodec && !compatibleAudio.includes(probeAudioCodec));
|
|
202
|
-
if (this.incompatibleDetected)
|
|
203
|
-
this.console.warn('configure your camera to output aac, mp3, mp2, or opus audio. incompatible audio codec detected', probeAudioCodec);
|
|
204
277
|
|
|
205
278
|
const mo = await this.mixinDevice.getVideoStream(mso);
|
|
206
279
|
const moBuffer = await mediaManager.convertMediaObjectToBuffer(mo, ScryptedMimeTypes.FFmpegInput);
|
|
207
280
|
const ffmpegInput = JSON.parse(moBuffer.toString()) as FFMpegInput;
|
|
208
281
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
282
|
+
// aac needs to have the adts header stripped for mpegts and mp4.
|
|
283
|
+
// use this filter sparingly as it prevents ffmpeg from starting on a mismatch.
|
|
284
|
+
// however, not using it on an aac stream also prevents ffmpeg from parsing.
|
|
285
|
+
// so only use it when the detected or probe codec reports aac.
|
|
286
|
+
const aacFilters = ['-bsf:a', 'aac_adtstoasc'];
|
|
287
|
+
// compatible audio like mp3, mp2, opus can be muxed without issue.
|
|
288
|
+
const compatibleFilters = [];
|
|
212
289
|
|
|
213
290
|
this.audioDisabled = false;
|
|
214
291
|
let acodec: string[];
|
|
215
|
-
|
|
292
|
+
|
|
293
|
+
const detectedNoAudio = this.detectedAudioCodec === null;
|
|
294
|
+
|
|
295
|
+
if (audioSoftMuted || probingAudioCodec || detectedNoAudio) {
|
|
216
296
|
// no audio? explicitly disable it.
|
|
217
297
|
acodec = ['-an'];
|
|
218
298
|
this.audioDisabled = true;
|
|
@@ -221,7 +301,6 @@ class PrebufferSession {
|
|
|
221
301
|
acodec = ['-an'];
|
|
222
302
|
}
|
|
223
303
|
else if (reencodeAudio) {
|
|
224
|
-
// setting no audio codec will allow ffmpeg to do an implicit conversion.
|
|
225
304
|
acodec = [
|
|
226
305
|
'-bsf:a', 'aac_adtstoasc',
|
|
227
306
|
'-acodec', 'libfdk_aac',
|
|
@@ -232,13 +311,31 @@ class PrebufferSession {
|
|
|
232
311
|
'-ac', `1`,
|
|
233
312
|
];
|
|
234
313
|
}
|
|
314
|
+
else if (aacAudio) {
|
|
315
|
+
// NOTE: If there is no audio track, the aac filters will still work fine without complaints
|
|
316
|
+
// from ffmpeg. This is why AAC and No Audio can be grouped into a single setting.
|
|
317
|
+
acodec = [
|
|
318
|
+
'-acodec',
|
|
319
|
+
'copy',
|
|
320
|
+
];
|
|
321
|
+
acodec.push(...aacFilters);
|
|
322
|
+
}
|
|
323
|
+
else if (compatibleAudio) {
|
|
324
|
+
acodec = [
|
|
325
|
+
'-acodec',
|
|
326
|
+
'copy',
|
|
327
|
+
];
|
|
328
|
+
acodec.push(...compatibleFilters);
|
|
329
|
+
}
|
|
235
330
|
else {
|
|
236
|
-
// NOTE: if there is no audio track, this will still work fine.
|
|
237
331
|
acodec = [
|
|
238
332
|
'-acodec',
|
|
239
333
|
'copy',
|
|
240
|
-
...(legacyAudio || this.legacyDetected ? [] : ['-bsf:a', 'aac_adtstoasc']),
|
|
241
334
|
];
|
|
335
|
+
|
|
336
|
+
const filters = assumedAudioCodec === 'aac' ? aacFilters : compatibleFilters;
|
|
337
|
+
|
|
338
|
+
acodec.push(...filters);
|
|
242
339
|
}
|
|
243
340
|
|
|
244
341
|
const vcodec = [
|
|
@@ -262,17 +359,43 @@ class PrebufferSession {
|
|
|
262
359
|
};
|
|
263
360
|
|
|
264
361
|
// if pcm prebuffer is requested, create the the parser. don't do it if
|
|
265
|
-
// the camera wants to mute the audio though
|
|
266
|
-
|
|
362
|
+
// the camera wants to mute the audio though, or no audio was detected
|
|
363
|
+
// in a prior attempt.
|
|
364
|
+
if (pcmAudio && !audioSoftMuted && !detectedNoAudio) {
|
|
267
365
|
rbo.parsers.s16le = createPCMParser();
|
|
268
366
|
}
|
|
269
367
|
|
|
270
368
|
this.parsers = rbo.parsers;
|
|
271
369
|
|
|
272
370
|
// create missing pts from dts so mpegts and mp4 muxing does not fail
|
|
273
|
-
|
|
371
|
+
const extraInputArguments = this.storage.getItem(this.ffmpegInputArgumentsKey) || DEFAULT_FFMPEG_INPUT_ARGUMENTS;
|
|
372
|
+
ffmpegInput.inputArguments.unshift(...extraInputArguments.split(' '));
|
|
274
373
|
|
|
275
374
|
const session = await startParserSession(ffmpegInput, rbo);
|
|
375
|
+
|
|
376
|
+
if (!session.inputAudioCodec) {
|
|
377
|
+
this.console.log('No audio stream detected.');
|
|
378
|
+
}
|
|
379
|
+
else if (!COMPATIBLE_AUDIO_CODECS.includes(session.inputAudioCodec?.toLowerCase())) {
|
|
380
|
+
this.console.log('Detected audio codec is not mp4/mpegts compatible.', session.inputAudioCodec);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
this.console.log('Detected audio codec is mp4/mpegts compatible.', session.inputAudioCodec);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// set/update the detected codec, set it to null if no audio was found.
|
|
387
|
+
this.detectedAudioCodec = session.inputAudioCodec || null;
|
|
388
|
+
|
|
389
|
+
if (session.inputVideoCodec !== 'h264') {
|
|
390
|
+
this.console.error(`Video codec is not h264. If there are errors, try changing your camera's encoder output.`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (probingAudioCodec) {
|
|
394
|
+
this.console.warn('Audio probe complete, ending rebroadcast session and restarting with detected codecs.');
|
|
395
|
+
session.kill();
|
|
396
|
+
return this.startPrebufferSession();
|
|
397
|
+
}
|
|
398
|
+
|
|
276
399
|
this.parserSession = session;
|
|
277
400
|
|
|
278
401
|
// cloud streams need a periodic token refresh.
|
|
@@ -307,33 +430,6 @@ class PrebufferSession {
|
|
|
307
430
|
this.parserSession = undefined;
|
|
308
431
|
});
|
|
309
432
|
|
|
310
|
-
if (!session.inputAudioCodec) {
|
|
311
|
-
this.console.warn('no audio detected.');
|
|
312
|
-
}
|
|
313
|
-
else if (!compatibleAudio.includes(session.inputAudioCodec)) {
|
|
314
|
-
this.console.error('Detected audio codec is not mp4/mpegts compatible.', session.inputAudioCodec);
|
|
315
|
-
// show an alert if no audio config was explicitly specified. Force the user to choose/experiment.
|
|
316
|
-
if (isUsingDefaultAudioConfig && !probe.noAudio) {
|
|
317
|
-
log.a(`${this.mixin.name} is using the ${session.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.`);
|
|
318
|
-
this.incompatibleDetected = true;
|
|
319
|
-
// this will probably crash ffmpeg due to mp4/mpegts not being a valid container for pcm,
|
|
320
|
-
// and then it will automatically restart with pcm handling.
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
else if (session.inputAudioCodec?.toLowerCase() !== 'aac') {
|
|
324
|
-
this.console.error('Detected audio codec was not AAC.', session.inputAudioCodec);
|
|
325
|
-
if (!legacyAudio) {
|
|
326
|
-
log.a(`${this.mixin.name} is using ${session.inputAudioCodec} audio. Enable MP2/MP3 Audio in Rebroadcast Settings Audio Configuration to suppress this alert.`);
|
|
327
|
-
this.legacyDetected = true;
|
|
328
|
-
// this will probably crash ffmpeg due to mp2/mp3/opus not supporting the aac bit stream filters,
|
|
329
|
-
// and then it will automatically restart with legacy handling.
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (session.inputVideoCodec !== 'h264') {
|
|
334
|
-
this.console.error(`video codec is not h264. If there are errors, try changing your camera's encoder output.`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
433
|
// s16le will be a no-op if there's no pcm, no harm.
|
|
338
434
|
for (const container of PrebufferParserValues) {
|
|
339
435
|
let shifts = 0;
|
|
@@ -380,7 +476,7 @@ class PrebufferSession {
|
|
|
380
476
|
return;
|
|
381
477
|
if (this.activeClients)
|
|
382
478
|
return;
|
|
383
|
-
|
|
479
|
+
|
|
384
480
|
clearTimeout(this.inactivityTimeout)
|
|
385
481
|
this.inactivityTimeout = setTimeout(() => {
|
|
386
482
|
if (this.activeClients)
|
|
@@ -465,7 +561,7 @@ class PrebufferSession {
|
|
|
465
561
|
|
|
466
562
|
mediaStreamOptions.prebuffer = requestedPrebuffer;
|
|
467
563
|
|
|
468
|
-
const {
|
|
564
|
+
const { pcmAudio, reencodeAudio } = this.getAudioConfig();
|
|
469
565
|
|
|
470
566
|
if (this.audioDisabled) {
|
|
471
567
|
mediaStreamOptions.audio = null;
|
|
@@ -570,6 +666,14 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
|
|
570
666
|
const enabledIds = enabled ? enabled.map(mso => mso.id) : [undefined];
|
|
571
667
|
const ids = msos?.map(mso => mso.id) || [undefined];
|
|
572
668
|
|
|
669
|
+
if (this.storage.getItem('warnedCloud') !== 'true') {
|
|
670
|
+
const cloud = msos?.find(mso => mso.source === 'cloud');
|
|
671
|
+
if (cloud) {
|
|
672
|
+
this.storage.setItem('warnedCloud', 'true');
|
|
673
|
+
log.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
573
677
|
const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
|
|
574
678
|
|
|
575
679
|
let active = 0;
|