@scrypted/prebuffer-mixin 0.1.214 → 0.1.217

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  {
2
- "scrypted.debugHost": "koushik-ubuntu",
2
+ "scrypted.debugHost": "127.0.0.1",
3
3
  }
@@ -1,2 +1,2 @@
1
- (()=>{var e={747:(e,t,i)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return n.default}});var r=a(i(945)),s=a(i(816)),n=a(i(243)),o=a(i(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:i}=c.decodeHash(e);return{hash:e,username:t,password:i}},buildAuthorizationRest:function({hash:e,username:t,password:i}){if(t&&i)return c.computeHash({username:t,password:i});if(!e)throw new s.default("E_NO_HASH");return e},computeHash:function({username:e,password:t}){return Buffer.from(e+":"+t).toString("base64")},decodeHash:function(e){const[t,...i]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:i.join(":")}}};var d=c;t.default=d},828:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=i(761),n=(r=i(113))&&r.__esModule?r:{default:r};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const i=n.default.createHash(e);return i.update(t),i.digest("hex")}var p={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),i=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};t.default=p},761:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){const r=e.trim().match(n);if(!r)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new s.default("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(i,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){return o(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")};var r,s=(r=i(945))&&r.__esModule?r:{default:r};const n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,i){"use strict";var r=this&&this.__createBinding||(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]}),s=this&&this.__exportStar||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,s(i(268),t);const n=i(268);class o 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=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}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=a,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(o.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(a.prototype,i,{set:t(i),get:e(i)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=c},268:(e,t,i)=>{"use strict";i.r(t),i.d(t,{DeviceBase:()=>r,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let s;!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"}(s||(s={}));const n={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:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:[]},VideoClips:{name:"VideoClips",methods:["getVideoClip","getVideoClipThumbnail","getVideoClips","removeVideoClips"],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"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,p,l,h,m;!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"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),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"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(l||(l={})),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.VideoRecorder="VideoRecorder",e.VideoClips="VideoClips",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",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){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.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))},113:e=>{"use strict";e.exports=require("crypto")},945:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>m});const r=require("os");function s(e,t){for(var i=0;i<t.length;i++){var r=t[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(i=e,-1===Function.toString.call(i).indexOf("[native code]")))return e;var i;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return a(e,arguments,u(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),d(r,e)},o(e)}function a(e,t,i){return a=c()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var s=new(Function.bind.apply(e,r));return i&&d(s,i.prototype),s},a.apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function d(e,t){return d=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},d(e,t)}function u(e){return u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},u(e)}let p=function(e){function t(e,i,...r){var s,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(r=(void 0===i?[]:[i]).concat(r),i=e,e=[]),o=this,(s=!(a=u(t).call(this,i))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=i||"E_UNEXPECTED",s.params=r,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var i,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),i=t,(o=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+r.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(i.prototype,o),a&&s(i,a),t}(o(Error));function l(e){return e instanceof p||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&h(e.code)&&e.params&&e.params instanceof Array}function h(e){return/^([A-Z0-9_]+)$/.test(e)}p.wrap=function(e,t,...i){let r=null;const s=h(e.message),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&i.push(e.message),r=new p(n,t,...i),r},p.cast=function(e,...t){return l(e)?e:p.wrap.apply(p,[e].concat(t))},p.bump=function(e,...t){return l(e)?p.wrap.apply(p,[e,e.code].concat(e.params)):p.wrap.apply(p,[e].concat(t))};const m=p}},t={};function i(r){var s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,i),n.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";i.r(r),i.d(r,{default:()=>we});var e=i(510),t=i.n(e);const s=require("events"),{deviceManager:n}=t();class o extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>n.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,i=this.getMixinSettings(),r=[];try{const e=await t||[];r.push(...e)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}try{const e=await i||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=n.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return r}async putSetting(t,i){const r=this.settingsGroupKey+":";if(!t?.startsWith(r))return this.mixinDevice.putSetting(t,i);await this.putMixinSetting(t.substring(r.length),i),n.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await n.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const a=require("net");var c=i.n(a);const d=require("child_process");var u=i.n(d);const p=require("dgram");var l=i.n(p);async function h(e,t){e.bind(t),await(0,s.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function m(e){return h(e,0)}async function f(e,t){return e.bind(t),await(0,s.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function g(){const e=new(c().Server),t=await async function(e){return e.listen(0),await(0,s.once)(e,"listening"),e.address().port}(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{server:e,url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:i}}const y=require("process");var v=i.n(y);const S=["decode_slice_header error","no frame!","non-existing PPS"];function b(e,t,i,r){const s=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const r=n=>{const o=n.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(s||i||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(o)};return r}t.stdout?.on("data",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];let r=!1;for(const e of t){try{if(r){const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}else i.push(e)}catch(t){i.push(e)}r="-i"===e}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(s.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let s;function n(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(s),s=setTimeout(n,r))}return i.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o}}async function x(e,t){const{console:i}=t;let r,n=!0;const o=new s.EventEmitter;let a,c,d,p,h;o.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{p=e,h=t}));function v(){n&&(o.emit("killed"),o.emit("error",new Error("killed"))),n=!1,T?.kill(),setTimeout((()=>T?.kill("SIGKILL")),1e3),h?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r)}const S=e.inputArguments.slice();r=setTimeout(v,3e4);let x=!1;const C=["pipe","pipe","pipe"];let O=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){x=!0;const i=l().createSocket("udp4"),s=await m(i),n=l().createSocket("udp4");await f(n,s.port+1),o.once("killed",(()=>{i.close(),n.close()})),S.push(...r.outputArguments,s.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{for await(const t of r.parseDatagram(i,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),a()})(),(async()=>{for await(const t of r.parseDatagram(n,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),o.emit(e,t),a()})()}else if(r.tcpProtocol){const s=await g(),n=new URL(r.tcpProtocol);n.port=s.port.toString(),S.push(...r.outputArguments,n.toString());const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{const t=await s.clientPromise;try{for await(const i of r.parse(t,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,i),a()}catch(e){i.error("rebroadcast parse error",e),v()}})()}else S.push(...r.outputArguments,"pipe:"+O++),C.push("pipe")}x&&(S.push("-sdp_file","pipe:"+O++),C.push("pipe")),S.unshift("-hide_banner"),w(i,S);const T=u().spawn(await P.getFFmpegPath(),S,{stdio:C});let D;b(i,T,void 0,t?.storage),T.on("exit",v),D=x?new Promise((e=>{const t=[];T.stdio[O-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let A=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const s=T.stdio[3+A];A++;try{const{resetActivityTimer:i}=M(e,v,o,t?.timeout);for await(const t of r.parse(s,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),i()}catch(e){i.error("rebroadcast parse error",e),v()}})),async function(e){return I(e,"Audio")}(T).then((e=>a=e)),async function(e){return I(e,"Video")}(T).then((e=>c=e)),async function(e){return new Promise((t=>{const i=r=>{const s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(T).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:D,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>n,kill:v,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},emit(e,t){return o.emit(e,t),this},on(e,t){return o.on(e,t),this},once(e,t){return o.once(e,t),this},removeListener(e,t){return o.removeListener(e,t),this}}}async function C(e,t){const i=await e;let r=!0;const s=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,e?.()};let n=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),s);i.once("close",(()=>{s()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function O(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const s=()=>{const r=e.read(t);r&&(o(),i(r))},n=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const T="\n".charCodeAt(0);async function D(e){return async function(e,t){const i=[];let r=0;for(;;){const s=await O(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;i[r++]=s[0]}return Buffer.from(i).toString()}(e,T)}function A(e){return e}async function*k(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),s=await O(e,i);yield{header:t,length:i,type:r,data:s}}}async function*R(e){let t,i,r;for await(const s of e)t?i||(i=s):t=s,yield{startStream:r,chunks:[s.header,s.data],type:s.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:E}=t(),_="v4";class B extends e.ScryptedDeviceBase{hasEnabledMixin={};constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=E.getComponent("plugins"),E.listen((async(t,i,r)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(E.getSystemState())){const t=E.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){if(!e||e.mixins?.includes(this.id))return;if(this.hasEnabledMixin[e.id]===_)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 t=(e.mixins||[]).slice();t.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==_&&(this.hasEnabledMixin[e]=_,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const{mediaManager:V}=t();function L(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));const i=t.findIndex((e=>e.startsWith("m=video")));-1!==i&&t.splice(i+1,0,"a=control:trackID=video");const r=t.findIndex((e=>e.startsWith("m=audio")));return-1!==r&&t.splice(r+1,0,"a=control:trackID=audio"),t.join("\r\n")}function H(e,t,i=["recvonly","sendrecv"]){const r=e.split("m=").filter((e=>e.startsWith(t)));for(const e of r){const t=()=>{const t=e.split("\n").map((e=>e.trim())).find((e=>e.startsWith("a=control:")));return{section:"m="+e,trackId:t?.split("a=control:")?.[1]}};for(const r of i)if(e.includes(`a=${r}`))return t();if(i.includes("recvonly")&&!e.includes("sendonly")&&!e.includes("inactive"))return t()}}var U=i(113),j=i.n(U);const K=require("tls");var N=i.n(K),$=i(747);class q extends Error{constructor(){super("Operation Timed Out")}}async function F(e){let t=[];for(;;){let i=await D(e);if(i=i.trim(),!i)return t;t.push(i)}}function W(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=r}return t}class G extends class{constructor(e){this.console=e}write(e,t,i){let r=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))r+=`${e}: ${i}\r\n`;r+="\r\n",this.client.write(r),this.console?.log("rtsp outgpoing message\n",r),this.console?.log(),i&&this.client.write(i)}async readMessage(){const e=await F(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;constructor(e,t){super(t),this.url=e;const i=new URL(e),r=parseInt(i.port)||554;e.startsWith("rtsps")?this.client=N().connect({rejectUnauthorized:!1,port:r,host:i.hostname}):this.client=c().connect(r,i.hostname)}writeRequest(e,t,i,r){t=t||{};let s=this.url;i&&(s+="/"+i);const n=new URL(s);n.username="",n.password="",s=n.toString();const o=`${e} ${s} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),this.authorization&&(t.Authorization=this.authorization),this.session&&(t.Session=this.session),this.write(o,t,r)}async request(e,t,i,r,s){this.writeRequest(e,t,i,r);const n=this.requestTimeout?await(o=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new q)),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=W(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=d["www-authenticate"];if(u){if(s)throw new Error("auth failed");const n=new URL(this.url);if(u.includes("Basic")){const e=$.rh.computeHash(n);this.authorization=`Basic ${e}`}else{const t=$.Nu.parseWWWAuthenticateRest(u),i=decodeURIComponent(n.username),r=decodeURIComponent(n.password),s=j().createHash("md5").update(`${i}:${t.realm}:${r}`).digest("hex"),o=j().createHash("md5").update(`${e}:${n.pathname}`).digest("hex"),a=j().createHash("md5").update(`${s}:${t.nonce}:${o}`).digest("hex"),c={username:i,realm:t.realm,nonce:t.nonce,uri:n.pathname,algorithm:"MD5",response:a},d=Object.entries(c).map((([e,t])=>{return`${e}=${t&&(i=t,`"${i.replace(/"/g,'\\"')}"`)}`;var i})).join(", ");this.authorization=`Digest ${d}`}return this.request(e,t,i,r,!0)}const p=parseInt(d["content-length"]);return p?{headers:d,body:await O(this.client,p)}:{headers:d,body:void 0}}async options(){return this.request("OPTIONS",{})}async writeGetParameter(){return this.writeRequest("GET_PARAMETER",{})}async describe(e){return this.request("DESCRIBE",{...e||{},Accept:"application/sdp"})}async setup(e,t,i){const r={Transport:`RTP/AVP/${i?"UDP":"TCP"};unicast;${i?"client_port":"interleaved"}=${e}-${e+1}`},s=await this.request("SETUP",r,t);if(s.headers.session){const e=function(e){const t={};for(const i of e.split(";")){const[e,r]=i.split("=",2);t[e]=r}return t}(s.headers.session);let t=parseInt(e.timeout);if(t){let e=setInterval((()=>this.writeGetParameter()),1e3*(t-5));this.client.once("close",(()=>clearInterval(e)))}this.session=s.headers.session.split(";")[0]}return s}async play(e="0.000"){const t={Range:`npt=${e}-`};return await this.request("PLAY",t),this.client}async pause(){return await this.request("PAUSE"),this.client}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class z{udpPorts={video:0,audio:0};constructor(e,t,i,r){this.client=e,this.sdp=t,this.udp=i,this.checkRequest=r,this.session=(0,U.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await D(this.client);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await O(this.client,4),t=e.readUInt16BE(2),i=await O(this.client,t),r=e.readUInt8(1);yield{type:r-r%2===this.videoChannel?"video":"audio",rtcp:r%2==1,header:e,packet:i}}}send(e,t){const i=Buffer.alloc(4);i.writeUInt8(36,0),i.writeUInt8(t,1),i.writeUInt16BE(e.length,2),this.client.write(i),this.client.write(Buffer.from(e))}sendUdp(e,t,i){this.udp.send(t,i?e+1:e,"127.0.0.1")}sendVideo(e,t){if(this.udp&&this.udpPorts.video)this.sendUdp(this.udpPorts.video,e,t);else{if(null==this.videoChannel)throw new Error("rtsp videoChannel not set up");this.send(e,t?this.videoChannel+1:this.videoChannel)}}sendAudio(e,t){if(this.udp&&this.udpPorts.audio)this.sendUdp(this.udpPorts.audio,e,t);else{if(null==this.audioChannel)throw new Error("rtsp audioChannel not set up");this.send(e,t?this.audioChannel+1:this.audioChannel)}}options(e,t){const i={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,i)}describe(e,t){const i={};i["Content-Base"]=e,i["Content-Type"]="application/sdp",this.respond(200,"OK",t,i,Buffer.from(this.sdp))}setup(e,t){const i={},r=t.transport;i.Transport=r,i.Session=this.session;let s=H(this.sdp,"audio"),n=H(this.sdp,"video");if(r.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const i=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[o,a,c]=i;s&&e.includes(s.trackId)?this.udpPorts.audio=parseInt(a):n&&e.includes(n.trackId)?this.udpPorts.video=parseInt(a):this.console?.warn("unknown track id",e)}else if(r.includes("TCP")){const t=r.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);s&&e.includes(s.trackId)?this.audioChannel=i:n&&e.includes(n.trackId)?this.videoChannel=i:this.console?.warn("unknown track id",e)}}this.respond(200,"OK",t,i)}play(e,t){const i={};let r=H(this.sdp,"audio"),s=H(this.sdp,"video"),n="";r&&(n=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&s&&(n+=","),s&&(n+=`url=${e}/trackID=${s.trackId};seq=0;rtptime=0`),i["RTP-Info"]=n,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),r=await O(this.client,i);this.sdp=r.toString();const s={};s.Session=this.session,this.respond(200,"OK",t,s)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async teardown(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=W(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,i,r,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",r,{}),this.client.destroy(),new Error("check request failed")}if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,s){let n=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),s&&(r["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(r))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&this.client.write(s)}}const{systemManager:J}=t();class Q{values={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const i=t[e],r=()=>this.getItem(e);let s;s="clippath"!==i.type?r:()=>{try{return JSON.parse(r())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)})}}async getSettings(){const e=[];for(const[t,i]of Object.entries(this.settings)){let r=Object.assign({},i);r.onGet&&(r=Object.assign(r,await r.onGet())),r.hide||await(this.options?.hide?.[t]?.())||(r.key=t,r.value=this.getItem(t),e.push(r),delete r.onPut,delete r.onGet,delete r.mapPut)}return e}async putSetting(t,i){const r=this.settings[t];let s;r&&(s=this.getItem(t)),r?.noStore||(r.mapPut&&(i=r.mapPut(s,i)),"object"==typeof i?this.device.storage.setItem(t,JSON.stringify(i)):this.device.storage.setItem(t,i?.toString())),r?.onPut?.(s,i),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItem(e){const t=this.settings[e];return t?function(e,t){const{defaultValue:i}=t,r=t.multiple?"array":t.type;if("boolean"===r)return"true"===e||"false"!==e&&(i||!1);if("number"===r)return parseFloat(e)||i||0;if("integer"===r)return parseInt(e)||i||0;if("array"===r)try{return JSON.parse(e)}catch(e){return[]}if("device"===r)return J.getDeviceById(e);if(e&&t.json)try{return JSON.parse(e)}catch(e){return i}return e||i}(this.device.storage.getItem(e),t):this.device.storage.getItem(e)}}const Y=require("stream"),{mediaManager:X}=t();async function Z(e,t,i,r,s,n){let o=!0;const a=new Y.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=parseInt(i.match(/m=audio.* ([0-9]+)/)?.[1]),d=parseInt(i.match(/m=video.* ([0-9]+)/)?.[1]),u=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!1,t.destroy()};let p,l;t.on("close",u),t.on("error",u),(async()=>{const{resetActivityTimer:e}=M("rtsp",u,a,n?.timeout);for(;;){let i,r;if(s){if(i=await O(t,4),"RTSP"===i.toString()){const e=W(await F(t)),i=parseInt(e["content-length"]);i&&await O(t,i);continue}r=i.readUInt16BE(2)}else i=await O(t,2),r=i.readUInt16BE(0);const n=await O(t,r),o=127&n[1];if(!s){const e=Buffer.alloc(2);e[0]=36,o===c?e[1]=0:o===d&&(e[1]=2),i=Buffer.concat([e,i])}let u;o===c?u="rtp-audio":o===d&&(u="rtp-video");const p={chunks:[i,n],type:u};a.emit("rtsp",p),e()}})().finally(u);const h=H(i,"audio"),m=H(i,"video");if(h){const e=h.section.toLowerCase();e.includes("mpeg4")?p="aac":e.includes("pcm")&&(p="pcm")}return m&&m.section.toLowerCase().includes("h264")&&(l="h264"),{sdp:Promise.resolve([Buffer.from(i)]),inputAudioCodec:p,inputVideoCodec:l,inputVideoResolution:void 0,isActive:()=>o,kill:u,mediaStreamOptions:r,emit(e,t){return a.emit(e,t),this},on(e,t){return a.on(e,t),this},once(e,t){return a.once(e,t),this},removeListener(e,t){return a.removeListener(e,t),this}}}const{mediaManager:ee,log:te,systemManager:ie,deviceManager:re}=t(),se=1e4,ne="prebufferDuration",oe="sendKeyframe",ae="Default",ce="AAC or No Audio",de=`${ce} (Copy)`,ue="Compatible Audio",pe="Other Audio",le=["aac","mp3","mp2","opus"],he="-fflags +genpts",me="Scrypted",fe="FFmpeg",ge="Default",ye=[ce,ue,pe],ve=["mpegts","mp4","rtsp"];class Se{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;needBitrateReset=!1;constructor(e,t,i){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=i,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const r="rtspServerPathKey-"+this.streamId;this.maxBitrateKey="maxBitrate-"+this.streamId,this.rtspServerPath=this.storage.getItem(r),this.rtspServerPath||(this.rtspServerPath=j().randomBytes(8).toString("hex"),this.storage.setItem(r,this.rtspServerPath))}get maxBitrate(){let e=parseInt(this.storage.getItem(this.maxBitrateKey));return e||(e=this.advertisedMediaStreamOptions?.video?.maxBitrate,this.storage.setItem(this.maxBitrateKey,e?.toString())),e||void 0}async resetBitrate(){this.console.log("Resetting bitrate after adaptive streaming session",this.maxBitrate),this.needBitrateReset=!1,this.mixinDevice.setVideoStreamOptions({id:this.streamId,video:{bitrate:this.maxBitrate}})}get streamId(){return this.advertisedMediaStreamOptions.id}get streamName(){return this.advertisedMediaStreamOptions.name}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";ye.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(ce),i=-1!==e.indexOf(ue),r=-1!==e.indexOf(pe);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:i,compatibleAudio:r,aacAudio:s}=this.getAudioConfig();return i||r||s}getParser(e,t,i){if(!this.canUseRtspParser(t,i))return fe;const r=e&&"scrypted"===i?.tool?me:fe,s=this.storage.getItem(this.rtspParserKey);return s&&s!==ge?s===me?me:s===fe?fe:r:r}getRebroadcastMode(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default",t="MPEG-TS";"scrypted"===this.advertisedMediaStreamOptions?.tool&&this.advertisedMediaStreamOptions?.container?.startsWith("rtsp")&&(t="RTSP"),"Default"===e&&(e=t);const i=e?.startsWith("RTSP");return{defaultMode:t,rtspMode:e?.startsWith("RTSP"),muxingMp4:!i||e?.includes("MP4")}}async getMixinSettings(){const t=[],i=this.parserSession;let r=0,s=0;const{muxingMp4:n,rtspMode:o,defaultMode:a}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunk.chunks)r+=t.byteLength}const c=Date.now()-s,d=Math.round(r/c*8),u=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";t.push({title:"Audio Codec Transcoding",group:u,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)||ae,choices:[ae,de,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"Rebroadcast Container",group:u,description:`Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default mode for this camera is ${a}.`,placeholder:"MPEG-TS",choices:[ge,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||ge});const p=()=>{t.push({title:"FFmpeg Input Arguments Prefix",group:u,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:he,choices:[he,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(n,this.advertisedMediaStreamOptions)&&o&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(o,n,this.advertisedMediaStreamOptions);t.push({key:this.rtspParserKey,group:u,title:"RTSP Parser",description:`Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser is lower latency. The Scrypted Parser is only available when the Audo Codec is not Transcoding and the Rebroadcast Container is RTSP. The default is "${e}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||ge,choices:[ge,fe,me]}),e!==me&&p()}else p();return i?t.push({key:"detectedResolution",group:u,title:"Detected Resolution and Bitrate",readonly:!0,value:`${i?.inputVideoResolution?.[0]||"unknown"} @ ${d||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:u,title:"Detected Video/Audio Codecs",readonly:!0,value:(i?.inputVideoCodec?.toString()||"unknown")+"/"+(i?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:u,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||"unknown"}):t.push({title:"Status",group:u,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),o&&t.push({group:u,key:"rtspRebroadcastUrl",title:"RTSP Rebroadcast Url",description:"The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.",readonly:!0,value:`rtsp://localhost:${this.mixin.plugin.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`}),this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&t.push({group:u,key:this.maxBitrateKey,title:"Max Bitrate",description:"This camera supports Adaptive Bitrate. Set the maximum bitrate to be allowed while using adaptive bitrate streaming. This will also serve as the default bitrate.",type:"number",value:this.maxBitrate?.toString()}),t}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(ne))||se;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,n=i?.audio?.codec,{isUsingDefaultAudioConfig:o,aacAudio:a,compatibleAudio:d,reencodeAudio:p}=this.getAudioConfig(),{rtspMode:l,muxingMp4:h}=this.getRebroadcastMode();let m=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===m&&(m=null);let f=!1;h&&!r&&!n&&o&&void 0===m&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),f=!0);const g=void 0===m?n?.toLowerCase():m?.toLowerCase(),y=!le.includes(g);!h||f||!1===i?.userConfigurable||r||y&&(o&&te.a(`${this.mixin.name} is using the ${g} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",g));const v=["-bsf:a","aac_adtstoasc"],S=[];let P;this.audioDisabled=!1;const I=null===m;let C=!1;if(!f&&o&&y&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",g):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),C=!0),r||f)P=["-an"],this.audioDisabled=!0;else if(p||C)P=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(a||I)P=["-acodec","copy"],P.push(...v);else if(d)P=["-acodec","copy"],P.push(...S);else{P=["-acodec","copy"];const e="aac"===g?v:S;P.push(...e)}const O=["-vcodec","copy"],T={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=T.parsers,this.console.log("rebroadcast mode:",l?"rtsp":"mpegts"),l){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,U.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];if("rtp-video"===i.type){const r=31&i.chunks[1].readUInt8(12),s=i.chunks[1].readUInt8(13),n=31&s,o=128&s;if((28===r||29===r)&&5===n&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const s=new z(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:O,acodec:o?["-acodec","copy"]:P});this.sdp=e.sdp,T.parsers.rtsp=e}else T.parsers.mpegts={container:"mpegts",outputArguments:[...(D={vcodec:O,acodec:P})?.vcodec||[],...D?.acodec||[],"-f","mpegts"],parse:(E=188,_=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],i=0;for(;;){const r=e.read();if(!r){await(0,s.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<E)continue;const n=Buffer.concat(t);_?.(n);const o=n.length%E,a=n.slice(0,n.length-o),c=n.slice(n.length-o);t=[c],i=c.length,yield{chunks:[a]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const s=i.chunks[r];let n=0;for(;n+188<s.length;){const i=s.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}};var D,E,_;h&&(T.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=k(e);yield*R(t)},findSyncFrame:A}}({vcodec:O,acodec:P}));const B=await this.mixinDevice.getVideoStream(i),L="x-scrypted/x-rfc4571"===B.mimeType;let j,K;this.storage.removeItem(this.lastDetectedAudioCodecKey);let N=!1;if(l&&L){N=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await ee.convertMediaObjectToJSON(B,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;j=await Z(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return c().connect(parseInt(t.port),t.hostname)}(t),i,r,!1,T),this.sdp=j.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await ee.convertMediaObjectToBuffer(B,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());K=i.mediaStreamOptions;if(this.getParser(l,h,i.mediaStreamOptions)===me){N=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new G(i.url,this.console);try{e.requestTimeout=1e4,await e.options();const t=(await e.describe()).body.toString().trim();this.console.log("sdp",t),this.sdp=Promise.resolve(t);const{audio:s,video:n}=function(e,t=["recvonly","sendrecv"]){return{audio:H(e,"audio",t)?.trackId,video:H(e,"video",t)?.trackId}}(t);let o=0;r||(await e.setup(o,s),o+=2),await e.setup(o,n);const a=await e.play();j=await Z(this.console,a,t,i.mediaStreamOptions,!0,T);const c=j.kill.bind(j);j.kill=async()=>{e.teardown().finally(c),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500),c()}}catch(t){throw e.client.destroy(),t}}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||he;i.inputArguments.unshift(...e.split(" ")),j=await x(i,T)}}N&&h&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||he,r=await ee.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const s=await async function(e,t,i,r){const s=e.slice();s.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),s.unshift("-hide_banner"),w(r,s);const n=u().spawn(await V.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,n),{cp:n,generator:k(n.stdio[3])}}(r.inputArguments,P,O,this.console),n=()=>{s.cp.kill("SIGKILL"),j.kill(),s.generator.throw(new Error("killed"))};if(!j.isActive)return void n();j.once("killed",n);const{resetActivityTimer:o}=M("mp4",n,j,T.timeout);for await(const e of R(s.generator))o(),j.emit("mp4",e)})).catch((()=>{})),!r&&n&&void 0!==j.inputAudioCodec&&j.inputAudioCodec!==n&&this.console.warn("Audio codec plugin reported vs detected mismatch",n,m);const $=i?.video?.codec;if($&&void 0!==j.inputVideoCodec&&j.inputVideoCodec!==$&&this.console.warn("Video codec plugin reported vs detected mismatch",$,j.inputVideoCodec),j.inputAudioCodec?le.includes(j.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",j.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",j.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,j.inputAudioCodec||"null"),"h264"!==j.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),f)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),j.kill(),this.startPrebufferSession();if(this.parserSession=j,re.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),K?.refreshAt){let t,i=K;const r=async()=>{if(!j.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await ee.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,s(i)},s=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};s(i),j.once("killed",(()=>clearTimeout(t)))}j.once("killed",(()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===j&&(this.parserSession=void 0)}));for(const i of ve){let r=0;j.on(i,(s=>{const n=this.prebuffers[i],o=Date.now(),a=()=>{if(this.prevIdr){const t="number"!=typeof this.detectedIdrInterval;this.detectedIdrInterval=o-this.prevIdr,t&&re.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}this.prevIdr=o};if("mdat"===s.type&&a(),"rtp-video"===s.type){const e=31&s.chunks[1].readUInt8(12),t=s.chunks[1].readUInt8(13),i=31&t,r=128&t;(28!==e&&29!==e||5!==i||128!=r)&&5!=e||a()}for(n.push({time:o,chunk:s});n.length&&n[0].time<o-t;)n.shift(),r++;r>1e3&&(this.prebuffers[i]=n.slice(),r=0)}))}return j}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t){this.printActiveClients(),this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.inactivityTimeout=void 0,this.activeClients?this.console.log("inactivity timeout found active clients."):(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),t.kill())}),3e4)))}async handleRebroadcasterClient(e){const{isActiveClient:t,container:i,session:r,socketPromise:s,requestedPrebuffer:n}=e;C(s,{connect:(e,s)=>{t&&(this.activeClients++,this.printActiveClients());const o=Date.now(),a=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),r.removeListener(i,a),r.removeListener("killed",c)};r.on(i,a),r.once("killed",c);const d=this.prebuffers[i];for(const e of d)e.time<o-n||a(e.chunk);return()=>{t&&(this.activeClients--,this.inactivityCheck(r)),c()}}})}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(oe);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:s}=this.getRebroadcastMode(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;e?.prebuffer&&"mp4"!==o&&"mp4"===e?.container&&(r+=1.5*(this.detectedIdrInterval||4e3));const a=Object.assign({},t.mediaStreamOptions);a.prebuffer=r;const{reencodeAudio:c}=this.getAudioConfig();let d=!1;this.audioDisabled?a.audio=null:c?a.audio={codec:"aac",encoder:"aac",profile:"aac_low"}:d=!0,d&&(a.audio=t?.mediaStreamOptions?.audio?.codec===t?.inputAudioCodec?t?.mediaStreamOptions?.audio:{codec:t?.inputAudioCodec}),a.video||(a.video={}),a.video.codec=t.inputVideoCodec,t.inputVideoResolution?.[2]&&t.inputVideoResolution?.[3]&&Object.assign(a.video,{width:parseInt(t.inputVideoResolution[2]),height:parseInt(t.inputVideoResolution[3])});const u=Date.now();let p=0;const l=this.prebuffers[o];for(const e of l)if(!(e.time<u-r))for(const t of e.chunk.chunks)p+=t.length;const h=Math.max(5e5,p).toString(),m=await(async i=>{let s,n;if("rtsp"===i){const e=await g();s=e.clientPromise.then((async e=>{let t=await this.sdp;t=L(t);const i=new z(e,t);return await i.handlePlayback(),e})),n=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,n=`tcp://127.0.0.1:${e.port}`}const o=!1!==e?.refresh;return this.handleRebroadcasterClient({isActiveClient:o,container:i,requestedPrebuffer:r,socketPromise:s,session:t}),n})(o),f={url:m,container:o,inputArguments:["-analyzeduration","0","-probesize",h,...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",m],mediaStreamOptions:a};return ee.createFFmpegMediaObject(f)}}class be extends o{released=!1;sessions=new Map;constructor(e,t){super(t),this.plugin=e,this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=e?.id;let i=this.sessions.get(t);return!i||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 t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t),r=i?i.map((e=>e.id)):[void 0],n=t?.map((e=>e.id))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){const e=t?.find((e=>"cloud"===e.source));e&&(this.storage.setItem("warnedCloud","true"),te.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 o=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery),a=this.storage.getItem("defaultStream");let c,d=0;for(const e of n){let i=this.sessions.get(e);if(!i){const n=t?.find((t=>t.id===e));n?.prebuffer&&te.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const u=n?.name,p=r.includes(e);if(i=new Se(this,n,o||!p),this.sessions.set(e,i),n?.name===a&&(c=i),o){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(!p){this.console.log("stream",u,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;d++,e=!0,this.online=!!d,await(0,s.once)(t,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&d--,e=!1,this.online=!!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)")})()}}c||(c=r?.length?this.sessions.get(t?.find((e=>e.id===r[0]))?.id):this.sessions.get(t?.find((e=>e.id===n?.[0]))?.id)),c?(this.sessions.set(void 0,c),this.console.log("Default Stream:",c.advertisedMediaStreamOptions.id,c.advertisedMediaStreamOptions.name)):this.console.warn("Unable to find Default Stream?"),re.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);t?.length>0&&e.push({title:"Default Stream",description:"The default stream to use when not specified.",key:"defaultStream",value:this.storage.getItem("defaultStream")||i?.[0]?.name||t[0].name,choices:t.map((e=>e.name))},{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:ne,value:this.storage.getItem(ne)||se.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:oe,value:("false"!==this.storage.getItem(oe)).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())e?.parserSessionPromise?.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(ne))||se;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}setVideoStreamOptions(e){const t=this.sessions.get(e.id);if(t&&e?.video?.bitrate){t.needBitrateReset=!0;const i=t.maxBitrate;i&&e?.video?.bitrate>i&&(this.console.log("clamping max bitrate request",e.video.bitrate,i),e.video.bitrate=i)}return this.mixinDevice.setVideoStreamOptions(e)}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const we=new class extends B{storageSettings=new Q(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});currentMixins=new Map;constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(ie.getSystemState())){const t=ie.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));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((()=>re.requestRestart()),i),this.startRtspServer()}startRtspServer(){!async function(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}(this.rtspServer),this.rtspServer=new(c().Server)((async e=>{let t;const i=new z(e,void 0,void 0,(async(e,r,s,n)=>{i.checkRequest=void 0;const o=new URL(r);for(const e of this.currentMixins.keys()){const r=this.currentMixins.get(e);for(const e of r.sessions.values())if(o.pathname.endsWith(e.rtspServerPath))return i.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,i.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),i.console=this.console;try{await i.handlePlayback();const r=await t.parserSessionPromise,s=1.5*Math.max(4e3,t.detectedIdrInterval||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:r,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await i.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:s,sdp:n}=r,{audioPayloadTypes:o,videoPayloadTypes:a}=function(e){const t=new Set,i=new Set,r=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},s=e.match(/m=audio.*/)?.[0];r(t,s?.split(" ").slice(3));const n=e.match(/m=video.*/)?.[0];return r(i,n?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(n),d=new URL(s);if(!d.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:u,url:p}=await g(),l={url:p,inputArguments:["-rtsp_transport","tcp","-i",p.replace("tcp","rtsp")]};return u.then((async e=>{const t=new z(e,n);await t.handlePlayback();const i=c().connect(parseInt(d.port),d.hostname);for(e.on("close",(()=>{i.destroy()})),i.on("close",(()=>{e.destroy()}));;){const r=(await O(i,2)).readInt16BE(0),s=await O(i,r),n=127&s[1];if(o.has(n))t.sendAudio(s,!1);else{if(!a.has(n))throw e.destroy(),i.destroy(),new Error("unknown payload type "+n);t.sendVideo(s,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){if(!i.includes(e.ScryptedInterface.VideoCamera))return null;const r=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online];return i.includes(e.ScryptedInterface.VideoCameraConfiguration)&&r.push(e.ScryptedInterface.VideoCameraConfiguration),r}async getMixin(e,t,i){this.setHasEnabledMixin(i.id);const r=new be(this,{mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"});return this.currentMixins.set(i.id,r),r}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in r)s[n]=r[n];r.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
1
+ (()=>{var e={747:(e,t,i)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return n.default}});var r=a(i(945)),s=a(i(816)),n=a(i(243)),o=a(i(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:i}=c.decodeHash(e);return{hash:e,username:t,password:i}},buildAuthorizationRest:function({hash:e,username:t,password:i}){if(t&&i)return c.computeHash({username:t,password:i});if(!e)throw new s.default("E_NO_HASH");return e},computeHash:function({username:e,password:t}){return Buffer.from(e+":"+t).toString("base64")},decodeHash:function(e){const[t,...i]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:i.join(":")}}};var d=c;t.default=d},828:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=i(761),n=(r=i(113))&&r.__esModule?r:{default:r};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const i=n.default.createHash(e);return i.update(t),i.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),i=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};t.default=l},761:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){const r=e.trim().match(n);if(!r)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new s.default("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(i,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){return o(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")};var r,s=(r=i(945))&&r.__esModule?r:{default:r};const n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,i){"use strict";var r=this&&this.__createBinding||(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]}),s=this&&this.__exportStar||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,s(i(268),t);const n=i(268);class o 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=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}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=a,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(o.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(a.prototype,i,{set:t(i),get:e(i)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=c},268:(e,t,i)=>{"use strict";i.r(t),i.d(t,{DeviceBase:()=>r,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>l,MediaPlayerState:()=>p,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let s;!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"}(s||(s={}));const n={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:[]},Microphone:{name:"Microphone",methods:["getAudioStream"],properties:[]},Display:{name:"Display",methods:["startDisplay","stopDisplay"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:[]},VideoClips:{name:"VideoClips",methods:["getVideoClip","getVideoClipThumbnail","getVideoClips","removeVideoClips"],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"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,l,p,h,m;!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.SmartDisplay="SmartDisplay",e.Speaker="Speaker",e.SmartSpeaker="SmartSpeaker",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"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),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"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(l||(l={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(p||(p={})),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.Microphone="Microphone",e.Display="Display",e.VideoCamera="VideoCamera",e.VideoRecorder="VideoRecorder",e.VideoClips="VideoClips",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",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){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.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))},113:e=>{"use strict";e.exports=require("crypto")},945:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>m});const r=require("os");function s(e,t){for(var i=0;i<t.length;i++){var r=t[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(i=e,-1===Function.toString.call(i).indexOf("[native code]")))return e;var i;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return a(e,arguments,u(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),d(r,e)},o(e)}function a(e,t,i){return a=c()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var s=new(Function.bind.apply(e,r));return i&&d(s,i.prototype),s},a.apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function d(e,t){return d=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},d(e,t)}function u(e){return u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},u(e)}let l=function(e){function t(e,i,...r){var s,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(r=(void 0===i?[]:[i]).concat(r),i=e,e=[]),o=this,(s=!(a=u(t).call(this,i))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=i||"E_UNEXPECTED",s.params=r,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var i,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),i=t,(o=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+r.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(i.prototype,o),a&&s(i,a),t}(o(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&h(e.code)&&e.params&&e.params instanceof Array}function h(e){return/^([A-Z0-9_]+)$/.test(e)}l.wrap=function(e,t,...i){let r=null;const s=h(e.message),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&i.push(e.message),r=new l(n,t,...i),r},l.cast=function(e,...t){return p(e)?e:l.wrap.apply(l,[e].concat(t))},l.bump=function(e,...t){return p(e)?l.wrap.apply(l,[e,e.code].concat(e.params)):l.wrap.apply(l,[e].concat(t))};const m=l}},t={};function i(r){var s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,i),n.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";i.r(r),i.d(r,{default:()=>be});var e=i(510),t=i.n(e);const{deviceManager:s}=t();class n extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,i=this.getMixinSettings(),r=[];try{const e=await t||[];r.push(...e)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}try{const e=await i||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=s.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return r}async putSetting(t,i){const r=this.settingsGroupKey+":";if(!t?.startsWith(r))return this.mixinDevice.putSetting(t,i);await this.putMixinSetting(t.substring(r.length),i),s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const o=require("net");var a=i.n(o);const c=require("child_process");var d=i.n(c);const u=require("events"),l=require("dgram");var p=i.n(l);async function h(e,t){e.bind(t),await(0,u.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function m(e){return h(e,0)}async function f(e,t){return e.bind(t),await(0,u.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function g(){const e=new(a().Server),t=await async function(e){return e.listen(0),await(0,u.once)(e,"listening"),e.address().port}(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{server:e,url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:i}}const y=require("process");var v=i.n(y);const S=["decode_slice_header error","no frame!","non-existing PPS"];function b(e,t,i,r){const s=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const r=n=>{const o=n.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(s||i||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(o)};return r}t.stdout?.on("data",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];let r=!1;for(const e of t){try{if(r){const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}else i.push(e)}catch(t){i.push(e)}r="-i"===e}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(s.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let s;function n(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(s),s=setTimeout(n,r))}return i.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o}}async function x(e,t){const{console:i}=t;let r,s=!0;const n=new u.EventEmitter;let o,a,c,l,h;n.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{l=e,h=t}));let v;const S=new Promise((e=>{v=e}));function x(){s&&(n.emit("killed"),n.emit("error",new Error("killed"))),s=!1,clearTimeout(r),v(),h?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),function(e){e&&(e.stdin.write("q\n"),setTimeout((()=>{e.kill(),setTimeout((()=>{e.kill("SIGKILL")}),2e3)}),2e3))}(k)}const C=e.inputArguments.slice();r=setTimeout(x,3e4);let O=!1;const T=e=>{if(!s)throw e(),new Error("parser session was killed killed before ffmpeg connected");n.on("killed",e)},D=["pipe","pipe","pipe"];let A=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){O=!0;const i=p().createSocket("udp4"),s=await m(i),o=p().createSocket("udp4");await f(o,s.port+1),T((()=>{i.close(),o.close()})),C.push(...r.outputArguments,s.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=M(e,x,n,t?.timeout);(async()=>{for await(const t of r.parseDatagram(i,parseInt(c?.[2]),parseInt(c?.[3])))l?.(void 0),n.emit(e,t),a()})(),(async()=>{for await(const t of r.parseDatagram(o,parseInt(c?.[2]),parseInt(c?.[3]),"rtcp"))l?.(void 0),n.emit(e,t),a()})()}else if(r.tcpProtocol){const s=await g(),o=new URL(r.tcpProtocol);o.port=s.port.toString(),C.push(...r.outputArguments,o.toString());const{resetActivityTimer:a}=M(e,x,n,t?.timeout);(async()=>{const t=await s.clientPromise;try{T((()=>t.destroy()));for await(const i of r.parse(t,parseInt(c?.[2]),parseInt(c?.[3])))l?.(void 0),n.emit(e,i),a()}catch(e){i.error("rebroadcast parse error",e),x()}})()}else C.push(...r.outputArguments,"pipe:"+A++),D.push("pipe")}O&&(C.push("-sdp_file","pipe:"+A++),D.push("pipe")),C.unshift("-hide_banner"),w(i,C);const k=d().spawn(await P.getFFmpegPath(),C,{stdio:D});let E;b(i,k,void 0,t?.storage),k.on("exit",x),E=O?new Promise((e=>{const t=[];k.stdio[A-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let R=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const s=k.stdio[3+R];R++;try{const{resetActivityTimer:i}=M(e,x,n,t?.timeout);for await(const t of r.parse(s,parseInt(c?.[2]),parseInt(c?.[3])))l?.(void 0),n.emit(e,t),i()}catch(e){i.error("rebroadcast parse error",e),x()}})),async function(e){return I(e,"Audio")}(k).then((e=>o=e)),async function(e){return I(e,"Video")}(k).then((e=>a=e)),async function(e){return new Promise((t=>{const i=r=>{const s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(k).then((e=>c=e)),await y,l=void 0,h=void 0,clearTimeout(r),{sdp:E,inputAudioCodec:o,inputVideoCodec:a,inputVideoResolution:c,get isActive(){return s},kill:x,killed:S,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},emit(e,t){return n.emit(e,t),this},on(e,t){return n.on(e,t),this},once(e,t){return n.once(e,t),this},removeListener(e,t){return n.removeListener(e,t),this}}}async function C(e,t){const i=await e;let r=!0;const s=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,e?.()};let n=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),s);i.once("close",(()=>{s()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function O(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const s=()=>{const r=e.read(t);r&&(o(),i(r))},n=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const T="\n".charCodeAt(0);async function D(e){return async function(e,t){const i=[];let r=0;for(;;){const s=await O(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;i[r++]=s[0]}return Buffer.from(i).toString()}(e,T)}function A(e){return e}async function*k(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),s=await O(e,i);yield{header:t,length:i,type:r,data:s}}}async function*E(e){let t,i,r;for await(const s of e)t?i||(i=s):t=s,yield{startStream:r,chunks:[s.header,s.data],type:s.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:R}=t(),_="v4";class B extends e.ScryptedDeviceBase{hasEnabledMixin={};unshiftMixin=!1;constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=R.getComponent("plugins"),R.listen((async(t,i,r)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(R.getSystemState())){const t=R.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){if(!e||e.mixins?.includes(this.id))return;if(this.hasEnabledMixin[e.id]===_)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 t=(e.mixins||[]).slice();this.unshiftMixin?t.unshift(this.id):t.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==_&&(this.hasEnabledMixin[e]=_,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const{mediaManager:V}=t();function L(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));const i=t.findIndex((e=>e.startsWith("m=video")));-1!==i&&t.splice(i+1,0,"a=control:trackID=video");const r=t.findIndex((e=>e.startsWith("m=audio")));return-1!==r&&t.splice(r+1,0,"a=control:trackID=audio"),t.join("\r\n")}function H(e,t,i=["recvonly","sendrecv"]){const r=e.split("m=").filter((e=>e.startsWith(t)));for(const e of r){const t=()=>{const t=e.split("\n").map((e=>e.trim())).find((e=>e.startsWith("a=control:")));return{section:"m="+e,trackId:t?.split("a=control:")?.[1]}};for(const r of i)if(e.includes(`a=${r}`))return t();if(i.includes("recvonly")&&!e.includes("sendonly")&&!e.includes("inactive"))return t()}}const U=require("stream");var j=i(113),K=i.n(j);const N=require("tls");var $=i.n(N),q=i(747);class F extends Error{constructor(){super("Operation Timed Out")}}function W(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=r}return t}class G extends class{constructor(e){this.console=e}write(e,t,i){let r=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))r+=`${e}: ${i}\r\n`;r+="\r\n",this.client.write(r),this.console?.log("rtsp outgoing message\n",r),this.console?.log(),i&&this.client.write(i)}async readMessage(){const e=await async function(e){let t=[];for(;;){let i=await D(e);if(i=i.trim(),!i)return t;t.push(i)}}(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;rfc4571=new U.PassThrough;needKeepAlive=!1;constructor(e,t){super(t),this.url=e;const i=new URL(e),r=parseInt(i.port)||554;e.startsWith("rtsps")?this.client=$().connect({rejectUnauthorized:!1,port:r,host:i.hostname}):this.client=a().connect(r,i.hostname)}writeRequest(e,t,i,r){t=t||{};let s=this.url;i&&(s+="/"+i);const n=new URL(s);n.username="",n.password="",s=n.toString();const o=`${e} ${s} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),this.authorization&&(t.Authorization=this.authorization),this.session&&(t.Session=this.session),this.write(o,t,r)}async handleDataPayload(e){if(36!==e[0])throw new Error("RTSP Client expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),i=await O(this.client,t);this.rfc4571.push(e),this.rfc4571.push(i)}async readDataPayload(){const e=await O(this.client,4);return this.handleDataPayload(e)}async readLoop(){try{for(;;)this.needKeepAlive&&(this.needKeepAlive=!1,await this.getParameter()),await this.readDataPayload()}catch(e){throw this.client.destroy(e),this.rfc4571.destroy(e),e}}async readMessage(){for(;;){const e=await O(this.client,4);if("RTSP"===e.toString()){this.client.unshift(e);const t=await super.readMessage();return this.console?.log("rtsp incoming message\n",t.join("\n")),this.console?.log(),t}await this.handleDataPayload(e)}}async request(e,t,i,r,s){this.writeRequest(e,t,i,r);const n=this.requestTimeout?await(o=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new F)),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=W(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=d["www-authenticate"];if(u){if(s)throw new Error("auth failed");const n=new URL(this.url);if(u.includes("Basic")){const e=q.rh.computeHash(n);this.authorization=`Basic ${e}`}else{const t=q.Nu.parseWWWAuthenticateRest(u),i=decodeURIComponent(n.username),r=decodeURIComponent(n.password),s=K().createHash("md5").update(`${i}:${t.realm}:${r}`).digest("hex"),o=K().createHash("md5").update(`${e}:${n.pathname}`).digest("hex"),a=K().createHash("md5").update(`${s}:${t.nonce}:${o}`).digest("hex"),c={username:i,realm:t.realm,nonce:t.nonce,uri:n.pathname,algorithm:"MD5",response:a},d=Object.entries(c).map((([e,t])=>{return`${e}=${t&&(i=t,`"${i.replace(/"/g,'\\"')}"`)}`;var i})).join(", ");this.authorization=`Digest ${d}`}return this.request(e,t,i,r,!0)}const l=parseInt(d["content-length"]);return l?{headers:d,body:await O(this.client,l)}:{headers:d,body:void 0}}async options(){return this.request("OPTIONS",{})}async getParameter(){return this.request("GET_PARAMETER")}async describe(e){return this.request("DESCRIBE",{...e||{},Accept:"application/sdp"})}async setup(e,t,i){const r={Transport:`RTP/AVP/${i?"UDP":"TCP"};unicast;${i?"client_port":"interleaved"}=${e}-${e+1}`},s=await this.request("SETUP",r,t);if(s.headers.session){const e=function(e){const t={};for(const i of e.split(";")){const[e,r]=i.split("=",2);t[e]=r}return t}(s.headers.session);let t=parseInt(e.timeout);if(t){let e=setInterval((()=>this.needKeepAlive=!0),1e3*(t-5));this.client.once("close",(()=>clearInterval(e)))}this.session=s.headers.session.split(";")[0]}return s}async play(e="0.000"){const t={Range:`npt=${e}-`};return this.request("PLAY",t)}async pause(){return this.request("PAUSE")}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class z{udpPorts={video:0,audio:0};constructor(e,t,i,r){this.client=e,this.sdp=t,this.udp=i,this.checkRequest=r,this.session=(0,j.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await D(this.client);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await O(this.client,4);if(36!==e[0])throw new Error("RTSP Server expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),i=await O(this.client,t),r=e.readUInt8(1);yield{type:r-r%2===this.videoChannel?"video":"audio",rtcp:r%2==1,header:e,packet:i}}}send(e,t){const i=Buffer.alloc(4);i.writeUInt8(36,0),i.writeUInt8(t,1),i.writeUInt16BE(e.length,2),this.client.write(i),this.client.write(Buffer.from(e))}sendUdp(e,t,i){this.udp.send(t,i?e+1:e,"127.0.0.1")}sendVideo(e,t){if(this.udp&&this.udpPorts.video)this.sendUdp(this.udpPorts.video,e,t);else{if(null==this.videoChannel)throw new Error("rtsp videoChannel not set up");this.send(e,t?this.videoChannel+1:this.videoChannel)}}sendAudio(e,t){if(this.udp&&this.udpPorts.audio)this.sendUdp(this.udpPorts.audio,e,t);else{if(null==this.audioChannel)throw new Error("rtsp audioChannel not set up");this.send(e,t?this.audioChannel+1:this.audioChannel)}}options(e,t){const i={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,i)}describe(e,t){const i={};i["Content-Base"]=e,i["Content-Type"]="application/sdp",this.respond(200,"OK",t,i,Buffer.from(this.sdp))}setup(e,t){const i={},r=t.transport;i.Transport=r,i.Session=this.session;let s=H(this.sdp,"audio"),n=H(this.sdp,"video");if(r.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const i=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[o,a,c]=i;s&&e.includes(s.trackId)?this.udpPorts.audio=parseInt(a):n&&e.includes(n.trackId)?this.udpPorts.video=parseInt(a):this.console?.warn("unknown track id",e)}else if(r.includes("TCP")){const t=r.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);s&&e.includes(s.trackId)?this.audioChannel=i:n&&e.includes(n.trackId)?this.videoChannel=i:this.console?.warn("unknown track id",e)}}this.respond(200,"OK",t,i)}play(e,t){const i={};let r=H(this.sdp,"audio"),s=H(this.sdp,"video"),n="";r&&(n=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&s&&(n+=","),s&&(n+=`url=${e}/trackID=${s.trackId};seq=0;rtptime=0`),i["RTP-Info"]=n,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),r=await O(this.client,i);this.sdp=r.toString();const s={};s.Session=this.session,this.respond(200,"OK",t,s)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async teardown(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=W(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,i,r,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",r,{}),this.client.destroy(),new Error("check request failed")}if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,s){let n=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),s&&(r["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(r))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&this.client.write(s)}}const{systemManager:J}=t();class Q{values={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const i=t[e],r=()=>this.getItem(e);let s;s="clippath"!==i.type?r:()=>{try{return JSON.parse(r())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)})}}async getSettings(){const e=[];for(const[t,i]of Object.entries(this.settings)){let r=Object.assign({},i);r.onGet&&(r=Object.assign(r,await r.onGet())),r.hide||await(this.options?.hide?.[t]?.())||(r.key=t,r.value=this.getItem(t),e.push(r),delete r.onPut,delete r.onGet,delete r.mapPut,delete r.mapGet)}return e}async putSetting(t,i){const r=this.settings[t];let s;r&&(s=this.getItem(t)),r?.noStore||(r.mapPut&&(i=r.mapPut(s,i)),"object"==typeof i?this.device.storage.setItem(t,JSON.stringify(i)):this.device.storage.setItem(t,i?.toString())),r?.onPut?.(s,i),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItem(e){const t=this.settings[e];if(!t)return this.device.storage.getItem(e);const i=function(e,t){const{defaultValue:i}=t,r=t.multiple?"array":t.type;if("boolean"===r)return"true"===e||"false"!==e&&(i||!1);if("number"===r)return parseFloat(e)||i||0;if("integer"===r)return parseInt(e)||i||0;if("array"===r)try{return JSON.parse(e)}catch(e){return[]}if("device"===r)return J.getDeviceById(e);if(e&&t.json)try{return JSON.parse(e)}catch(e){return i}return e||i}(this.device.storage.getItem(e),t);return t.mapGet?t.mapGet(i):i}}const{mediaManager:Y}=t();async function X(e,t,i,r,s,n){let o=!0;const a=new U.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=parseInt(i.match(/m=audio.* ([0-9]+)/)?.[1]),d=parseInt(i.match(/m=video.* ([0-9]+)/)?.[1]);let u;const l=new Promise((e=>{u=e})),p=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!1,u(),t.destroy()};let h,m;t.on("close",p),t.on("error",p),(async()=>{const{resetActivityTimer:e}=M("rtsp",p,a,n?.timeout);for(;;){let i,r;s?(i=await O(t,4),r=i.readUInt16BE(2)):(i=await O(t,2),r=i.readUInt16BE(0));const n=await O(t,r),o=127&n[1];if(!s){const e=Buffer.alloc(2);e[0]=36,o===c?e[1]=0:o===d&&(e[1]=2),i=Buffer.concat([e,i])}let u;o===c?u="rtp-audio":o===d&&(u="rtp-video");const l={chunks:[i,n],type:u};a.emit("rtsp",l),e()}})().finally(p);const f=H(i,"audio"),g=H(i,"video");if(f){const e=f.section.toLowerCase();e.includes("mpeg4")?h="aac":e.includes("pcm")&&(h="pcm")}return g&&g.section.toLowerCase().includes("h264")&&(m="h264"),{sdp:Promise.resolve([Buffer.from(i)]),inputAudioCodec:h,inputVideoCodec:m,inputVideoResolution:void 0,get isActive(){return o},kill:p,killed:l,mediaStreamOptions:r,emit(e,t){return a.emit(e,t),this},on(e,t){return a.on(e,t),this},once(e,t){return a.once(e,t),this},removeListener(e,t){return a.removeListener(e,t),this}}}const{mediaManager:Z,log:ee,systemManager:te,deviceManager:ie}=t(),re=1e4,se="prebufferDuration",ne="sendKeyframe",oe="Default",ae="AAC or No Audio",ce=`${ae} (Copy)`,de="Compatible Audio",ue="Other Audio",le=["aac","mp3","mp2","opus"],pe="-fflags +genpts",he="Scrypted",me="FFmpeg",fe="Default",ge=[ae,de,ue],ye=["mpegts","mp4","rtsp"];class ve{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;needBitrateReset=!1;constructor(e,t,i){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=i,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const r="rtspServerPathKey-"+this.streamId;this.maxBitrateKey="maxBitrate-"+this.streamId,this.rtspServerPath=this.storage.getItem(r),this.rtspServerPath||(this.rtspServerPath=K().randomBytes(8).toString("hex"),this.storage.setItem(r,this.rtspServerPath))}get maxBitrate(){let e=parseInt(this.storage.getItem(this.maxBitrateKey));return e||(e=this.advertisedMediaStreamOptions?.video?.maxBitrate,this.storage.setItem(this.maxBitrateKey,e?.toString())),e||void 0}async resetBitrate(){this.console.log("Resetting bitrate after adaptive streaming session",this.maxBitrate),this.needBitrateReset=!1,this.mixinDevice.setVideoStreamOptions({id:this.streamId,video:{bitrate:this.maxBitrate}})}get streamId(){return this.advertisedMediaStreamOptions.id}get streamName(){return this.advertisedMediaStreamOptions.name}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";ge.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(ae),i=-1!==e.indexOf(de),r=-1!==e.indexOf(ue);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:i,compatibleAudio:r,aacAudio:s}=this.getAudioConfig();return i||r||s}getParser(e,t,i){if(!this.canUseRtspParser(t,i))return me;const r=e&&"scrypted"===i?.tool?he:me,s=this.storage.getItem(this.rtspParserKey);return s&&s!==fe?s===he?he:s===me?me:r:r}getRebroadcastMode(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default",t="MPEG-TS";"scrypted"===this.advertisedMediaStreamOptions?.tool&&this.advertisedMediaStreamOptions?.container?.startsWith("rtsp")&&(t="RTSP"),"Default"===e&&(e=t);const i=e?.startsWith("RTSP");return{defaultMode:t,rtspMode:e?.startsWith("RTSP"),muxingMp4:!i||e?.includes("MP4")}}async getMixinSettings(){const t=[],i=this.parserSession;let r=0,s=0;const{muxingMp4:n,rtspMode:o,defaultMode:a}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunk.chunks)r+=t.byteLength}const c=Date.now()-s,d=Math.round(r/c*8),u=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";t.push({title:"Audio Codec Transcoding",group:u,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)||oe,choices:[oe,ce,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"Rebroadcast Container",group:u,description:`Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default mode for this camera is ${a}.`,placeholder:"MPEG-TS",choices:[fe,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||fe});const l=()=>{t.push({title:"FFmpeg Input Arguments Prefix",group:u,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:pe,choices:[pe,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(n,this.advertisedMediaStreamOptions)&&o&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(o,n,this.advertisedMediaStreamOptions);t.push({key:this.rtspParserKey,group:u,title:"RTSP Parser",description:`Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser is lower latency. The Scrypted Parser is only available when the Audo Codec is not Transcoding and the Rebroadcast Container is RTSP. The default is "${e}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||fe,choices:[fe,me,he]}),e!==he&&l()}else l();return i?t.push({key:"detectedResolution",group:u,title:"Detected Resolution and Bitrate",readonly:!0,value:`${i?.inputVideoResolution?.[0]||"unknown"} @ ${d||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:u,title:"Detected Video/Audio Codecs",readonly:!0,value:(i?.inputVideoCodec?.toString()||"unknown")+"/"+(i?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:u,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||"unknown"}):t.push({title:"Status",group:u,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),o&&t.push({group:u,key:"rtspRebroadcastUrl",title:"RTSP Rebroadcast Url",description:"The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.",readonly:!0,value:`rtsp://localhost:${this.mixin.plugin.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`}),this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&t.push({group:u,key:this.maxBitrateKey,title:"Max Bitrate",description:"This camera supports Adaptive Bitrate. Set the maximum bitrate to be allowed while using adaptive bitrate streaming. This will also serve as the default bitrate.",type:"number",value:this.maxBitrate?.toString()}),t}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(se))||re;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,s=i?.audio?.codec,{isUsingDefaultAudioConfig:n,aacAudio:o,compatibleAudio:c,reencodeAudio:l}=this.getAudioConfig(),{rtspMode:p,muxingMp4:h}=this.getRebroadcastMode();let m=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===m&&(m=null);let f=!1;h&&!r&&!s&&n&&void 0===m&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),f=!0);const g=void 0===m?s?.toLowerCase():m?.toLowerCase(),y=!le.includes(g);!h||f||!1===i?.userConfigurable||r||y&&(n&&ee.a(`${this.mixin.name} is using the ${g} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",g));const v=["-bsf:a","aac_adtstoasc"],S=[];let P;this.audioDisabled=!1;const I=null===m;let C=!1;if(!f&&n&&y&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",g):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),C=!0),r||f)P=["-an"],this.audioDisabled=!0;else if(l||C)P=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(o||I)P=["-acodec","copy"],P.push(...v);else if(c)P=["-acodec","copy"],P.push(...S);else{P=["-acodec","copy"];const e="aac"===g?v:S;P.push(...e)}const O=["-vcodec","copy"],T={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=T.parsers,this.console.log("rebroadcast mode:",p?"rtsp":"mpegts"),p){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,j.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];if("rtp-video"===i.type){const r=31&i.chunks[1].readUInt8(12),s=i.chunks[1].readUInt8(13),n=31&s,o=128&s;if((28===r||29===r)&&5===n&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const s=new z(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:O,acodec:n?["-acodec","copy"]:P});this.sdp=e.sdp,T.parsers.rtsp=e}else T.parsers.mpegts={container:"mpegts",outputArguments:[...(D={vcodec:O,acodec:P})?.vcodec||[],...D?.acodec||[],"-f","mpegts"],parse:(R=188,_=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],i=0;for(;;){const r=e.read();if(!r){await(0,u.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<R)continue;const s=Buffer.concat(t);_?.(s);const n=s.length%R,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],i=a.length,yield{chunks:[o]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const s=i.chunks[r];let n=0;for(;n+188<s.length;){const i=s.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}};var D,R,_;h&&(T.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=k(e);yield*E(t)},findSyncFrame:A}}({vcodec:O,acodec:P}));const B=await this.mixinDevice.getVideoStream(i),L="x-scrypted/x-rfc4571"===B.mimeType;let U,K;this.storage.removeItem(this.lastDetectedAudioCodecKey);let N=!1;if(p&&L){N=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Z.convertMediaObjectToJSON(B,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;U=await X(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return a().connect(parseInt(t.port),t.hostname)}(t),i,r,!1,T),this.sdp=U.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Z.convertMediaObjectToBuffer(B,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());K=i.mediaStreamOptions;if(this.getParser(p,h,i.mediaStreamOptions)===he){N=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new G(i.url,this.console);try{e.requestTimeout=1e4,await e.options();const t=(await e.describe()).body.toString().trim();this.console.log("sdp",t),this.sdp=Promise.resolve(t);const{audio:s,video:n}=function(e,t=["recvonly","sendrecv"]){return{audio:H(e,"audio",t)?.trackId,video:H(e,"video",t)?.trackId}}(t);let o=0;r||(await e.setup(o,s),o+=2),await e.setup(o,n),await e.play(),U=await X(this.console,e.rfc4571,t,i.mediaStreamOptions,!0,T);const a=U.kill.bind(U);let c=!1;if(U.kill=async()=>{c||(c=!0,e.teardown().finally(a)),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500),e.client.destroy(),a()},!U.isActive)throw new Error("parser was killed before rtsp client started");e.readLoop().finally((()=>U.kill()))}catch(t){throw e.client.destroy(),t}}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||pe;i.inputArguments.unshift(...e.split(" ")),U=await x(i,T)}}N&&h&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||pe,r=await Z.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const s=await async function(e,t,i,r){const s=e.slice();s.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),s.unshift("-hide_banner"),w(r,s);const n=d().spawn(await V.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,n),{cp:n,generator:k(n.stdio[3])}}(r.inputArguments,P,O,this.console),n=()=>{s.cp.kill("SIGKILL"),U.kill(),s.generator.throw(new Error("killed"))};if(!U.isActive)return void n();U.killed.finally(n);const{resetActivityTimer:o}=M("mp4",n,U,T.timeout);for await(const e of E(s.generator))o(),U.emit("mp4",e)})).catch((()=>{})),!r&&s&&void 0!==U.inputAudioCodec&&U.inputAudioCodec!==s&&this.console.warn("Audio codec plugin reported vs detected mismatch",s,m);const $=i?.video?.codec;if($&&void 0!==U.inputVideoCodec&&U.inputVideoCodec!==$&&this.console.warn("Video codec plugin reported vs detected mismatch",$,U.inputVideoCodec),U.inputAudioCodec?le.includes(U.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",U.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,U.inputAudioCodec||"null"),"h264"!==U.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),f)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),U.kill(),this.startPrebufferSession();if(this.parserSession=U,ie.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),K?.refreshAt){let t,i=K;const r=async()=>{if(!U.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await Z.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,s(i)},s=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};s(i),U.killed.finally((()=>clearTimeout(t)))}U.killed.finally((()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===U&&(this.parserSession=void 0)}));for(const i of ye){let r=0;U.on(i,(s=>{const n=this.prebuffers[i],o=Date.now(),a=()=>{if(this.prevIdr){const t="number"!=typeof this.detectedIdrInterval;this.detectedIdrInterval=o-this.prevIdr,t&&ie.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}this.prevIdr=o};if("mdat"===s.type&&a(),"rtp-video"===s.type){const e=31&s.chunks[1].readUInt8(12),t=s.chunks[1].readUInt8(13),i=31&t,r=128&t;(28!==e&&29!==e||5!==i||128!=r)&&5!=e||a()}for(n.push({time:o,chunk:s});n.length&&n[0].time<o-t;)n.shift(),r++;r>1e3&&(this.prebuffers[i]=n.slice(),r=0)}))}return U}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t,i){this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(this.inactivityTimeout&&!i||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.inactivityTimeout=void 0,this.activeClients?this.console.log("inactivity timeout found active clients."):(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),t.kill())}),3e4))))}async handleRebroadcasterClient(e){const{isActiveClient:t,container:i,session:r,socketPromise:s,requestedPrebuffer:n}=e;C(s,{connect:(e,s)=>{t&&(this.activeClients++,this.printActiveClients());const o=Date.now(),a=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{r.removeListener(i,a),r.removeListener("killed",c),s()};r.on(i,a),r.once("killed",c);const d=this.prebuffers[i];for(const e of d)e.time<o-n||a(e.chunk);return()=>{t&&(this.activeClients--,this.printActiveClients()),this.inactivityCheck(r,t),c()}}})}async getVideoStream(e){if(!1===e?.refresh&&!this.parserSessionPromise)throw new Error("Stream is currently unavailable and will not be started for this request. RequestMediaStreamOptions.refresh === false");this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(ne);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:s}=this.getRebroadcastMode(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;e?.prebuffer&&"mp4"!==o&&"mp4"===e?.container&&(r+=1.5*(this.detectedIdrInterval||4e3));const a=Object.assign({},t.mediaStreamOptions);a.prebuffer=r;const{reencodeAudio:c}=this.getAudioConfig();let d=!1;this.audioDisabled?a.audio=null:c?a.audio={codec:"aac",encoder:"aac",profile:"aac_low"}:d=!0,d&&(a.audio=t?.mediaStreamOptions?.audio?.codec===t?.inputAudioCodec?t?.mediaStreamOptions?.audio:{codec:t?.inputAudioCodec}),a.video||(a.video={}),a.video.codec=t.inputVideoCodec,t.inputVideoResolution?.[2]&&t.inputVideoResolution?.[3]&&Object.assign(a.video,{width:parseInt(t.inputVideoResolution[2]),height:parseInt(t.inputVideoResolution[3])});const u=Date.now();let l=0;const p=this.prebuffers[o];for(const e of p)if(!(e.time<u-r))for(const t of e.chunk.chunks)l+=t.length;const h=Math.max(5e5,l).toString(),m=await(async i=>{let s,n;if("rtsp"===i){const e=await g();s=e.clientPromise.then((async e=>{let t=await this.sdp;t=L(t);const i=new z(e,t);return await i.handlePlayback(),e})),n=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,n=`tcp://127.0.0.1:${e.port}`}const o=!1!==e?.refresh;return this.handleRebroadcasterClient({isActiveClient:o,container:i,requestedPrebuffer:r,socketPromise:s,session:t}),n})(o),f={url:m,container:o,inputArguments:["-analyzeduration","0","-probesize",h,...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",m],mediaStreamOptions:a};return Z.createFFmpegMediaObject(f)}}class Se extends n{released=!1;sessions=new Map;constructor(e,t){super(t),this.plugin=e,this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){if(e?.directMediaStream)return this.mixinDevice.getVideoStream(e);await this.ensurePrebufferSessions();const t=e?.id,i=this.sessions.get(t);return i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e)}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t),r=i?i.map((e=>e.id)):[void 0],s=t?.map((e=>e.id))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){const e=t?.find((e=>"cloud"===e.source));e&&(this.storage.setItem("warnedCloud","true"),ee.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 n=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let o=0;for(const e of s){let i=this.sessions.get(e);if(!i){const s=t?.find((t=>t.id===e));s?.prebuffer&&ee.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const a=s?.name,c=r.includes(e);if(i=new ve(this,s,n||!c),this.sessions.set(e,i),n){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(!c){this.console.log("stream",a,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;o++,e=!0,this.online=!!o,await t.killed,this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&o--,e=!1,this.online=!!o}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)")})()}}if(!this.sessions.has(void 0)){const e=this.storage.getItem("defaultStream");let i=this.sessions.get(t?.find((t=>t.name===e))?.id);i||(i=this.sessions.get(t?.find((e=>e.id===r[0]))?.id)),i||(i=this.sessions.get(t?.find((e=>e.id===s?.[0]))?.id)),i?(this.sessions.set(void 0,i),this.console.log("Default Stream:",i.advertisedMediaStreamOptions.id,i.advertisedMediaStreamOptions.name)):this.console.warn("Unable to find Default Stream?")}ie.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);t?.length>0&&e.push({title:"Default Stream",description:"The default stream to use when not specified.",key:"defaultStream",value:this.storage.getItem("defaultStream")||i?.[0]?.name||t[0].name,choices:t.map((e=>e.name))},{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:se,value:this.storage.getItem(se)||re.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:ne,value:("false"!==this.storage.getItem(ne)).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())e?.parserSessionPromise?.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(se))||re;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}setVideoStreamOptions(e){const t=this.sessions.get(e.id);if(t&&e?.video?.bitrate){t.needBitrateReset=!0;const i=t.maxBitrate;i&&e?.video?.bitrate>i&&(this.console.log("clamping max bitrate request",e.video.bitrate,i),e.video.bitrate=i)}return this.mixinDevice.setVideoStreamOptions(e)}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const be=new class extends B{storageSettings=new Q(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});currentMixins=new Map;constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(te.getSystemState())){const t=te.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));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((()=>ie.requestRestart()),i),this.startRtspServer()}startRtspServer(){!async function(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}(this.rtspServer),this.rtspServer=new(a().Server)((async e=>{let t;const i=new z(e,void 0,void 0,(async(e,r,s,n)=>{i.checkRequest=void 0;const o=new URL(r);for(const e of this.currentMixins.keys()){const r=this.currentMixins.get(e);for(const e of r.sessions.values())if(o.pathname.endsWith(e.rtspServerPath))return i.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,i.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),i.console=this.console;try{await i.handlePlayback();const r=await t.parserSessionPromise,s=1.5*Math.max(4e3,t.detectedIdrInterval||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:r,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await i.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:s,sdp:n}=r,{audioPayloadTypes:o,videoPayloadTypes:c}=function(e){const t=new Set,i=new Set,r=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},s=e.match(/m=audio.*/)?.[0];r(t,s?.split(" ").slice(3));const n=e.match(/m=video.*/)?.[0];return r(i,n?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(n),d=new URL(s);if(!d.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:u,url:l}=await g(),p={url:l,inputArguments:["-rtsp_transport","tcp","-i",l.replace("tcp","rtsp")]};return u.then((async e=>{const t=new z(e,n);await t.handlePlayback();const i=a().connect(parseInt(d.port),d.hostname);for(e.on("close",(()=>{i.destroy()})),i.on("close",(()=>{e.destroy()}));;){const r=(await O(i,2)).readInt16BE(0),s=await O(i,r),n=127&s[1];if(o.has(n))t.sendAudio(s,!1);else{if(!c.has(n))throw e.destroy(),i.destroy(),new Error("unknown payload type "+n);t.sendVideo(s,!1)}}})),Buffer.from(JSON.stringify(p))}async canMixin(t,i){if(!i.includes(e.ScryptedInterface.VideoCamera))return null;const r=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online];return i.includes(e.ScryptedInterface.VideoCameraConfiguration)&&r.push(e.ScryptedInterface.VideoCameraConfiguration),r}async getMixin(e,t,i){this.setHasEnabledMixin(i.id);const r=new Se(this,{mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"});return this.currentMixins.set(i.id,r),r}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in r)s[n]=r[n];r.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
2
2
  //# sourceMappingURL=main.nodejs.js.map
package/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.1.214",
3
+ "version": "0.1.217",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -622,15 +622,24 @@ class PrebufferSession {
622
622
  channel += 2;
623
623
  }
624
624
  await rtspClient.setup(channel, video);
625
- const socket = await rtspClient.play();
626
- session = await startRFC4571Parser(this.console, socket, sdp, ffmpegInput.mediaStreamOptions, true, rbo);
625
+ await rtspClient.play();
626
+ session = await startRFC4571Parser(this.console, rtspClient.rfc4571, sdp, ffmpegInput.mediaStreamOptions, true, rbo);
627
627
  const sessionKill = session.kill.bind(session);
628
+ let issuedTeardown = false;
628
629
  session.kill = async () => {
629
630
  // issue a teardown to upstream to close gracefully but don't rely on it responding.
630
- rtspClient.teardown().finally(sessionKill);
631
+ if (!issuedTeardown) {
632
+ issuedTeardown = true;
633
+ rtspClient.teardown().finally(sessionKill);
634
+ }
631
635
  await sleep(500);
636
+ rtspClient.client.destroy();
632
637
  sessionKill();
633
638
  }
639
+ if (!session.isActive)
640
+ throw new Error('parser was killed before rtsp client started');
641
+
642
+ rtspClient.readLoop().finally(() => session.kill());
634
643
  }
635
644
  catch (e) {
636
645
  rtspClient.client.destroy();
@@ -669,7 +678,7 @@ class PrebufferSession {
669
678
  return;
670
679
  }
671
680
 
672
- session.once('killed', kill);
681
+ session.killed.finally(kill);
673
682
 
674
683
  const { resetActivityTimer } = setupActivityTimer('mp4', kill, session, rbo.timeout);
675
684
 
@@ -745,10 +754,10 @@ class PrebufferSession {
745
754
  }
746
755
 
747
756
  scheduleRefresh(mso);
748
- session.once('killed', () => clearTimeout(refreshTimeout));
757
+ session.killed.finally(() => clearTimeout(refreshTimeout));
749
758
  }
750
759
 
751
- session.once('killed', () => {
760
+ session.killed.finally(() => {
752
761
  clearTimeout(this.inactivityTimeout)
753
762
  this.parserSessionPromise = undefined;
754
763
  if (this.parserSession === session)
@@ -812,11 +821,11 @@ class PrebufferSession {
812
821
  this.console.log(this.streamName, 'active rebroadcast clients:', this.activeClients);
813
822
  }
814
823
 
815
- inactivityCheck(session: ParserSession<PrebufferParsers>) {
816
- this.printActiveClients();
824
+ inactivityCheck(session: ParserSession<PrebufferParsers>, resetTimeout: boolean) {
817
825
  if (this.activeClients)
818
826
  return;
819
827
 
828
+ // should bitrate be reset immediately once the stream goes inactive?
820
829
  if (this.needBitrateReset && this.mixin.mixinDeviceInterfaces.includes(ScryptedInterface.VideoCameraConfiguration)) {
821
830
  this.resetBitrate();
822
831
  }
@@ -825,6 +834,10 @@ class PrebufferSession {
825
834
  return;
826
835
  }
827
836
 
837
+ // passive clients should not reset timeouts.
838
+ if (this.inactivityTimeout && !resetTimeout)
839
+ return;
840
+
828
841
  clearTimeout(this.inactivityTimeout)
829
842
  this.inactivityTimeout = setTimeout(() => {
830
843
  this.inactivityTimeout = undefined;
@@ -868,9 +881,9 @@ class PrebufferSession {
868
881
  }
869
882
 
870
883
  const cleanup = () => {
871
- destroy();
872
884
  session.removeListener(container, safeWriteData);
873
885
  session.removeListener('killed', cleanup);
886
+ destroy();
874
887
  }
875
888
 
876
889
  session.on(container, safeWriteData);
@@ -897,11 +910,9 @@ class PrebufferSession {
897
910
  return () => {
898
911
  if (isActiveClient) {
899
912
  this.activeClients--;
900
- this.inactivityCheck(session);
901
- }
902
- else {
903
- // this.console.log('passive client request ended');
913
+ this.printActiveClients();
904
914
  }
915
+ this.inactivityCheck(session, isActiveClient);
905
916
  cleanup();
906
917
  };
907
918
  }
@@ -909,6 +920,9 @@ class PrebufferSession {
909
920
  }
910
921
 
911
922
  async getVideoStream(options?: RequestMediaStreamOptions): Promise<MediaObject> {
923
+ if (options?.refresh === false && !this.parserSessionPromise)
924
+ throw new Error('Stream is currently unavailable and will not be started for this request. RequestMediaStreamOptions.refresh === false');
925
+
912
926
  this.ensurePrebufferSession();
913
927
 
914
928
  const session = await this.parserSessionPromise;
@@ -1066,15 +1080,13 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1066
1080
  }
1067
1081
 
1068
1082
  async getVideoStream(options?: RequestMediaStreamOptions): Promise<MediaObject> {
1083
+ if (options?.directMediaStream)
1084
+ return this.mixinDevice.getVideoStream(options);
1085
+
1069
1086
  await this.ensurePrebufferSessions();
1070
1087
 
1071
1088
  const id = options?.id;
1072
- let session = this.sessions.get(id);
1073
- if (!session || options?.directMediaStream)
1074
- return this.mixinDevice.getVideoStream(options);
1075
- session.ensurePrebufferSession();
1076
- await session.parserSessionPromise;
1077
- session = this.sessions.get(id);
1089
+ const session = this.sessions.get(id);
1078
1090
  if (!session)
1079
1091
  return this.mixinDevice.getVideoStream(options);
1080
1092
  return session.getVideoStream(options);
@@ -1096,9 +1108,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1096
1108
 
1097
1109
  const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
1098
1110
 
1099
- const defaultStreamName = this.storage.getItem('defaultStream');
1100
- let defaultSession: PrebufferSession;
1101
-
1102
1111
  let active = 0;
1103
1112
  for (const id of ids) {
1104
1113
  let session = this.sessions.get(id);
@@ -1113,9 +1122,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1113
1122
  session = new PrebufferSession(this, mso, stopInactive);
1114
1123
  this.sessions.set(id, session);
1115
1124
 
1116
- if (mso?.name === defaultStreamName)
1117
- defaultSession = session;
1118
-
1119
1125
  if (isBatteryPowered) {
1120
1126
  this.console.log('camera is battery powered, prebuffering and rebroadcasting will only work on demand.');
1121
1127
  continue;
@@ -1135,7 +1141,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1135
1141
  active++;
1136
1142
  wasActive = true;
1137
1143
  this.online = !!active;
1138
- await once(ps, 'killed');
1144
+ await ps.killed;
1139
1145
  this.console.error('prebuffer session ended');
1140
1146
  }
1141
1147
  catch (e) {
@@ -1155,19 +1161,21 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1155
1161
  }
1156
1162
  }
1157
1163
 
1158
- if (!defaultSession) {
1159
- if (enabledIds?.length)
1164
+ if (!this.sessions.has(undefined)) {
1165
+ const defaultStreamName = this.storage.getItem('defaultStream');
1166
+ let defaultSession = this.sessions.get(msos?.find(mso => mso.name === defaultStreamName)?.id);
1167
+ if (!defaultSession)
1160
1168
  defaultSession = this.sessions.get(msos?.find(mso => mso.id === enabledIds[0])?.id);
1161
- else
1169
+ if (!defaultSession)
1162
1170
  defaultSession = this.sessions.get(msos?.find(mso => mso.id === ids?.[0])?.id);
1163
- }
1164
1171
 
1165
- if (defaultSession) {
1166
- this.sessions.set(undefined, defaultSession);
1167
- this.console.log('Default Stream:', defaultSession.advertisedMediaStreamOptions.id, defaultSession.advertisedMediaStreamOptions.name);
1168
- }
1169
- else {
1170
- this.console.warn('Unable to find Default Stream?');
1172
+ if (defaultSession) {
1173
+ this.sessions.set(undefined, defaultSession);
1174
+ this.console.log('Default Stream:', defaultSession.advertisedMediaStreamOptions.id, defaultSession.advertisedMediaStreamOptions.name);
1175
+ }
1176
+ else {
1177
+ this.console.warn('Unable to find Default Stream?');
1178
+ }
1171
1179
  }
1172
1180
 
1173
1181
  deviceManager.onMixinEvent(this.id, this.mixinProviderNativeId, ScryptedInterface.Settings, undefined);
package/src/rfc4571.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
2
2
  import { readLength } from "@scrypted/common/src/read-stream";
3
3
  import sdk, { MediaStreamOptions } from "@scrypted/sdk";
4
- import { EventEmitter } from "stream";
4
+ import { EventEmitter, Readable } from "stream";
5
5
  import net from 'net';
6
6
  import { StreamChunk } from "@scrypted/common/src/stream-parser";
7
7
  import { parseHeaders, readMessage, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
@@ -20,7 +20,7 @@ export function connectRFC4571Parser(url: string) {
20
20
  }
21
21
 
22
22
 
23
- export async function startRFC4571Parser(console: Console, socket: net.Socket, sdp: string, mediaStreamOptions: MediaStreamOptions, hasRstpPrefix?: boolean, options?: ParserOptions<"rtsp">): Promise<ParserSession<"rtsp">> {
23
+ export async function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: MediaStreamOptions, hasRstpPrefix?: boolean, options?: ParserOptions<"rtsp">): Promise<ParserSession<"rtsp">> {
24
24
  let isActive = true;
25
25
  const events = new EventEmitter();
26
26
  // need this to prevent kill from throwing due to uncaught Error during cleanup
@@ -29,12 +29,18 @@ export async function startRFC4571Parser(console: Console, socket: net.Socket, s
29
29
  const audioPt = parseInt((sdp as string).match(/m=audio.* ([0-9]+)/)?.[1]);
30
30
  const videoPt = parseInt((sdp as string).match(/m=video.* ([0-9]+)/)?.[1]);
31
31
 
32
+ let sessionKilled: any;
33
+ const killed = new Promise<void>(resolve => {
34
+ sessionKilled = resolve;
35
+ });
36
+
32
37
  const kill = () => {
33
38
  if (isActive) {
34
39
  events.emit('killed');
35
40
  events.emit('error', new Error('killed'));
36
41
  }
37
42
  isActive = false;
43
+ sessionKilled();
38
44
  socket.destroy();
39
45
  };
40
46
 
@@ -49,18 +55,6 @@ export async function startRFC4571Parser(console: Console, socket: net.Socket, s
49
55
  let length: number;
50
56
  if (hasRstpPrefix) {
51
57
  header = await readLength(socket, 4);
52
- // rtsp over tcp will actually interleave RTSP request/responses
53
- // within the RTSP data stream. The only way to tell if it's a request/response
54
- // is to see if the header + data starts with RTSP/1.0 message line.
55
- // Or RTSP, if looking at only the header bytes. Then grab the response out.
56
- if (header.toString() === 'RTSP') {
57
- const response = parseHeaders(await readMessage(socket));
58
- const cl = parseInt(response['content-length']);
59
- if (cl)
60
- await readLength(socket, cl);
61
- continue;
62
- }
63
-
64
58
  length = header.readUInt16BE(2);
65
59
  }
66
60
  else {
@@ -120,8 +114,9 @@ export async function startRFC4571Parser(console: Console, socket: net.Socket, s
120
114
  inputAudioCodec,
121
115
  inputVideoCodec,
122
116
  inputVideoResolution: undefined,
123
- isActive() { return isActive },
117
+ get isActive() { return isActive },
124
118
  kill,
119
+ killed,
125
120
  mediaStreamOptions,
126
121
  emit(container: 'rtsp', chunk: StreamChunk) {
127
122
  events.emit(container, chunk);