@scrypted/prebuffer-mixin 0.1.225 → 0.1.228
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +86 -65
- package/src/rfc4571.ts +71 -58
package/dist/main.nodejs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{var e={747:(e,t,r)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return o.default}});var i=a(r(257)),s=a(r(816)),o=a(r(243)),n=a(r(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,o.default,n.default];s.default,o.default,n.default},816:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},o=r(761);const n=["realm"],a=n,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,o.parseHTTPHeadersQuotedKeyValueSet)(e,a,n)},buildWWWAuthenticateRest:function(e){return(0,o.buildHTTPHeadersQuotedKeyValueSet)(e,a,n)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:r}=c.decodeHash(e);return{hash:e,username:t,password:r}},buildAuthorizationRest:function({hash:e,username:t,password:r}){if(t&&r)return c.computeHash({username:t,password:r});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,...r]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:r.join(":")}}};var d=c;t.default=d},828:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},o=r(761);const n=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,o.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===n.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,n);return(0,o.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,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=r(761),o=(i=r(113))&&i.__esModule?i:{default:i};const n=["realm","nonce"],a=[...n,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const r=o.default.createHash(e);return r.update(t),r.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,n)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,n)},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(":")),r=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,r].join(":"))}};t.default=l},761:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){const i=e.trim().match(o);if(!i)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=i.map(((e,t)=>{const r=e.split("=");if(2!==r.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,r.length);return r})).reduce((function(e,[r,i],o){if(-1===t.indexOf(r))throw new s.default("E_UNAUTHORIZED_KEY",o,r);return e[r]=i.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return n(r,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){return n(r,e),t.reduce((function(t,r){return e[r]?t+(t?", ":"")+r+'="'+e[r]+'"':t}),"")};var i,s=(i=r(257))&&i.__esModule?i:{default:i};const o=/\w+=(".*?"|[^",]+)(?=,|$)/g;function n(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,r){"use strict";var i=this&&this.__createBinding||(Object.create?function(e,t,r,i){void 0===i&&(i=r),Object.defineProperty(e,i,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,i){void 0===i&&(i=r),e[i]=t[r]}),s=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||i(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,s(r(741),t);const o=r(741);class n extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}async createMediaObject(e,t){return mediaManager.createMediaObject(e,t,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:deviceManager.getMixinConsole(e.sourceId,this.nativeId)}_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=n;class a extends o.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}async createMediaObject(e,t){return mediaManager.createMediaObject(e,t,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:deviceManager.getMixinConsole(e.sourceId,this.mixinProviderNativeId)}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 r of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(n.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(a.prototype,r,{set:t(r),get:e(r)})}();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},733:e=>{const t=require("realfs");e.exports=t},741:(e,t,r)=>{"use strict";r.r(t),r.d(t,{DeviceBase:()=>i,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>n,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>l,MediaPlayerState:()=>p,ScryptedInterface:()=>m,ScryptedMimeTypes:()=>h});class i{}let s;!function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.pluginId="pluginId",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 o={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","pluginId","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 n,a,c,d,u,l,p,m,h;!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"}(n||(n={})),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"}(m||(m={})),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.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(h||(h={}))},113:e=>{"use strict";e.exports=require("crypto")},37:e=>{"use strict";e.exports=require("os")},257:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>h});var i=r(37);function s(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function o(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function n(e){var t="function"==typeof Map?new Map:void 0;return n=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;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,i)}function i(){return a(e,arguments,u(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),d(i,e)},n(e)}function a(e,t,r){return a=c()?Reflect.construct:function(e,t,r){var i=[null];i.push.apply(i,t);var s=new(Function.bind.apply(e,i));return r&&d(s,r.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,r,...i){var s,n,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(i=(void 0===r?[]:[r]).concat(i),r=e,e=[]),n=this,(s=!(a=u(t).call(this,r))||"object"!=typeof a&&"function"!=typeof a?o(n):a).code=r||"E_UNEXPECTED",s.params=i,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(o(s),s.constructor),s}var r,n,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),r=t,(n=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+i.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(r.prototype,n),a&&s(r,a),t}(n(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&m(e.code)&&e.params&&e.params instanceof Array}function m(e){return/^([A-Z0-9_]+)$/.test(e)}l.wrap=function(e,t,...r){let i=null;const s=m(e.message),o=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&r.push(e.message),i=new l(o,t,...r),i},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 h=l}},t={};function r(i){var s=t[i];if(void 0!==s)return s.exports;var o=t[i]={exports:{}};return e[i].call(o.exports,o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};(()=>{"use strict";r.r(i),r.d(i,{default:()=>Ne});var e=r(510),t=r.n(e);const{systemManager:s}=t(),o="v4";class n 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=s.getComponent("plugins"),s.listen((async(t,r,i)=>{r.eventInterface!==e.ScryptedInterface.ScryptedDevice||r.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(s.getSystemState())){const t=s.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]===o)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 r=await this.pluginsComponent;await r.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==o&&(this.hasEnabledMixin[e]=o,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const a=require("child_process");var c=r.n(a);const d=require("process");var u=r.n(d);const l=["decode_slice_header error","no frame!","non-existing PPS"];function p(e){if(e){try{e.stdin.write("q\n")}catch(e){}setTimeout((()=>{e.kill(),setTimeout((()=>{e.kill("SIGKILL")}),2e3)}),2e3)}}function m(e,t,r,i){const s=!!u().env.SCRYPTED_FFMPEG_NOISY||!!i?.getItem("SCRYPTED_FFMPEG_NOISY");function o(e){const i=o=>{const n=o.toString();for(const e of l)if(-1!==n.indexOf(e))return;if(!(s||r||-1===n.indexOf("frame=")&&-1===n.indexOf("size=")))return e(n),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(n)};return i}t.stdout?.on("data",o(e.log)),t.stderr?.on("data",o(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function h(e,t){const r=[];let i=!1;for(const e of t){try{if(i){const t=new URL(e);r.push(`${t.protocol}[REDACTED]`)}else r.push(e)}catch(t){r.push(e)}i="-i"===e}e.log(r.join(" "))}const f=require("events");async function g(e,t){if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise(((r,i)=>{const s=()=>{const i=e.read(t);i&&(n(),r(i))},o=()=>{n(),i(new Error(`stream ended during read for minimum ${t} bytes`))},n=()=>{e.removeListener("readable",s),e.removeListener("end",o)};e.on("readable",s),e.on("end",o)}))}const y="\n".charCodeAt(0);async function v(e){return async function(e,t){const r=[];let i=0;for(;;){const s=await g(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;r[i++]=s[0]}return Buffer.from(r).toString()}(e,y)}const{mediaManager:S}=t();async function*b(e){for(;;){const t=await g(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),s=await g(e,r);yield{header:t,length:r,type:i,data:s}}}const _=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];const w=require("net");var P=r.n(w);const x=require("dgram");var M=r.n(x);async function T(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function I(e,t){e.bind(t),await(0,f.once)(e,"listening");const r=e.address().port;return{port:r,url:`udp://127.0.0.1:${r}`}}async function C(e){return I(e,0)}async function O(){return async function(e){const t=M().createSocket("udp4"),{port:r,url:i}=await I(t,e);return{server:t,port:r,url:i}}(0)}async function R(e,t){return e.bind(t),await(0,f.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function D(){const e=new(P().Server),t=await async function(e){return e.listen(0),await(0,f.once)(e,"listening"),e.address().port}(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{r(new Error("timeout waiting for client"))}),3e4);e.on("connection",(r=>{e.close(),clearTimeout(i),t(r)}))}));return{server:e,url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:r}}function E(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:A}=t();async function k(e,t){return new Promise((r=>{const i=s=>{const o=s.toString(),n=o.indexOf(`${t}: `);if(-1!==n){const s=o.substring(n+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),r(s.substring(0,a)))}};e.stdout.on("data",i),e.stderr.on("data",i)}))}function B(e,t,r,i){let s;function o(){console.error("timeout waiting for data, killing parser session",e),t()}function n(){i&&(clearTimeout(s),s=setTimeout(o,i))}return r.once("killed",(()=>clearTimeout(s))),n(),{resetActivityTimer:n}}async function V(e,t){const{console:r}=t;let i,s=!0;const o=new f.EventEmitter;let n,a,d,u,l;o.on("error",(e=>r.error("rebroadcast error",e)));const g=new Promise(((e,t)=>{u=e,l=t}));let y;const v=new Promise((e=>{y=e}));function S(){s&&(o.emit("killed"),o.emit("error",new Error("killed"))),s=!1,clearTimeout(i),y(),l?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),p(T)}const b=e.inputArguments.slice();i=setTimeout(S,3e4);let _=!1;const w=e=>{if(!s)throw e(),new Error("parser session was killed killed before ffmpeg connected");o.on("killed",e)},P=["pipe","pipe","pipe"];let x=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){_=!0;const r=M().createSocket("udp4"),s=await C(r),n=M().createSocket("udp4");await R(n,s.port+1),w((()=>{r.close(),n.close()})),b.push(...i.outputArguments,s.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=B(e,S,o,t?.timeout);(async()=>{for await(const t of i.parseDatagram(r,parseInt(d?.[2]),parseInt(d?.[3])))u?.(void 0),o.emit(e,t),a()})(),(async()=>{for await(const t of i.parseDatagram(n,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))u?.(void 0),o.emit(e,t),a()})()}else if(i.tcpProtocol){const s=await D(),n=new URL(i.tcpProtocol);n.port=s.port.toString(),b.push(...i.outputArguments,n.toString());const{resetActivityTimer:a}=B(e,S,o,t?.timeout);(async()=>{const t=await s.clientPromise;try{w((()=>t.destroy()));for await(const r of i.parse(t,parseInt(d?.[2]),parseInt(d?.[3])))u?.(void 0),o.emit(e,r),a()}catch(e){r.error("rebroadcast parse error",e),S()}})()}else b.push(...i.outputArguments,"pipe:"+x++),P.push("pipe")}_&&(b.push("-sdp_file","pipe:"+x++),P.push("pipe")),b.unshift("-hide_banner"),h(r,b);const T=c().spawn(await A.getFFmpegPath(),b,{stdio:P});let I;m(r,T,void 0,t?.storage),T.on("exit",S),I=_?new Promise((e=>{const t=[];T.stdio[x-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let O=0;return Object.keys(t.parsers).forEach((async e=>{const i=t.parsers[e];if(!i.parse||i.tcpProtocol)return;const s=T.stdio[3+O];O++;try{const{resetActivityTimer:r}=B(e,S,o,t?.timeout);for await(const t of i.parse(s,parseInt(d?.[2]),parseInt(d?.[3])))u?.(void 0),o.emit(e,t),r()}catch(e){r.error("rebroadcast parse error",e),S()}})),async function(e){return k(e,"Audio")}(T).then((e=>n=e)),async function(e){return k(e,"Video")}(T).then((e=>a=e)),async function(e){return new Promise((t=>{const r=i=>{const s=i.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);o&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(o))};e.stdout.on("data",r),e.stderr.on("data",r)}))}(T).then((e=>d=e)),await g,u=void 0,l=void 0,clearTimeout(i),{sdp:I,inputAudioCodec:n,inputVideoCodec:a,inputVideoResolution:{width:parseInt(d?.[1]),height:parseInt(d?.[2])},get isActive(){return s},kill:S,killed:v,negotiateMediaStream:()=>{const t=E(e.mediaStreamOptions)||{id:void 0,name:void 0};return t.video||(t.video={}),t.video.codec=a,t.audio=t?.audio?.codec===n?e?.mediaStreamOptions?.audio:{codec:n},t},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 j(e,t){const r=await e;let i=!0;const s=()=>{r.removeAllListeners(),r.destroy();const e=o;o=void 0,e?.()};let o=t?.connect((e=>{i&&(i=!1,e.startStream&&r.write(e.startStream));for(const t of e.chunks)r.write(t);return r.writableLength}),s);r.once("close",(()=>{s()})),r.on("error",(e=>t?.console?.log("client stream ended")))}const L=require("stream");var H=r(113),U=r.n(H);function N(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));let r=0;for(let e=0;e<t.length;e++){t[e].startsWith("m=")&&(t.splice(e+1,0,"a=control:trackID="+r),r++)}return t.join("\r\n")}function F(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function K(e){return e.filter((e=>e.startsWith("a=fmtp"))).map((e=>{const t=e.indexOf(" ");if(-1===t)return;const r=e.substring(0,t),i=e.substring(t+1),s=parseInt(r.split(":")[1]);if(!r||!i||NaN===s)return;const o={};return i.split(";").map((e=>e.trim())).forEach((e=>{const[t,...r]=e.split("=");o[t]=r.join("=")})),{payloadType:s,parameters:o}})).filter((e=>!!e))}const q="a=control:";function $(e){const t=e.find((e=>e.startsWith(q)))?.substring(q.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase();let i,s;r?.includes("mpeg4")?i="aac":r?.includes("opus")?i="opus":r?.includes("pcm")?i="pcm":r?.includes("h264")?i="h264":r?.includes("h265")&&(i="h265");for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){s=t;break}}return{...(o=e[0],{type:o.split(" ")[0].substring(2),payloadTypes:F(o)}),fmtp:K(e),lines:e,contents:e.join("\r\n"),control:t,codec:i,direction:s};var o}function G(e){const t=e.split("\n").map((e=>e.trim())),r=[],i=[];let s;for(const e of t)e.startsWith("m=")&&(s&&i.push(s),s=[]),s?s.push(e):r.push(e);s&&i.push(s);const o={header:{lines:r,contents:r.join("\r\n")},msections:i.map($),toSdp:()=>[...o.header.lines,...o.msections.map((e=>e.lines)).flat()].join("\r\n")};return o}const W=require("tls");var z=r.n(W),J=r(747);class Q extends Error{constructor(){super("Operation Timed Out")}}function Y(e,t){if("h264"!==e.type)return;const r=e.chunks[e.chunks.length-1].subarray(12),i=31&r[0];if(24===i){let e=1;for(;e<r.length;){const i=r.readUInt16BE(e);e+=2;if((31&r[e])===t)return r.subarray(e,e+i);e+=i}}else if(28===i){const e=31&r[1],i=!!(128&r[1]);if(e===t&&i)return r.subarray(1)}else if(i===t)return r}function X(e){const t={};for(const r of e.slice(1)){const e=r.indexOf(":");let i="";-1!==e&&(i=r.substring(e+1).trim());t[r.substring(0,e).toLowerCase()]=i}return t}class Z extends class{constructor(e){this.console=e}write(e,t,r){let i=`${e}\r\n`;r&&(t["Content-Length"]=r.length.toString());for(const[e,r]of Object.entries(t))i+=`${e}: ${r}\r\n`;i+="\r\n",this.client.write(i),this.console?.log("rtsp outgoing message\n",i),this.console?.log(),r&&this.client.write(r)}async readMessage(){const e=await async function(e){let t=[];for(;;){let r=await v(e);if(r=r.trim(),!r)return t;t.push(r)}}(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;rfc4571=new L.PassThrough;needKeepAlive=!1;constructor(e,t){super(t),this.url=e;const r=new URL(e),i=parseInt(r.port)||554;e.startsWith("rtsps")?this.client=z().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=P().connect(i,r.hostname)}writeRequest(e,t,r,i){t=t||{};let s=this.url;r&&(r.includes("rtsp://")||r.includes("rtsps://")?s=r:s+="/"+r);const o=new URL(s);o.username="",o.password="",s=o.toString();const n=`${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(n,t,i)}async handleDataPayload(e){if(36!==e[0])throw new Error("RTSP Client expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),r=await g(this.client,t);this.rfc4571.push(e),this.rfc4571.push(r)}async readDataPayload(){const e=await g(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 g(this.client,4);if("RTSP"===e.toString()){this.client.unshift(e);return await super.readMessage()}await this.handleDataPayload(e)}}async request(e,t,r,i,s){this.writeRequest(e,t,r,i);const o=this.requestTimeout?await(n=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new Q)),n),a.then(e),a.catch(t)}))):await this.readMessage();var n,a;const c=o[0],d=X(o);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 o=new URL(this.url);if(u.includes("Basic")){const e=J.rh.computeHash(o);this.authorization=`Basic ${e}`}else{const t=J.Nu.parseWWWAuthenticateRest(u),r=decodeURIComponent(o.username),i=decodeURIComponent(o.password),s=U().createHash("md5").update(`${r}:${t.realm}:${i}`).digest("hex"),n=U().createHash("md5").update(`${e}:${o.pathname}`).digest("hex"),a=U().createHash("md5").update(`${s}:${t.nonce}:${n}`).digest("hex"),c={username:r,realm:t.realm,nonce:t.nonce,uri:o.pathname,algorithm:"MD5",response:a},d=Object.entries(c).map((([e,t])=>{return`${e}=${t&&(r=t,`"${r.replace(/"/g,'\\"')}"`)}`;var r})).join(", ");this.authorization=`Digest ${d}`}return this.request(e,t,r,i,!0)}const l=parseInt(d["content-length"]);return l?{headers:d,body:await g(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,r){const i={Transport:`RTP/AVP/${r?"UDP":"TCP"};unicast;${r?"client_port":"interleaved"}=${e}-${e+1}`},s=await this.request("SETUP",i,t);if(s.headers.session){const e=function(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}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 ee{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,H.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await v(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 g(this.client,4);if(36!==e[0])throw new Error("RTSP Server expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),r=await g(this.client,t),i=e.readUInt8(1),s=i-i%2,o=Object.values(this.setupTracks).find((e=>e.destination===s));if(!o)throw new Error("RSTP Server received unknown channel: "+i);yield{type:o.codec,rtcp:i%2==1,header:e,packet:r}}}send(e,t){const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(t,1),r.writeUInt16BE(e.length,2),this.client.write(r),this.client.write(Buffer.from(e))}sendUdp(e,t,r){this.udp.send(t,r?e+1:e,"127.0.0.1")}sendTrack(e,t,r){const i=this.setupTracks[e];i?"udp"!==i.protocol?this.send(t,r?i.destination+1:i.destination):this.udp?this.sendUdp(i.destination,t,r):this.console?.warn("RTSP Server UDP socket not available."):this.console?.warn("RTSP Server track not found:",e)}options(e,t){const r={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,r)}describe(e,t){const r={};r["Content-Base"]=e,r["Content-Type"]="application/sdp",this.respond(200,"OK",t,r,Buffer.from(this.sdp))}setup(e,t){const r={},i=t.transport;r.Transport=i,r.Session=this.session;const s=G(this.sdp).msections.find((t=>e.endsWith(t.control)));if(s){if(i.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const e=i.match(/.*?client_port=([0-9]+)-([0-9]+)/),[r,o,n]=e;this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(o),codec:s.codec}}else if(i.includes("TCP")){const e=i.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const t=parseInt(e[1]);parseInt(e[2]);this.setupTracks[s.control]={control:s.control,protocol:"tcp",destination:t,codec:s.codec}}}this.respond(200,"OK",t,r)}else this.respond(404,"Not Found",t,r)}play(e,t){const r={},i=Object.values(this.setupTracks).map((t=>`url=${e}/${t.control}`)).join(",")+";seq=0;rtptime=0";r["RTP-Info"]=i,r.Range="npt=now-",r.Session=this.session,this.respond(200,"OK",t,r)}async announce(e,t){const r=parseInt(t["content-length"]),i=await g(this.client,r);this.sdp=i.toString();const s={};s.Session=this.session,this.respond(200,"OK",t,s)}async record(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async teardown(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=X(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,r,i,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",i,{}),this.client.destroy(),new Error("check request failed")}if(this[t])return await this[t](r,i),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",i,{})}respond(e,t,r,i,s){let o=`RTSP/1.0 ${e} ${t}\r\n`;r.cseq&&(i.CSeq=r.cseq),s&&(i["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(i))o+=`${e}: ${t}\r\n`;this.console?.log("response headers",o),o+="\r\n",this.client.write(o),s&&(this.client.write(s),this.console?.log("response body",s.toString()))}}const{systemManager:te}=t();class re{values={};hasValue={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const r=t[e],i=()=>this.getItem(e);let s;s="clippath"!==r.type?i:()=>{try{return JSON.parse(i())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)}),Object.defineProperty(this.hasValue,e,{get:()=>null!=this.device.storage.getItem(e)})}}get keys(){const e={};for(const t of Object.keys(this.settings))e[t]=t;return e}async getSettings(){const e=await(this.options?.onGet?.()),t=[];for(const[r,i]of Object.entries(this.settings)){let s=Object.assign({},i);e?.[r]&&(s=Object.assign(s,e[r])),s.onGet&&(s=Object.assign(s,await s.onGet())),s.hide||await(this.options?.hide?.[r]?.())||(s.key=r,s.value=this.getItemInternal(r,s),t.push(s),delete s.onPut,delete s.onGet,delete s.mapPut,delete s.mapGet)}return t}async putSetting(t,r){const i=this.settings[t];let s;i&&(s=this.getItemInternal(t,i)),i?.noStore||(i.mapPut&&(r=i.mapPut(s,r)),"object"==typeof r?this.device.storage.setItem(t,JSON.stringify(r)):this.device.storage.setItem(t,r?.toString())),i?.onPut?.(s,r),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItemInternal(e,t){if(!t)return this.device.storage.getItem(e);const r=function(e,t){const{defaultValue:r}=t,i=t.multiple?"array":t.type;if("boolean"===i)return"true"===e||"false"!==e&&(r||!1);if("number"===i)return parseFloat(e)||r||0;if("integer"===i)return parseInt(e)||r||0;if("array"===i)try{return JSON.parse(e)}catch(e){return r||[]}if("device"===i)return te.getDeviceById(e);if(e&&t.json)try{return JSON.parse(e)}catch(e){return r}return e||r}(this.device.storage.getItem(e),t);return t.mapGet?t.mapGet(r):r}getItem(e){return this.getItemInternal(e,this.settings[e])}}const{deviceManager:ie}=t();class se extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>ie.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,r=this.getMixinSettings(),i=[];try{const e=await t||[];i.push(...e)}catch(e){const t=this.name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}try{const e=await r||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=ie.getDeviceState(this.mixinProviderNativeId).name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}return i}async putSetting(t,r){const i=this.settingsGroupKey+":";if(!t?.startsWith(i))return this.mixinDevice.putSetting(t,r);await this.putMixinSetting(t.substring(i.length),r),ie.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await ie.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}function oe(e){return e}async function*ne(e){let t,r,i;for await(const s of e)t?r||(r=s):t=s,yield{startStream:i,chunks:[s.header,s.data],type:s.type},t&&r&&!i&&(i=Buffer.concat([t.header,t.data,r.header,r.data]))}class ae{bitoffset=0;constructor(e){this.view=e}ExpGolomb(){const{view:e}=this;let{zeros:t,skip:r,byt:i,byteoffset:s}=function(e,t){let r=0,i=t>>3,s=7&t,o=-1,n=e.getUint8(i)<<s;do{r=128&n,n<<=1,o++,s++,8===s&&(s=0,i++,n=e.getUint8(i))}while(!r);return{zeros:o,skip:s,byt:n,byteoffset:i}}(e,this.bitoffset),o=1;for(;t>0;)o=o<<1|(128&i)>>>7,i<<=1,r++,t--,8===r&&(r=0,s++,i=e.getUint8(s));return this.bitoffset=s<<3|r,o-1}SignedExpGolomb(){const e=this.ExpGolomb();return 1&e?e+1>>>1:-(e>>>1)}readBit(){const e=7&this.bitoffset,t=this.bitoffset>>3;return this.bitoffset++,this.view.getUint8(t)>>7-e&1}readByte(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=8;const r=this.view.getUint8(t);if(0===e)return r;return r<<e|this.view.getUint8(t+1)>>8-e}}function ce(e,t){let r=8,i=8;const s=[];for(let o=0;o<t;o++){if(0!==i){i=(r+e.SignedExpGolomb()+256)%256}i&&(r=i),s.push(i)}return s}function de(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new ae(new DataView(e.buffer,e.byteOffset+4)),r=e[1],i=e[2],s=e[3],o=t.ExpGolomb();let n=1,a=0,c=0,d=0,u=0,l=0;const p=[];if(100===r||110===r||122===r||244===r||44===r||83===r||86===r||118===r||128===r){n=t.ExpGolomb();let e=8;if(3===n&&(e=12,d=t.readBit()),a=t.ExpGolomb()+8,c=t.ExpGolomb()+8,u=t.readBit(),l=t.readBit(),l){let r=0;for(;r<6;r++)t.readBit()&&p.push(ce(t,16));for(;r<e;r++)t.readBit()&&p.push(ce(t,64))}}const m=t.ExpGolomb()+4,h=t.ExpGolomb();let f=0,g=0,y=0;const v=[];let S=0;if(0===h)S=t.ExpGolomb()+4;else if(1===h){f=t.readBit(),g=t.SignedExpGolomb(),y=t.SignedExpGolomb();const e=t.SignedExpGolomb();for(let r=0;r<e;r++)v.push(t.SignedExpGolomb())}const b=t.ExpGolomb(),_=t.readBit(),w=t.ExpGolomb()+1,P=t.ExpGolomb()+1,x=t.readBit();let M=0;x||(M=t.readBit());const T=t.readBit(),I=t.readBit(),C=function(e,t){return e?{left:t.ExpGolomb(),right:t.ExpGolomb(),top:t.ExpGolomb(),bottom:t.ExpGolomb()}:{left:0,right:0,top:0,bottom:0}}(I,t),O=t.readBit(),R=function(e,t){const r={aspect_ratio_info_present_flag:0,aspect_ratio_idc:0,sar_width:0,sar_height:0,overscan_info_present_flag:0,overscan_appropriate_flag:0,video_signal_type_present_flag:0,video_format:0,video_full_range_flag:0,colour_description_present_flag:0,colour_primaries:0,transfer_characteristics:0,matrix_coefficients:0,chroma_loc_info_present_flag:0,chroma_sample_loc_type_top_field:0,chroma_sample_loc_type_bottom_field:0,nal_hrd_parameters_present_flag:0,vcl_hrd_parameters_present_flag:0,low_delay_hrd_flag:0,pic_struct_present_flag:0,bitstream_restriction_flag:0,motion_vectors_over_pic_boundaries_flag:0,max_bytes_per_pic_denom:0,max_bits_per_mb_denom:0,log2_max_mv_length_horizontal:0,log2_max_mv_length_vertical:0,num_reorder_frames:0,max_dec_frame_buffering:0};return e&&(r.aspect_ratio_info_present_flag=t.readBit(),r.aspect_ratio_info_present_flag&&(r.aspect_ratio_idc=t.ExpGolomb(),255===r.aspect_ratio_idc&&(r.sar_width=t.readByte(),r.sar_height=t.readByte())),r.overscan_info_present_flag=t.readBit(),r.overscan_info_present_flag&&(r.overscan_appropriate_flag=t.readBit()),r.video_signal_type_present_flag=t.readBit(),r.video_signal_type_present_flag&&(r.video_format=t.readBit()<<2|t.readBit()<<1|t.readBit(),r.video_full_range_flag=t.readBit(),r.colour_description_present_flag=t.readBit(),r.colour_description_present_flag&&(r.colour_primaries=t.readByte(),r.transfer_characteristics=t.readByte(),r.matrix_coefficients=t.readByte())),r.chroma_loc_info_present_flag=t.readBit(),r.chroma_loc_info_present_flag&&(r.chroma_sample_loc_type_top_field=t.ExpGolomb(),r.chroma_sample_loc_type_bottom_field=t.ExpGolomb()),r.nal_hrd_parameters_present_flag=t.readBit(),r.vcl_hrd_parameters_present_flag=t.readBit(),(r.nal_hrd_parameters_present_flag||r.vcl_hrd_parameters_present_flag)&&(r.low_delay_hrd_flag=t.readBit()),r.pic_struct_present_flag=t.readBit(),r.bitstream_restriction_flag=t.readBit(),r.bitstream_restriction_flag&&(r.bitstream_restriction_flag=t.readBit(),r.motion_vectors_over_pic_boundaries_flag=t.readBit(),r.max_bytes_per_pic_denom=t.ExpGolomb(),r.max_bits_per_mb_denom=t.ExpGolomb(),r.log2_max_mv_length_horizontal=t.ExpGolomb(),r.log2_max_mv_length_vertical=t.ExpGolomb(),r.num_reorder_frames=t.ExpGolomb(),r.max_dec_frame_buffering=t.ExpGolomb())),r}(O,t);return{sps_id:o,profile_compatibility:i,profile_idc:r,level_idc:s,chroma_format_idc:n,bit_depth_luma:a,bit_depth_chroma:c,color_plane_flag:d,qpprime_y_zero_transform_bypass_flag:u,seq_scaling_matrix_present_flag:l,seq_scaling_matrix:p,log2_max_frame_num:m,pic_order_cnt_type:h,delta_pic_order_always_zero_flag:f,offset_for_non_ref_pic:g,offset_for_top_to_bottom_field:y,offset_for_ref_frame:v,log2_max_pic_order_cnt_lsb:S,max_num_ref_frames:b,gaps_in_frame_num_value_allowed_flag:_,pic_width_in_mbs:w,pic_height_in_map_units:P,frame_mbs_only_flag:x,mb_adaptive_frame_field_flag:M,direct_8x8_inference_flag:T,frame_cropping_flag:I,frame_cropping:C,vui_parameters_present_flag:O,vui_parameters:R}}function ue(e){let t,r;0==e.chroma_format_idc&&0==e.color_plane_flag?t=r=0:1==e.chroma_format_idc&&0==e.color_plane_flag?t=r=2:2==e.chroma_format_idc&&0==e.color_plane_flag?(t=2,r=1):3==e.chroma_format_idc&&(0==e.color_plane_flag?t=r=1:1==e.color_plane_flag&&(t=r=0));let i=e.pic_width_in_mbs,s=e.pic_height_in_map_units,o=(2-e.frame_mbs_only_flag)*s,n=0,a=0,c=0,d=0;return e.frame_cropping_flag&&(n=e.frame_cropping.left,a=e.frame_cropping.right,c=e.frame_cropping.top,d=e.frame_cropping.bottom),{width:16*i-t*(n+a),height:16*o-r*(2-e.frame_mbs_only_flag)*(c+d)}}async function le(e,t,r,i,s,o){let n=!0;const a=new L.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=G(r),d=c.msections.find((e=>"audio"===e.type)),u=c.msections.find((e=>"video"===e.type)),l=d?.payloadTypes?.[0],p=u?.payloadTypes?.[0],m=d?.codec,h=u.codec;let f;const y=new Promise((e=>{f=e})),v=()=>{n&&(a.emit("killed"),a.emit("error",new Error("killed"))),n=!1,f(),t.destroy()};t.on("close",v),t.on("error",v);const{resetActivityTimer:S}=B("rtsp",v,a,s?.timeout);let b;const _=u?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],w=_?.split(",")?.[0];if(w)try{const t=de(Buffer.from(w,"base64"));b=ue(t),e.log("parsed sdp sps",t)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{for(;;){let r,i,s;if(o){r=await g(t,4),i=r.readUInt16BE(2);const e=r.readUInt8(1);s=o[e]}else r=await g(t,2),i=r.readUInt16BE(0);const n=await g(t,i),c=127&n[1];if(!o){const e=Buffer.alloc(2);e[0]=36,c===l?e[1]=0:c===p&&(e[1]=2),r=Buffer.concat([e,r]),c===l?s=m:c===p&&(s=h)}const d={startStream:undefined,chunks:[r,n],type:s};if(!b){const t=Y(d,7);if(t)try{const r=de(t);b=ue(r),e.log(b),e.log("parsed bitstream sps",r)}catch(t){e.warn("sps parsing failed"),b={width:NaN,height:NaN}}}a.emit("rtsp",d),S()}})().finally(v),{sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:m,inputVideoCodec:h,get inputVideoResolution(){return b},get isActive(){return n},kill:v,killed:y,resetActivityTimer:S,negotiateMediaStream:e=>{const t=E(i)||{id:void 0,name:void 0};if(t.video||(t.video={}),t.video.codec=h,e?.audio?.codec&&e?.audio?.codec!==m){if(c.msections.find((t=>"audio"===t.type&&t.codec===e?.audio?.codec)))return t.audio={codec:e?.audio?.codec},t}return t.audio=t?.audio?.codec===m?i?.audio:{codec:m},t},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}}}var pe=r(37),me=r.n(pe);const he=["BCM2708","BCM2709","BCM2710","BCM2835","BCM2836","BCM2837","BCM2837B0","BCM2711"];function fe(){let e;try{e=r(733).readFileSync("/proc/cpuinfo",{encoding:"utf8"})}catch(e){return!1}const t=e.split("\n").map((e=>e.replace(/\t/g,""))).filter((e=>e.length>0)).map((e=>e.split(":"))).map((e=>e.map((e=>e.trim())))).filter((e=>"Hardware"===e[0]));if(!t||0==t.length)return!1;return function(e){return he.indexOf(e)>-1}(t[0][1])}const ge="Video4Linux (Docker compatible)";function ye(){if(fe()){return{}}if("darwin"===me().platform())return{VideoToolbox:["-hwaccel","auto"]};const e={"Nvidia CUDA":["-vsync","0","–hwaccel","cuda","-hwaccel_output_format","cuda"],"Nvidia CUVID":["-vsync","0","–hwaccel","cuvid","-c:v","h264_cuvid"]};if(fe())e["Raspberry Pi"]=["-c:v","h264_mmal"],e[ge]=["-c:v","h264_v4l2m2m"];else if("linux"===me().platform())e[ge]=["-c:v","h264_v4l2m2m"];else{if("win32"!==me().platform())return{};e["Intel QuickSync"]=["-c:v","h264_qsv"]}return e}function ve(){const e={"Copy Video, Transcode Audio":"copy"};if(fe());else if("darwin"===me().platform())e.VideoToolbox="h264_videotoolbox";else if("win32"===me().platform())e["Intel QuickSync"]="h264_qsv",e.AMD="h264_amf",e.Nvidia="h264_nvenc";else{if("linux"!==me().platform())return{};e.V4L2="h264_v4l2m2m",e.VAAPI="h264_vaapi",e.Nvidia="nvenc_h264"}const t={};for(const[r,i]of Object.entries(e))t[r]=["-c:v",i];return fe()&&(t["Raspberry Pi"]=["-pix_fmt","yuv420p","-c:v","h264_v4l2m2m"]),t["libx264 (Software)"]=["-c:v","libx264","-preset","ultrafast","-bf","0"],t}function Se(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}function be(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Se(t)}function _e(e){const t={defaultStream:{title:"Local Stream",description:"The media stream to use when streaming on your local network. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.",hide:!0,prefersPrebuffer:!0,preferredResolution:8294400},remoteStream:{title:"Remote (Medium Resolution) Stream",description:"The media stream to use when streaming from outside your local network. Selecting a low birate stream is recommended. Recommended resolution: 1270x720.",hide:!0,prefersPrebuffer:!1,preferredResolution:921600},lowResolutionStream:{title:"Low Resolution Stream",description:"The media stream to use for low resolution output, such as Apple Watch and Video Analysis. Recommended resolution: 480x360.",hide:!0,prefersPrebuffer:!1,preferredResolution:172800},recordingStream:{title:"Local Recording Stream",description:"The media stream to use when recording to local storage such as an NVR. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.",hide:!0,prefersPrebuffer:!0,preferredResolution:8294400},remoteRecordingStream:{title:"Remote Recording Stream",description:"The media stream to use when recording to cloud storage such as HomeKit Secure Video clips in iCloud. This stream should be prebuffered. Recommended resolution: 1270x720.",hide:!0,prefersPrebuffer:!0,preferredResolution:921600}},r=new re(e,{enabledStreams:{title:"Prebuffered Streams",description:"Prebuffering maintains an active connection to the stream and improves load times. Prebuffer also retains the recent video for capturing motion events with HomeKit Secure video. Enabling Prebuffer is not recommended on Cloud cameras.",multiple:!0,hide:!1},...t,transcodeStreams:{group:"Transcoding",title:"Transcode Streams",description:"The media streams to transcode. Transcoding audio and video is not recommended and should only be used when necessary. The Rebroadcast Plugin manages the system-wide Transcode settings. See the Rebroadcast Readme for optimal configuration.",multiple:!0,choices:Object.values(t).map((e=>e.title))},missingCodecParameters:{group:"Transcoding",title:"Add H264 Extra Data",description:"Some cameras do not include H264 extra data in the stream and this causes live streaming to always fail (but recordings may be working). This is a inexpensive video filter and does not perform a transcode. Enable this setting only as necessary.",type:"boolean"},videoDecoderArguments:{group:"Transcoding",title:"Video Decoder Arguments",description:"FFmpeg arguments used to decode input video when transcoding a stream.",placeholder:"-hwaccel auto",choices:Object.keys(ye()),combobox:!0,mapPut:(e,t)=>ye()[t]?.join(" ")||t}});function i(e,t){const i=be(r,t);return function(e,t){if(!e)return;let r,i;for(const s of e){const e=Math.abs(s.video?.width*s.video?.height-t);(!r||e<i)&&(r=s,i=e,Number.isNaN(i)&&(i=Number.MAX_SAFE_INTEGER))}return r}(e.prefersPrebuffer&&i?.length>0?i:t,e.preferredResolution)}function s(e,s){const o=r.settings[e],n=r.values[e];let a="Default"===n,c=s?.find((e=>e.name===n));return!a&&c||(a=!0,c=i(o,s)),{title:t[e].title,isDefault:a,stream:c}}function o(e,t){const r=["Default",...t.map((e=>e.name))],s=i(e,t).name;return{defaultValue:"Default",description:e.description+` The default for this stream is ${s}.`,choices:r,hide:!1}}return r.options={onGet:async()=>{let r;try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Se(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream:o(t.defaultStream,i),remoteStream:o(t.remoteStream,i),lowResolutionStream:o(t.lowResolutionStream,i),recordingStream:o(t.recordingStream,i),remoteRecordingStream:o(t.remoteRecordingStream,i)}:{enabledStreams:r}}catch(t){e.console.error("error retrieving getVideoStreamOptions",t)}return{}}},{getDefaultStream:e=>s(r.keys.defaultStream,e),getRemoteStream:e=>s(r.keys.remoteStream,e),getLowResolutionStream:e=>s(r.keys.lowResolutionStream,e),getRecordingStream:e=>s(r.keys.recordingStream,e),getRemoteRecordingStream:e=>s(r.keys.remoteRecordingStream,e),storageSettings:r}}const{mediaManager:we,log:Pe,systemManager:xe,deviceManager:Me}=t(),Te="Default",Ie="AAC or No Audio",Ce=`${Ie} (Copy)`,Oe="Compatible Audio",Re="Other Audio",De=["aac","mp3","mp2","opus"],Ee="-fflags +genpts",Ae="Scrypted",ke="FFmpeg (TCP)",Be="FFmpeg (UDP)",Ve="Default",je=[Ie,Oe,Re],Le=["mpegts","mp4","rtsp"];class He{prebuffers={mp4:[],mpegts:[],rtsp:[]};audioDisabled=!1;activeClients=0;needBitrateReset=!1;constructor(e,t,r){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const i="rtspServerPathKey-"+this.streamId;this.maxBitrateKey="maxBitrate-"+this.streamId,this.rtspServerPath=this.storage.getItem(i),this.rtspServerPath||(this.rtspServerPath=U().randomBytes(8).toString("hex"),this.storage.setItem(i,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)||"";je.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ie),r=-1!==e.indexOf(Oe),i=-1!==e.indexOf(Re);return{isUsingDefaultAudioConfig:!(t||r||i),aacAudio:t,compatibleAudio:r,reencodeAudio:i}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:r,compatibleAudio:i,aacAudio:s}=this.getAudioConfig();return r||i||s}getParser(e,t,r){if(!this.canUseRtspParser(t,r))return Ve;const i=e&&"scrypted"===r?.tool?Ae:Ve,s=this.storage.getItem(this.rtspParserKey);return s&&s!==Ve?s===Ae?Ae:s===ke?ke:s===Be?Be:i:i}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 r=e?.startsWith("RTSP");return{defaultMode:t,rtspMode:e?.startsWith("RTSP"),muxingMp4:!r||e?.includes("MP4")}}async getMixinSettings(){const t=[],r=this.parserSession;let i=0,s=0;const{muxingMp4:o,rtspMode:n,defaultMode:a}=this.getRebroadcastMode();for(const e of o?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const c=Date.now()-s,d=Math.round(i/c*8),u=this.streamName?`Stream: ${this.streamName}`:"Stream";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)||Te,choices:[Te,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:[Ve,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ve});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:Ee,choices:[Ee,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(o,this.advertisedMediaStreamOptions)&&n&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(n,o,this.advertisedMediaStreamOptions),r=n&&"scrypted"===this.advertisedMediaStreamOptions?.tool?Ae:"FFmpeg";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 "${r}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||Ve,choices:[Ve,ke,Be,Ae]}),e!==Ae&&l()}else l();if(r){const e=r.inputVideoResolution?.width&&r.inputVideoResolution?.height?`${r.inputVideoResolution?.width}x${r.inputVideoResolution?.height}`:"unknown";t.push({key:"detectedResolution",group:u,title:"Detected Resolution and Bitrate",readonly:!0,value:`${e} @ ${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:(r?.inputVideoCodec?.toString()||"unknown")+"/"+(r?.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"})}else t.push({title:"Status",group:u,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return n&&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(){let t;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];try{t=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===t?.audio,i=t?.audio?.codec,{isUsingDefaultAudioConfig:s,aacAudio:o,compatibleAudio:n,reencodeAudio:a}=this.getAudioConfig(),{rtspMode:d,muxingMp4:u}=this.getRebroadcastMode();let l=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===l&&(l=null);let g=!1;u&&!r&&!i&&s&&void 0===l&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),g=!0);const y=void 0===l?i?.toLowerCase():l?.toLowerCase(),v=!De.includes(y);!u||g||!1===t?.userConfigurable||r||v&&(s&&Pe.a(`${this.mixin.name} is using the ${y} 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:",y));const w=["-bsf:a","aac_adtstoasc"],x=[];let M;this.audioDisabled=!1;const I=null===l;let C=!1;if(!g&&s&&v&&(!1===t?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",y):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),C=!0),r||g)M=["-an"],this.audioDisabled=!0;else if(a||C)M=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(o||I)M=["-acodec","copy"],M.push(...w);else if(n)M=["-acodec","copy"],M.push(...x);else{M=["-acodec","copy"];const e="aac"===y?w:x;M.push(...e)}const R=["-vcodec","copy"],D={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=D.parsers,this.console.log("rebroadcast mode:",d?"rtsp":"mpegts"),d){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,H.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){let t;for(let r=0;r<e.length;r++)Y(e[r],7)&&(t=r);return void 0!==t?e.slice(t):e},sdp:new Promise((e=>t=e)),async*parse(e,r,i){const s=new ee(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:o,packet:n}of s.handleRecord())yield{chunks:[o,n],type:`${t?"rtcp":"rtp"}-${e}`,width:r,height:i}}}}({vcodec:R,acodec:s?["-acodec","copy"]:M});this.sdp=e.sdp,D.parsers.rtsp=e}else D.parsers.mpegts={container:"mpegts",outputArguments:[...(E={vcodec:R,acodec:M})?.vcodec||[],...E?.acodec||[],"-f","mpegts"],parse:(A=188,k=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],r=0;for(;;){const i=e.read();if(!i){await(0,f.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<A)continue;const s=Buffer.concat(t);k?.(s);const o=s.length%A,n=s.slice(0,s.length-o),a=s.slice(s.length-o);t=[a],r=a.length,yield{chunks:[n]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const s=r.chunks[i];let o=0;for(;o+188<s.length;){const r=s.subarray(o,o+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);o+=188}}}return e}};var E,A,k;u&&(D.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],..._],async*parse(e){const t=b(e);yield*ne(t)},findSyncFrame:oe}}({vcodec:R,acodec:M}));const j=await this.mixinDevice.getVideoStream(t),L="x-scrypted/x-rfc4571"===j.mimeType;let U,N;this.storage.removeItem(this.lastDetectedAudioCodecKey);let F=!1;if(d&&L){F=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await we.convertMediaObjectToJSON(j,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;U=await le(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return P().connect(parseInt(t.port),t.hostname)}(t),r,i,D),this.sdp=U.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await we.convertMediaObjectToBuffer(j,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());N=i.mediaStreamOptions;const s=this.getParser(d,u,i.mediaStreamOptions);if(s===Ae){F=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new Z(i.url,this.console);let t=[];const s=()=>{for(const e of t)T(e)};try{e.requestTimeout=1e4,await e.options();let o=(await e.describe()).body.toString().trim();this.console.log("sdp",o);const n=G(o);let a=0;const c={},d=!1,u=async(r,i)=>{let s=a;if(d){const e=a,{port:r,server:o}=await O();t.push(o),s=r,o.on("message",(t=>{const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(e,1),r.writeUInt16BE(t.length,2);const s={chunks:[r,t],type:i};U?.emit("rtsp",s),U?.resetActivityTimer?.()}))}await e.setup(s,r,d),c[a]=i,a+=2};if(!r){for(const e of n.msections.filter((e=>"audio"===e.type)))await u(e.control,e.codec);0===a&&this.console.warn("sdp did not contain audio track and audio was not reported as missing.")}const l=n.msections.find((e=>"video"===e.type));n.msections=n.msections.filter((e=>e===l||"audio"===e.type)),o=[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n"),this.sdp=Promise.resolve(o),await u(l.control,l.codec),await e.play(),U=await le(this.console,e.rfc4571,o,i.mediaStreamOptions,D,c);const p=U.kill.bind(U);let m=!1;if(U.kill=async()=>{s(),m||(m=!0,e.teardown().finally(p)),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500),e.client.destroy(),p()},!U.isActive)throw new Error("parser was killed before rtsp client started");e.readLoop().finally((()=>U.kill()))}catch(t){throw s(),e.client.destroy(),t}}else{s===Be?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===ke&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;i.inputArguments.unshift(...e.split(" ")),U=await V(i,D)}}F&&u&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async e=>{const t=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;e.inputArguments.unshift(...t.split(" "));const r=await async function(e,t,r,i){const s=e.slice();s.push(...r,...t,..._,"pipe:3"),s.unshift("-hide_banner"),h(i,s);const o=c().spawn(await S.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return m(i,o),{cp:o,generator:b(o.stdio[3])}}(e.inputArguments,M,R,this.console),i=()=>{p(r.cp),U.kill(),r.generator.throw(new Error("killed"))};if(!U.isActive)return void i();U.killed.finally(i);const{resetActivityTimer:s}=B("mp4",i,U,D.timeout);for await(const e of ne(r.generator))s(),U.emit("mp4",e)})).catch((()=>{})),!r&&i&&void 0!==U.inputAudioCodec&&U.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,l);const K=t?.video?.codec;if(K&&void 0!==U.inputVideoCodec&&U.inputVideoCodec!==K&&this.console.warn("Video codec plugin reported vs detected mismatch",K,U.inputVideoCodec),U.inputAudioCodec?De.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."),g)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),U.kill(),this.startPrebufferSession();if(this.parserSession=U,Me.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),N?.refreshAt){let t,r=N;const i=async()=>{if(!U.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await we.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(i.toString());r=o.mediaStreamOptions,s(r)},s=e=>{const r=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),t=setTimeout(i,r)};s(r),U.killed.finally((()=>clearTimeout(t)))}U.killed.finally((()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===U&&(this.parserSession=void 0)}));for(const t of Le){let r,i=0;this.detectedIdrInterval=void 0,U.on(t,(s=>{const o=this.prebuffers[t],n=Date.now(),a=()=>{if(r){const t="number"!=typeof this.detectedIdrInterval;this.detectedIdrInterval=n-r,t&&Me.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}r=n};for(("mdat"===s.type||"h264"===s.type&&Y(s,5))&&a(),o.push({time:n,chunk:s});o.length&&o[0].time<n-1e4;)o.shift(),i++;i>1e3&&(this.prebuffers[t]=o.slice(),i=0)}))}return U}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t,r){this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(this.inactivityTimeout&&!r||(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:r,session:i,socketPromise:s,requestedPrebuffer:o}=e;o&&this.console.log("sending prebuffer",o),j(s,{connect:(s,n)=>{t&&(this.activeClients++,this.printActiveClients());const a=Date.now(),c=t=>{if(e.filter&&!(t=e.filter(t)))return;s(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{i.removeListener(r,c),i.removeListener("killed",d),n()};i.on(r,c),i.once("killed",d);const u=this.prebuffers[r];if("rtsp"!==r)for(const e of u)e.time<a-o||c(e.chunk);else{const e=this.parsers[r].findSyncFrame(u.filter((e=>e.time>=a-o)).map((e=>e.chunk)));for(const t of e)c(t)}return()=>{t&&(this.activeClients--,this.printActiveClients()),this.inactivityCheck(i,t),d()}}})}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;let r=e?.prebuffer;null==r&&(r=1.5*Math.max(4e3,this.detectedIdrInterval||4e3));const{rtspMode:i}=this.getRebroadcastMode(),s=i?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:s;e?.prebuffer&&"mp4"!==o&&"mp4"===e?.container&&(r+=1.5*(this.detectedIdrInterval||4e3));const n=t.negotiateMediaStream(e);let a,c,d,u=await this.sdp;const l=new Map;if("rtsp"===o){const e=G(u);e.msections.length>2&&(e.msections=e.msections.filter((e=>e.codec===n.video?.codec||e.codec===n.audio?.codec)),u=e.toSdp(),d=e=>{const t=l.get(e.type);if(null==t)return;const r=e.chunks.slice(),i=Buffer.from(r[0]);return i.writeUInt8(t,1),r[0]=i,{startStream:e.startStream,chunks:r}});const t=await D();a=t.clientPromise.then((async e=>{u=N(u);const t=new ee(e,u);t.console=this.console,await t.handlePlayback();for(const e of Object.values(t.setupTracks))l.set(e.codec,e.destination);return e})),c=t.url.replace("tcp://","rtsp://")}else{const e=await D();a=e.clientPromise,c=`tcp://127.0.0.1:${e.port}`}n.sdp=u;const p=!1!==e?.refresh;this.handleRebroadcasterClient({isActiveClient:p,container:o,requestedPrebuffer:r,socketPromise:a,session:t,filter:d}),n.prebuffer=r;const{reencodeAudio:m}=this.getAudioConfig();this.audioDisabled?n.audio=null:m&&(n.audio={codec:"aac",encoder:"aac",profile:"aac_low"}),t.inputVideoResolution?.width&&t.inputVideoResolution?.height&&Object.assign(n.video,t.inputVideoResolution);const h=Date.now();let f=0;const g=this.prebuffers[o];for(const e of g)if(!(e.time<h-r))for(const t of e.chunk.chunks)f+=t.length;return{url:c,container:o,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,f).toString(),...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",c],mediaStreamOptions:n}}}class Ue extends se{released=!1;sessions=new Map;streamSettings=_e(this);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();let t,r,i=e?.id;const s=await this.mixinDevice.getVideoStreamOptions();let o;const n=2e6;if(!e.id){switch(e.destination){case"medium-resolution":case"remote":o=this.streamSettings.getRemoteStream(s),r=this.plugin.storageSettings.values.remoteStreamingBitrate;break;case"low-resolution":o=this.streamSettings.getLowResolutionStream(s),r=512e3;break;case"local-recorder":o=this.streamSettings.getRecordingStream(s),r=n;break;case"remote-recorder":o=this.streamSettings.getRemoteRecordingStream(s),r=n;break;default:o=this.streamSettings.getDefaultStream(s),r=n}i=o.stream.id,this.console.log("Selected stream",o.stream.name),this.streamSettings.storageSettings.values.transcodeStreams?.includes(o.title)&&(t=this.plugin.storageSettings.values.h264EncoderArguments?.split(" "))}const a=this.sessions.get(i);if(!a)return this.mixinDevice.getVideoStream(e);const c=await a.getVideoStream(e);return c.h264EncoderArguments=t,c.destinationVideoBitrate=r,this.streamSettings.storageSettings.values.missingCodecParameters&&(c.h264FilterArguments=c.h264FilterArguments||[],c.h264FilterArguments.push("-bsf:v","dump_extra")),c.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" "),we.createFFmpegMediaObject(c,{sourceId:this.id})}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),r=this.getPrebufferedStreams(t),i=r?r.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"),Pe.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);let n=0;for(const e of s){let r=this.sessions.get(e);if(!r){const s=t?.find((t=>t.id===e));s?.prebuffer&&Pe.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const a=s?.name,c=i.includes(e);if(r=new He(this,s,o||!c),this.sessions.set(e,r),o){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)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{const t=await r.parserSessionPromise;n++,e=!0,this.online=!!n,await t.killed,this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&n--,e=!1,this.online=!!n}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.streamSettings.storageSettings.values.defaultStream;let r=this.sessions.get(t?.find((t=>t.name===e))?.id);r||(r=this.sessions.get(t?.find((e=>e.id===i[0]))?.id)),r||(r=this.sessions.get(t?.find((e=>e.id===s?.[0]))?.id)),r?(this.sessions.set(void 0,r),this.console.log("Default Stream:",r.advertisedMediaStreamOptions.id,r.advertisedMediaStreamOptions.name)):this.console.warn("Unable to find Default Stream?")}Me.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];e.push(...await this.streamSettings.storageSettings.getSettings());for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){this.console.error("error in prebuffer session getMixinSettings",e)}return e}async putMixinSetting(e,t){if(this.streamSettings.storageSettings.settings[e]?await this.streamSettings.storageSettings.putSetting(e,t):this.storage.setItem(e,t?.toString()),"Transcoding"===this.streamSettings.storageSettings.settings[e]?.group)return;const r=this.sessions;this.sessions=new Map;for(const e of r.values())e?.parserSessionPromise?.then((e=>e.kill()));this.ensurePrebufferSessions()}getPrebufferedStreams(e){return be(this.streamSettings.storageSettings,e)}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getPrebufferedStreams(e);for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=1e4);return e}setVideoStreamOptions(e){const t=this.sessions.get(e.id);if(t&&e?.video?.bitrate){t.needBitrateReset=!0;const r=t.maxBitrate;r&&e?.video?.bitrate>r&&(this.console.log("clamping max bitrate request",e.video.bitrate,r),e.video.bitrate=r)}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 Ne=new class extends n{storageSettings=new re(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"},remoteStreamingBitrate:{title:"Remote Streaming Bitrate",type:"number",defaultValue:1e6,description:"The bitrate to use when remote streaming. This setting will only be used when transcoding or adaptive bitrate is enabled on a camera."},h264EncoderArguments:{title:"H264 Encoder Arguments",description:"FFmpeg arguments used to encode h264 video. This is not camera specific and is used to setup the hardware accelerated encoder on your Scrypted server. This setting will only be used when transcoding is enabled on a camera.",choices:Object.keys(ve()),defaultValue:ve()["libx264 (Software)"].join(" "),combobox:!0,mapPut:(e,t)=>ve()[t]?.join(" ")||t||ve()["libx264 (Software)"]?.join(" ")}});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(xe.getSystemState())){const t=xe.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout((()=>Me.requestRestart()),r),this.startRtspServer()}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){T(this.rtspServer),this.rtspServer=new(P().Server)((async e=>{let t;const r=new ee(e,void 0,void 0,(async(e,i,s,o)=>{r.checkRequest=void 0;const n=new URL(i);for(const e of this.currentMixins.keys()){const i=this.currentMixins.get(e);for(const e of i.sessions.values())if(n.pathname.endsWith(e.rtspServerPath))return r.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,r.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),r.console=this.console;try{await r.handlePlayback();const i=await t.parserSessionPromise,s=1.5*Math.max(4e3,t.detectedIdrInterval||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await r.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,r){const i=JSON.parse(e.toString()),{url:s,sdp:o}=i,n=G(o),a=new Map;for(const e of n.msections)for(const t of e.payloadTypes)a.set(t,e.control);const c=new URL(s);if(!c.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:d,url:u}=await D(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new ee(e,o);await t.handlePlayback();const r=P().connect(parseInt(c.port),c.hostname);for(e.on("close",(()=>{r.destroy()})),r.on("close",(()=>{e.destroy()}));;){const i=(await g(r,2)).readInt16BE(0),s=await g(r,i),o=127&s[1],n=a.get(o);if(!n)throw e.destroy(),r.destroy(),new Error("unknown payload type "+o);t.sendTrack(n,s,!1)}})),Buffer.from(JSON.stringify(l))}async canMixin(t,r){if(!r.includes(e.ScryptedInterface.VideoCamera))return null;const i=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Ue(this,{mixinDevice:e,mixinDeviceState:r,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Stream Management",groupKey:"prebuffer"});return this.currentMixins.set(r.id,i),i}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var o in i)s[o]=i[o];i.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
1
|
+
(()=>{var e={747:(e,t,r)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return o.default}});var i=a(r(257)),s=a(r(816)),o=a(r(243)),n=a(r(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,o.default,n.default];s.default,o.default,n.default},816:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},o=r(761);const n=["realm"],a=n,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,o.parseHTTPHeadersQuotedKeyValueSet)(e,a,n)},buildWWWAuthenticateRest:function(e){return(0,o.buildHTTPHeadersQuotedKeyValueSet)(e,a,n)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:r}=c.decodeHash(e);return{hash:e,username:t,password:r}},buildAuthorizationRest:function({hash:e,username:t,password:r}){if(t&&r)return c.computeHash({username:t,password:r});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,...r]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:r.join(":")}}};var d=c;t.default=d},828:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},o=r(761);const n=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,o.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===n.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,n);return(0,o.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,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=r(761),o=(i=r(113))&&i.__esModule?i:{default:i};const n=["realm","nonce"],a=[...n,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const r=o.default.createHash(e);return r.update(t),r.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,n)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,n)},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(":")),r=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,r].join(":"))}};t.default=l},761:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){const i=e.trim().match(o);if(!i)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=i.map(((e,t)=>{const r=e.split("=");if(2!==r.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,r.length);return r})).reduce((function(e,[r,i],o){if(-1===t.indexOf(r))throw new s.default("E_UNAUTHORIZED_KEY",o,r);return e[r]=i.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return n(r,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){return n(r,e),t.reduce((function(t,r){return e[r]?t+(t?", ":"")+r+'="'+e[r]+'"':t}),"")};var i,s=(i=r(257))&&i.__esModule?i:{default:i};const o=/\w+=(".*?"|[^",]+)(?=,|$)/g;function n(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,r){"use strict";var i=this&&this.__createBinding||(Object.create?function(e,t,r,i){void 0===i&&(i=r),Object.defineProperty(e,i,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,i){void 0===i&&(i=r),e[i]=t[r]}),s=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||i(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,s(r(741),t);const o=r(741);class n extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}async createMediaObject(e,t){return mediaManager.createMediaObject(e,t,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:deviceManager.getMixinConsole(e.sourceId,this.nativeId)}_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=n;class a extends o.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}async createMediaObject(e,t){return mediaManager.createMediaObject(e,t,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:deviceManager.getMixinConsole(e.sourceId,this.mixinProviderNativeId)}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 r of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(n.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(a.prototype,r,{set:t(r),get:e(r)})}();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},733:e=>{const t=require("realfs");e.exports=t},741:(e,t,r)=>{"use strict";r.r(t),r.d(t,{DeviceBase:()=>i,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>n,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>l,MediaPlayerState:()=>p,ScryptedInterface:()=>m,ScryptedMimeTypes:()=>h});class i{}let s;!function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.pluginId="pluginId",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 o={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","pluginId","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 n,a,c,d,u,l,p,m,h;!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"}(n||(n={})),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"}(m||(m={})),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.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(h||(h={}))},113:e=>{"use strict";e.exports=require("crypto")},37:e=>{"use strict";e.exports=require("os")},257:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>h});var i=r(37);function s(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function o(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function n(e){var t="function"==typeof Map?new Map:void 0;return n=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;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,i)}function i(){return a(e,arguments,u(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),d(i,e)},n(e)}function a(e,t,r){return a=c()?Reflect.construct:function(e,t,r){var i=[null];i.push.apply(i,t);var s=new(Function.bind.apply(e,i));return r&&d(s,r.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,r,...i){var s,n,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(i=(void 0===r?[]:[r]).concat(i),r=e,e=[]),n=this,(s=!(a=u(t).call(this,r))||"object"!=typeof a&&"function"!=typeof a?o(n):a).code=r||"E_UNEXPECTED",s.params=i,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(o(s),s.constructor),s}var r,n,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),r=t,(n=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+i.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(r.prototype,n),a&&s(r,a),t}(n(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&m(e.code)&&e.params&&e.params instanceof Array}function m(e){return/^([A-Z0-9_]+)$/.test(e)}l.wrap=function(e,t,...r){let i=null;const s=m(e.message),o=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&r.push(e.message),i=new l(o,t,...r),i},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 h=l}},t={};function r(i){var s=t[i];if(void 0!==s)return s.exports;var o=t[i]={exports:{}};return e[i].call(o.exports,o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};(()=>{"use strict";r.r(i),r.d(i,{default:()=>Ne});var e=r(510),t=r.n(e);const{systemManager:s}=t(),o="v4";class n 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=s.getComponent("plugins"),s.listen((async(t,r,i)=>{r.eventInterface!==e.ScryptedInterface.ScryptedDevice||r.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(s.getSystemState())){const t=s.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]===o)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 r=await this.pluginsComponent;await r.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==o&&(this.hasEnabledMixin[e]=o,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}var a=r(37),c=r.n(a);const d=["BCM2708","BCM2709","BCM2710","BCM2835","BCM2836","BCM2837","BCM2837B0","BCM2711"];function u(){let e;try{e=r(733).readFileSync("/proc/cpuinfo",{encoding:"utf8"})}catch(e){return!1}const t=e.split("\n").map((e=>e.replace(/\t/g,""))).filter((e=>e.length>0)).map((e=>e.split(":"))).map((e=>e.map((e=>e.trim())))).filter((e=>"Hardware"===e[0]));if(!t||0==t.length)return!1;return function(e){return d.indexOf(e)>-1}(t[0][1])}const l="Video4Linux (Docker compatible)";function p(){if(u()){return{}}if("darwin"===c().platform())return{VideoToolbox:["-hwaccel","auto"]};const e={"Nvidia CUDA":["-vsync","0","–hwaccel","cuda","-hwaccel_output_format","cuda"],"Nvidia CUVID":["-vsync","0","–hwaccel","cuvid","-c:v","h264_cuvid"]};if(u())e["Raspberry Pi"]=["-c:v","h264_mmal"],e[l]=["-c:v","h264_v4l2m2m"];else if("linux"===c().platform())e[l]=["-c:v","h264_v4l2m2m"];else{if("win32"!==c().platform())return{};e["Intel QuickSync"]=["-c:v","h264_qsv"]}return e}function m(){const e={"Copy Video, Transcode Audio":"copy"};if(u());else if("darwin"===c().platform())e.VideoToolbox="h264_videotoolbox";else if("win32"===c().platform())e["Intel QuickSync"]="h264_qsv",e.AMD="h264_amf",e.Nvidia="h264_nvenc";else{if("linux"!==c().platform())return{};e.V4L2="h264_v4l2m2m",e.VAAPI="h264_vaapi",e.Nvidia="nvenc_h264"}const t={};for(const[r,i]of Object.entries(e))t[r]=["-c:v",i];return u()&&(t["Raspberry Pi"]=["-pix_fmt","yuv420p","-c:v","h264_v4l2m2m"]),t["libx264 (Software)"]=["-c:v","libx264","-preset","ultrafast","-bf","0","-force_key_frames","expr:gte(t,n_forced*4)"],t}const h=require("child_process");var f=r.n(h);const g=require("process");var y=r.n(g);const v=["decode_slice_header error","no frame!","non-existing PPS"];function S(e){if(e){try{e.stdin.write("q\n")}catch(e){}setTimeout((()=>{e.kill(),setTimeout((()=>{e.kill("SIGKILL")}),2e3)}),2e3)}}function b(e,t,r,i){const s=!!y().env.SCRYPTED_FFMPEG_NOISY||!!i?.getItem("SCRYPTED_FFMPEG_NOISY");function o(e){const i=o=>{const n=o.toString();for(const e of v)if(-1!==n.indexOf(e))return;if(!(s||r||-1===n.indexOf("frame=")&&-1===n.indexOf("size=")))return e(n),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(n)};return i}t.stdout?.on("data",o(e.log)),t.stderr?.on("data",o(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function _(e,t){const r=[];let i=!1;for(const e of t){try{if(i){const t=new URL(e);r.push(`${t.protocol}[REDACTED]`)}else r.push(e)}catch(t){r.push(e)}i="-i"===e}e.log(r.join(" "))}const w=require("events");async function P(e,t){if(e.readableEnded||e.destroyed)throw new Error("stream ended");if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise(((r,i)=>{const s=()=>{const s=e.read(t);if(s)return n(),void r(s);(e.readableEnded||e.destroyed)&&i(new Error("stream ended during read"))},o=()=>{n(),i(new Error(`stream ended during read for minimum ${t} bytes`))},n=()=>{e.removeListener("readable",s),e.removeListener("end",o)};e.on("readable",s),e.on("end",o)}))}const x="\n".charCodeAt(0);async function M(e){return async function(e,t){const r=[];let i=0;for(;;){const s=await P(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;r[i++]=s[0]}return Buffer.from(r).toString()}(e,x)}const{mediaManager:T}=t();async function*I(e){for(;;){const t=await P(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),s=await P(e,r);yield{header:t,length:r,type:i,data:s}}}const C=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];const R=require("net");var O=r.n(R);const D=require("dgram");var E=r.n(D);async function A(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function k(e,t){e.bind(t),await(0,w.once)(e,"listening");const r=e.address().port;return{port:r,url:`udp://127.0.0.1:${r}`}}async function B(e){return k(e,0)}async function V(){return async function(e){const t=E().createSocket("udp4"),{port:r,url:i}=await k(t,e);return{server:t,port:r,url:i}}(0)}async function L(e,t){return e.bind(t),await(0,w.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function j(){const e=new(O().Server),t=await async function(e){return e.listen(0),await(0,w.once)(e,"listening"),e.address().port}(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{r(new Error("timeout waiting for client"))}),3e4);e.on("connection",(r=>{e.close(),clearTimeout(i),t(r)}))}));return{server:e,url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:r}}function H(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:U}=t();async function N(e,t){return new Promise((r=>{const i=s=>{const o=s.toString(),n=o.indexOf(`${t}: `);if(-1!==n){const s=o.substring(n+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),r(s.substring(0,a)))}};e.stdout.on("data",i),e.stderr.on("data",i)}))}function K(e,t,r,i){let s;let o=Date.now();function n(){o=Date.now()}return i&&(s=setInterval((()=>{Date.now()>o+i&&(clearInterval(s),s=void 0,console.error("timeout waiting for data, killing parser session",e),t())}),i)),r.once("killed",(()=>clearTimeout(s))),n(),{resetActivityTimer:n,clearActivityTimer:function(){clearTimeout(s)}}}async function q(e,t){const{console:r}=t;let i,s=!0;const o=new w.EventEmitter;let n,a,c,d,u;o.on("error",(e=>r.error("rebroadcast error",e)));const l=new Promise(((e,t)=>{d=e,u=t}));let p;const m=new Promise((e=>{p=e}));function h(){s&&(o.emit("killed"),o.emit("error",new Error("killed"))),s=!1,clearTimeout(i),p(),u?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),S(M)}const g=e.inputArguments.slice();i=setTimeout(h,3e4);let y=!1;const v=e=>{if(!s)throw e(),new Error("parser session was killed killed before ffmpeg connected");o.on("killed",e)},P=["pipe","pipe","pipe"];let x=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){y=!0;const r=E().createSocket("udp4"),s=await B(r),n=E().createSocket("udp4");await L(n,s.port+1),v((()=>{r.close(),n.close()})),g.push(...i.outputArguments,s.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=K(e,h,o,t?.timeout);(async()=>{for await(const t of i.parseDatagram(r,parseInt(c?.[2]),parseInt(c?.[3])))d?.(void 0),o.emit(e,t),a()})(),(async()=>{for await(const t of i.parseDatagram(n,parseInt(c?.[2]),parseInt(c?.[3]),"rtcp"))d?.(void 0),o.emit(e,t),a()})()}else if(i.tcpProtocol){const s=await j(),n=new URL(i.tcpProtocol);n.port=s.port.toString(),g.push(...i.outputArguments,n.toString());const{resetActivityTimer:a}=K(e,h,o,t?.timeout);(async()=>{const t=await s.clientPromise;try{v((()=>t.destroy()));for await(const r of i.parse(t,parseInt(c?.[2]),parseInt(c?.[3])))d?.(void 0),o.emit(e,r),a()}catch(e){r.error("rebroadcast parse error",e),h()}})()}else g.push(...i.outputArguments,"pipe:"+x++),P.push("pipe")}y&&(g.push("-sdp_file","pipe:"+x++),P.push("pipe")),g.unshift("-hide_banner"),_(r,g);const M=f().spawn(await U.getFFmpegPath(),g,{stdio:P});let T;b(r,M,void 0,t?.storage),M.on("exit",h),T=y?new Promise((e=>{const t=[];M.stdio[x-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let I=0;return Object.keys(t.parsers).forEach((async e=>{const i=t.parsers[e];if(!i.parse||i.tcpProtocol)return;const s=M.stdio[3+I];I++;try{const{resetActivityTimer:r}=K(e,h,o,t?.timeout);for await(const t of i.parse(s,parseInt(c?.[2]),parseInt(c?.[3])))d?.(void 0),o.emit(e,t),r()}catch(e){r.error("rebroadcast parse error",e),h()}})),async function(e){return N(e,"Audio")}(M).then((e=>n=e)),async function(e){return N(e,"Video")}(M).then((e=>a=e)),async function(e){return new Promise((t=>{const r=i=>{const s=i.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);o&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(o))};e.stdout.on("data",r),e.stderr.on("data",r)}))}(M).then((e=>c=e)),await l,d=void 0,u=void 0,clearTimeout(i),{sdp:T,inputAudioCodec:n,inputVideoCodec:a,inputVideoResolution:{width:parseInt(c?.[1]),height:parseInt(c?.[2])},get isActive(){return s},kill:h,killed:m,negotiateMediaStream:()=>{const t=H(e.mediaStreamOptions)||{id:void 0,name:void 0};return t.video||(t.video={}),t.video.codec=a,t.audio=t?.audio?.codec===n?e?.mediaStreamOptions?.audio:{codec:n},t},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 F(e,t){const r=await e;let i=!0;const s=()=>{r.removeAllListeners(),r.destroy();const e=o;o=void 0,e?.()};let o=t?.connect((e=>{i&&(i=!1,e.startStream&&r.write(e.startStream));for(const t of e.chunks)r.write(t);return r.writableLength}),s);r.once("close",(()=>{s()})),r.on("error",(e=>t?.console?.log("client stream ended")))}const $=require("stream");var G=r(113),W=r.n(G);function z(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));let r=0;for(let e=0;e<t.length;e++){t[e].startsWith("m=")&&(t.splice(e+1,0,"a=control:trackID="+r),r++)}return t.join("\r\n")}function J(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function Q(e){return e.filter((e=>e.startsWith("a=fmtp"))).map((e=>{const t=e.indexOf(" ");if(-1===t)return;const r=e.substring(0,t),i=e.substring(t+1),s=parseInt(r.split(":")[1]);if(!r||!i||NaN===s)return;const o={};return i.split(";").map((e=>e.trim())).forEach((e=>{const[t,...r]=e.split("=");o[t]=r.join("=")})),{payloadType:s,parameters:o}})).filter((e=>!!e))}const Y="a=control:";function X(e){const t=e.find((e=>e.startsWith(Y)))?.substring(Y.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase();let i,s;r?.includes("mpeg4")?i="aac":r?.includes("opus")?i="opus":r?.includes("pcm")?i="pcm":r?.includes("h264")?i="h264":r?.includes("h265")&&(i="h265");for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){s=t;break}}return{...(o=e[0],{type:o.split(" ")[0].substring(2),payloadTypes:J(o)}),fmtp:Q(e),lines:e,contents:e.join("\r\n"),control:t,codec:i,direction:s};var o}function Z(e){const t=e.split("\n").map((e=>e.trim())),r=[],i=[];let s;for(const e of t)e.startsWith("m=")&&(s&&i.push(s),s=[]),s?s.push(e):r.push(e);s&&i.push(s);const o={header:{lines:r,contents:r.join("\r\n")},msections:i.map(X),toSdp:()=>[...o.header.lines,...o.msections.map((e=>e.lines)).flat()].join("\r\n")};return o}const ee=require("tls");var te=r.n(ee),re=r(747);class ie extends Error{constructor(){super("Operation Timed Out")}}function se(e,t){if("h264"!==e.type)return;const r=e.chunks[e.chunks.length-1].subarray(12),i=31&r[0];if(24===i){let e=1;for(;e<r.length;){const i=r.readUInt16BE(e);e+=2;if((31&r[e])===t)return r.subarray(e,e+i);e+=i}}else if(28===i){const e=31&r[1],i=!!(128&r[1]);if(e===t&&i)return r.subarray(1)}else if(i===t)return r}function oe(e){const t={};for(const r of e.slice(1)){const e=r.indexOf(":");let i="";-1!==e&&(i=r.substring(e+1).trim());t[r.substring(0,e).toLowerCase()]=i}return t}class ne extends class{constructor(e){this.console=e}write(e,t,r){let i=`${e}\r\n`;r&&(t["Content-Length"]=r.length.toString());for(const[e,r]of Object.entries(t))i+=`${e}: ${r}\r\n`;i+="\r\n",this.client.write(i),this.console?.log("rtsp outgoing message\n",i),this.console?.log(),r&&this.client.write(r)}async readMessage(){const e=await async function(e){let t=[];for(;;){let r=await M(e);if(r=r.trim(),!r)return t;t.push(r)}}(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;rfc4571=new $.PassThrough;needKeepAlive=!1;constructor(e,t){super(t),this.url=e;const r=new URL(e),i=parseInt(r.port)||554;e.startsWith("rtsps")?this.client=te().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=O().connect(i,r.hostname)}writeRequest(e,t,r,i){t=t||{};let s=this.url;r&&(r.includes("rtsp://")||r.includes("rtsps://")?s=r:s+="/"+r);const o=new URL(s);o.username="",o.password="",s=o.toString();const n=`${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(n,t,i)}async handleDataPayload(e){if(36!==e[0])throw new Error("RTSP Client expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),r=await P(this.client,t);this.rfc4571.push(e),this.rfc4571.push(r)}async readDataPayload(){const e=await P(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 P(this.client,4);if("RTSP"===e.toString()){this.client.unshift(e);return await super.readMessage()}await this.handleDataPayload(e)}}async request(e,t,r,i,s){this.writeRequest(e,t,r,i);const o=this.requestTimeout?await(n=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new ie)),n),a.then(e),a.catch(t)}))):await this.readMessage();var n,a;const c=o[0],d=oe(o);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 o=new URL(this.url);if(u.includes("Basic")){const e=re.rh.computeHash(o);this.authorization=`Basic ${e}`}else{const t=re.Nu.parseWWWAuthenticateRest(u),r=decodeURIComponent(o.username),i=decodeURIComponent(o.password),s=W().createHash("md5").update(`${r}:${t.realm}:${i}`).digest("hex"),n=W().createHash("md5").update(`${e}:${o.pathname}`).digest("hex"),a=W().createHash("md5").update(`${s}:${t.nonce}:${n}`).digest("hex"),c={username:r,realm:t.realm,nonce:t.nonce,uri:o.pathname,algorithm:"MD5",response:a},d=Object.entries(c).map((([e,t])=>{return`${e}=${t&&(r=t,`"${r.replace(/"/g,'\\"')}"`)}`;var r})).join(", ");this.authorization=`Digest ${d}`}return this.request(e,t,r,i,!0)}const l=parseInt(d["content-length"]);return l?{headers:d,body:await P(this.client,l)}:{headers:d,body:void 0}}async options(){return this.request("OPTIONS",{})}async getParameter(){return this.request("GET_PARAMETER")}writeGetParameter(){return this.writeRequest("GET_PARAMETER")}async describe(e){return this.request("DESCRIBE",{...e||{},Accept:"application/sdp"})}async setup(e,t,r){const i={Transport:`RTP/AVP/${r?"UDP":"TCP"};unicast;${r?"client_port":"interleaved"}=${e}-${e+1}`},s=await this.request("SETUP",i,t);if(s.headers.session){const e=function(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}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)}writePlay(e="0.000"){const t={Range:`npt=${e}-`};return this.writeRequest("PLAY",t)}async pause(){return this.request("PAUSE")}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}writeTeardown(){this.writeRequest("TEARDOWN")}}class ae{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,G.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await M(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 P(this.client,4);if(36!==e[0])throw new Error("RTSP Server expected frame magic but received: "+e.toString());const t=e.readUInt16BE(2),r=await P(this.client,t),i=e.readUInt8(1),s=i-i%2,o=Object.values(this.setupTracks).find((e=>e.destination===s));if(!o)throw new Error("RSTP Server received unknown channel: "+i);yield{type:o.codec,rtcp:i%2==1,header:e,packet:r}}}send(e,t){const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(t,1),r.writeUInt16BE(e.length,2),this.client.write(r),this.client.write(Buffer.from(e))}sendUdp(e,t,r){this.udp.send(t,r?e+1:e,"127.0.0.1")}sendTrack(e,t,r){const i=this.setupTracks[e];i?"udp"!==i.protocol?this.send(t,r?i.destination+1:i.destination):this.udp?this.sendUdp(i.destination,t,r):this.console?.warn("RTSP Server UDP socket not available."):this.console?.warn("RTSP Server track not found:",e)}options(e,t){const r={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,r)}describe(e,t){const r={};r["Content-Base"]=e,r["Content-Type"]="application/sdp",this.respond(200,"OK",t,r,Buffer.from(this.sdp))}setup(e,t){const r={},i=t.transport;r.Transport=i,r.Session=this.session;const s=Z(this.sdp).msections.find((t=>e.endsWith(t.control)));if(s){if(i.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const e=i.match(/.*?client_port=([0-9]+)-([0-9]+)/),[r,o,n]=e;this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(o),codec:s.codec}}else if(i.includes("TCP")){const e=i.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const t=parseInt(e[1]);parseInt(e[2]);this.setupTracks[s.control]={control:s.control,protocol:"tcp",destination:t,codec:s.codec}}}this.respond(200,"OK",t,r)}else this.respond(404,"Not Found",t,r)}play(e,t){const r={},i=Object.values(this.setupTracks).map((t=>`url=${e}/${t.control}`)).join(",")+";seq=0;rtptime=0";r["RTP-Info"]=i,r.Range="npt=now-",r.Session=this.session,this.respond(200,"OK",t,r)}async announce(e,t){const r=parseInt(t["content-length"]),i=await P(this.client,r);this.sdp=i.toString();const s={};s.Session=this.session,this.respond(200,"OK",t,s)}async record(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async teardown(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=oe(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,r,i,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",i,{}),this.client.destroy(),new Error("check request failed")}if(this[t])return await this[t](r,i),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",i,{})}respond(e,t,r,i,s){let o=`RTSP/1.0 ${e} ${t}\r\n`;r.cseq&&(i.CSeq=r.cseq),s&&(i["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(i))o+=`${e}: ${t}\r\n`;this.console?.log("response headers",o),o+="\r\n",this.client.write(o),s&&(this.client.write(s),this.console?.log("response body",s.toString()))}}const{systemManager:ce}=t();class de{values={};hasValue={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const r=t[e],i=()=>this.getItem(e);let s;s="clippath"!==r.type?i:()=>{try{return JSON.parse(i())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)}),Object.defineProperty(this.hasValue,e,{get:()=>null!=this.device.storage.getItem(e)})}}get keys(){const e={};for(const t of Object.keys(this.settings))e[t]=t;return e}async getSettings(){const e=await(this.options?.onGet?.()),t=[];for(const[r,i]of Object.entries(this.settings)){let s=Object.assign({},i);e?.[r]&&(s=Object.assign(s,e[r])),s.onGet&&(s=Object.assign(s,await s.onGet())),s.hide||await(this.options?.hide?.[r]?.())||(s.key=r,s.value=this.getItemInternal(r,s),t.push(s),delete s.onPut,delete s.onGet,delete s.mapPut,delete s.mapGet)}return t}async putSetting(t,r){const i=this.settings[t];let s;i&&(s=this.getItemInternal(t,i)),i?.noStore||(i.mapPut&&(r=i.mapPut(s,r)),"object"==typeof r?this.device.storage.setItem(t,JSON.stringify(r)):this.device.storage.setItem(t,r?.toString())),i?.onPut?.(s,r),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItemInternal(e,t){if(!t)return this.device.storage.getItem(e);const r=function(e,t){const{defaultValue:r}=t,i=t.multiple?"array":t.type;if("boolean"===i)return"true"===e||"false"!==e&&(r||!1);if("number"===i)return parseFloat(e)||r||0;if("integer"===i)return parseInt(e)||r||0;if("array"===i)try{return JSON.parse(e)}catch(e){return r||[]}if("device"===i)return ce.getDeviceById(e);if(e&&t.json)try{return JSON.parse(e)}catch(e){return r}return e||r}(this.device.storage.getItem(e),t);return t.mapGet?t.mapGet(r):r}getItem(e){return this.getItemInternal(e,this.settings[e])}}const{deviceManager:ue}=t();class le extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>ue.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,r=this.getMixinSettings(),i=[];try{const e=await t||[];i.push(...e)}catch(e){const t=this.name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}try{const e=await r||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;i.push(...e)}catch(e){const t=ue.getDeviceState(this.mixinProviderNativeId).name,r=`${t} Extension settings failed to load.`;this.console.error(r,e),i.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:r,readonly:!0})}return i}async putSetting(t,r){const i=this.settingsGroupKey+":";if(!t?.startsWith(i))return this.mixinDevice.putSetting(t,r);await this.putMixinSetting(t.substring(i.length),r),ue.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await ue.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}function pe(e){return e}async function*me(e){let t,r,i;for await(const s of e)t?r||(r=s):t=s,yield{startStream:i,chunks:[s.header,s.data],type:s.type},t&&r&&!i&&(i=Buffer.concat([t.header,t.data,r.header,r.data]))}class he{bitoffset=0;constructor(e){this.view=e}ExpGolomb(){const{view:e}=this;let{zeros:t,skip:r,byt:i,byteoffset:s}=function(e,t){let r=0,i=t>>3,s=7&t,o=-1,n=e.getUint8(i)<<s;do{r=128&n,n<<=1,o++,s++,8===s&&(s=0,i++,n=e.getUint8(i))}while(!r);return{zeros:o,skip:s,byt:n,byteoffset:i}}(e,this.bitoffset),o=1;for(;t>0;)o=o<<1|(128&i)>>>7,i<<=1,r++,t--,8===r&&(r=0,s++,i=e.getUint8(s));return this.bitoffset=s<<3|r,o-1}SignedExpGolomb(){const e=this.ExpGolomb();return 1&e?e+1>>>1:-(e>>>1)}readBit(){const e=7&this.bitoffset,t=this.bitoffset>>3;return this.bitoffset++,this.view.getUint8(t)>>7-e&1}readByte(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=8;const r=this.view.getUint8(t);if(0===e)return r;return r<<e|this.view.getUint8(t+1)>>8-e}}function fe(e,t){let r=8,i=8;const s=[];for(let o=0;o<t;o++){if(0!==i){i=(r+e.SignedExpGolomb()+256)%256}i&&(r=i),s.push(i)}return s}function ge(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new he(new DataView(e.buffer,e.byteOffset+4)),r=e[1],i=e[2],s=e[3],o=t.ExpGolomb();let n=1,a=0,c=0,d=0,u=0,l=0;const p=[];if(100===r||110===r||122===r||244===r||44===r||83===r||86===r||118===r||128===r){n=t.ExpGolomb();let e=8;if(3===n&&(e=12,d=t.readBit()),a=t.ExpGolomb()+8,c=t.ExpGolomb()+8,u=t.readBit(),l=t.readBit(),l){let r=0;for(;r<6;r++)t.readBit()&&p.push(fe(t,16));for(;r<e;r++)t.readBit()&&p.push(fe(t,64))}}const m=t.ExpGolomb()+4,h=t.ExpGolomb();let f=0,g=0,y=0;const v=[];let S=0;if(0===h)S=t.ExpGolomb()+4;else if(1===h){f=t.readBit(),g=t.SignedExpGolomb(),y=t.SignedExpGolomb();const e=t.SignedExpGolomb();for(let r=0;r<e;r++)v.push(t.SignedExpGolomb())}const b=t.ExpGolomb(),_=t.readBit(),w=t.ExpGolomb()+1,P=t.ExpGolomb()+1,x=t.readBit();let M=0;x||(M=t.readBit());const T=t.readBit(),I=t.readBit(),C=function(e,t){return e?{left:t.ExpGolomb(),right:t.ExpGolomb(),top:t.ExpGolomb(),bottom:t.ExpGolomb()}:{left:0,right:0,top:0,bottom:0}}(I,t),R=t.readBit(),O=function(e,t){const r={aspect_ratio_info_present_flag:0,aspect_ratio_idc:0,sar_width:0,sar_height:0,overscan_info_present_flag:0,overscan_appropriate_flag:0,video_signal_type_present_flag:0,video_format:0,video_full_range_flag:0,colour_description_present_flag:0,colour_primaries:0,transfer_characteristics:0,matrix_coefficients:0,chroma_loc_info_present_flag:0,chroma_sample_loc_type_top_field:0,chroma_sample_loc_type_bottom_field:0,nal_hrd_parameters_present_flag:0,vcl_hrd_parameters_present_flag:0,low_delay_hrd_flag:0,pic_struct_present_flag:0,bitstream_restriction_flag:0,motion_vectors_over_pic_boundaries_flag:0,max_bytes_per_pic_denom:0,max_bits_per_mb_denom:0,log2_max_mv_length_horizontal:0,log2_max_mv_length_vertical:0,num_reorder_frames:0,max_dec_frame_buffering:0};return e&&(r.aspect_ratio_info_present_flag=t.readBit(),r.aspect_ratio_info_present_flag&&(r.aspect_ratio_idc=t.ExpGolomb(),255===r.aspect_ratio_idc&&(r.sar_width=t.readByte(),r.sar_height=t.readByte())),r.overscan_info_present_flag=t.readBit(),r.overscan_info_present_flag&&(r.overscan_appropriate_flag=t.readBit()),r.video_signal_type_present_flag=t.readBit(),r.video_signal_type_present_flag&&(r.video_format=t.readBit()<<2|t.readBit()<<1|t.readBit(),r.video_full_range_flag=t.readBit(),r.colour_description_present_flag=t.readBit(),r.colour_description_present_flag&&(r.colour_primaries=t.readByte(),r.transfer_characteristics=t.readByte(),r.matrix_coefficients=t.readByte())),r.chroma_loc_info_present_flag=t.readBit(),r.chroma_loc_info_present_flag&&(r.chroma_sample_loc_type_top_field=t.ExpGolomb(),r.chroma_sample_loc_type_bottom_field=t.ExpGolomb()),r.nal_hrd_parameters_present_flag=t.readBit(),r.vcl_hrd_parameters_present_flag=t.readBit(),(r.nal_hrd_parameters_present_flag||r.vcl_hrd_parameters_present_flag)&&(r.low_delay_hrd_flag=t.readBit()),r.pic_struct_present_flag=t.readBit(),r.bitstream_restriction_flag=t.readBit(),r.bitstream_restriction_flag&&(r.bitstream_restriction_flag=t.readBit(),r.motion_vectors_over_pic_boundaries_flag=t.readBit(),r.max_bytes_per_pic_denom=t.ExpGolomb(),r.max_bits_per_mb_denom=t.ExpGolomb(),r.log2_max_mv_length_horizontal=t.ExpGolomb(),r.log2_max_mv_length_vertical=t.ExpGolomb(),r.num_reorder_frames=t.ExpGolomb(),r.max_dec_frame_buffering=t.ExpGolomb())),r}(R,t);return{sps_id:o,profile_compatibility:i,profile_idc:r,level_idc:s,chroma_format_idc:n,bit_depth_luma:a,bit_depth_chroma:c,color_plane_flag:d,qpprime_y_zero_transform_bypass_flag:u,seq_scaling_matrix_present_flag:l,seq_scaling_matrix:p,log2_max_frame_num:m,pic_order_cnt_type:h,delta_pic_order_always_zero_flag:f,offset_for_non_ref_pic:g,offset_for_top_to_bottom_field:y,offset_for_ref_frame:v,log2_max_pic_order_cnt_lsb:S,max_num_ref_frames:b,gaps_in_frame_num_value_allowed_flag:_,pic_width_in_mbs:w,pic_height_in_map_units:P,frame_mbs_only_flag:x,mb_adaptive_frame_field_flag:M,direct_8x8_inference_flag:T,frame_cropping_flag:I,frame_cropping:C,vui_parameters_present_flag:R,vui_parameters:O}}function ye(e){let t,r;0==e.chroma_format_idc&&0==e.color_plane_flag?t=r=0:1==e.chroma_format_idc&&0==e.color_plane_flag?t=r=2:2==e.chroma_format_idc&&0==e.color_plane_flag?(t=2,r=1):3==e.chroma_format_idc&&(0==e.color_plane_flag?t=r=1:1==e.color_plane_flag&&(t=r=0));let i=e.pic_width_in_mbs,s=e.pic_height_in_map_units,o=(2-e.frame_mbs_only_flag)*s,n=0,a=0,c=0,d=0;return e.frame_cropping_flag&&(n=e.frame_cropping.left,a=e.frame_cropping.right,c=e.frame_cropping.top,d=e.frame_cropping.bottom),{width:16*i-t*(n+a),height:16*o-r*(2-e.frame_mbs_only_flag)*(c+d)}}async function ve(e,t,r,i,s,o){let n=!0;const a=new $.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=Z(r),d=c.msections.find((e=>"audio"===e.type)),u=c.msections.find((e=>"video"===e.type)),l=d?.payloadTypes?.[0],p=u?.payloadTypes?.[0],m=d?.codec,h=u.codec;let f;const g=new Promise((e=>{f=e})),y=()=>{n&&(a.emit("killed"),a.emit("error",new Error("killed"))),n=!1,f(),t.destroy()};t.on("close",y),t.on("error",y);const{resetActivityTimer:v}=K("rtsp",y,a,s?.timeout);let S;const b=u?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],_=b?.split(",")?.[0];if(_)try{const t=ge(Buffer.from(_,"base64"));S=ye(t),e.log("parsed sdp sps",t)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{const r=o?.channelMap?4:2,i=o?.channelMap?2:0;await async function(e,t){let r;const{skipHeader:i,callback:s}=t,o=t.offset||0,n=t.headerLength||2;let a,c;e.on("error",(e=>r=e));let d=0,u=0;const l=()=>{for(;;){if(d!==u)return;if(a){const t=e.read(c);if(!t)return;s(a,t),a=void 0}else{if(a=e.read(n),!a)return;if(i(a,(()=>u++)))return d++,void(a=void 0);c=a.readUInt16BE(o)}}};throw l(),e.on("readable",l),await(0,w.once)(e,"end"),new Error("stream ended")}(t,{headerLength:r,offset:i,skipHeader:(e,r)=>"RTSP"===e.toString()&&(t.unshift(e),o.handleRTSP().then(r),!0),callback:(t,r)=>{let i;if(o?.onLoop?.(),o?.channelMap){const e=t.readUInt8(1);i=o?.channelMap[e]}else{const e=127&r[1],s=Buffer.alloc(2);s[0]=36,e===l?s[1]=0:e===p&&(s[1]=2),t=Buffer.concat([s,t]),e===l?i=m:e===p&&(i=h)}const s={startStream:undefined,chunks:[t,r],type:i};if(!S){const t=se(s,7);if(t)try{const r=ge(t);S=ye(r),e.log(S),e.log("parsed bitstream sps",r)}catch(t){e.warn("sps parsing failed"),S={width:NaN,height:NaN}}}a.emit("rtsp",s),v()}})})().catch((e=>{throw e})).finally(y),{sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:m,inputVideoCodec:h,get inputVideoResolution(){return S},get isActive(){return n},kill:y,killed:g,resetActivityTimer:v,negotiateMediaStream:e=>{const t=H(i)||{id:void 0,name:void 0};if(t.video||(t.video={}),t.video.codec=h,e?.audio?.codec&&e?.audio?.codec!==m){if(c.msections.find((t=>"audio"===t.type&&t.codec===e?.audio?.codec)))return t.audio={codec:e?.audio?.codec},t}return t.audio=t?.audio?.codec===m?i?.audio:{codec:m},t},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}}}function Se(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}function be(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Se(t)}function _e(e){const t={defaultStream:{title:"Local Stream",description:"The media stream to use when streaming on your local network. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.",hide:!0,prefersPrebuffer:!0,preferredResolution:8294400},remoteStream:{title:"Remote (Medium Resolution) Stream",description:"The media stream to use when streaming from outside your local network. Selecting a low birate stream is recommended. Recommended resolution: 1270x720.",hide:!0,prefersPrebuffer:!1,preferredResolution:921600},lowResolutionStream:{title:"Low Resolution Stream",description:"The media stream to use for low resolution output, such as Apple Watch and Video Analysis. Recommended resolution: 480x360.",hide:!0,prefersPrebuffer:!1,preferredResolution:172800},recordingStream:{title:"Local Recording Stream",description:"The media stream to use when recording to local storage such as an NVR. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.",hide:!0,prefersPrebuffer:!0,preferredResolution:8294400},remoteRecordingStream:{title:"Remote Recording Stream",description:"The media stream to use when recording to cloud storage such as HomeKit Secure Video clips in iCloud. This stream should be prebuffered. Recommended resolution: 1270x720.",hide:!0,prefersPrebuffer:!0,preferredResolution:921600}},r=new de(e,{enabledStreams:{title:"Prebuffered Streams",description:"Prebuffering maintains an active connection to the stream and improves load times. Prebuffer also retains the recent video for capturing motion events with HomeKit Secure video. Enabling Prebuffer is not recommended on Cloud cameras.",multiple:!0,hide:!1},...t,transcodeStreams:{group:"Transcoding",title:"Transcode Streams",description:"The media streams to transcode. Transcoding audio and video is not recommended and should only be used when necessary. The Rebroadcast Plugin manages the system-wide Transcode settings. See the Rebroadcast Readme for optimal configuration.",multiple:!0,choices:Object.values(t).map((e=>e.title))},missingCodecParameters:{group:"Transcoding",title:"Add H264 Extra Data",description:"Some cameras do not include H264 extra data in the stream and this causes live streaming to always fail (but recordings may be working). This is a inexpensive video filter and does not perform a transcode. Enable this setting only as necessary.",type:"boolean"},videoDecoderArguments:{group:"Transcoding",title:"Video Decoder Arguments",description:"FFmpeg arguments used to decode input video when transcoding a stream.",placeholder:"-hwaccel auto",choices:Object.keys(p()),combobox:!0,mapPut:(e,t)=>p()[t]?.join(" ")||t}});function i(e,t){const i=be(r,t);return function(e,t){if(!e)return;let r,i;for(const s of e){const e=Math.abs(s.video?.width*s.video?.height-t);(!r||e<i)&&(r=s,i=e,Number.isNaN(i)&&(i=Number.MAX_SAFE_INTEGER))}return r}(e.prefersPrebuffer&&i?.length>0?i:t,e.preferredResolution)}function s(e,s){const o=r.settings[e],n=r.values[e];let a="Default"===n,c=s?.find((e=>e.name===n));return!a&&c||(a=!0,c=i(o,s)),{title:t[e].title,isDefault:a,stream:c}}function o(e,t){const r=["Default",...t.map((e=>e.name))],s=i(e,t).name;return{defaultValue:"Default",description:e.description+` The default for this stream is ${s}.`,choices:r,hide:!1}}return r.options={onGet:async()=>{let r;try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Se(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream:o(t.defaultStream,i),remoteStream:o(t.remoteStream,i),lowResolutionStream:o(t.lowResolutionStream,i),recordingStream:o(t.recordingStream,i),remoteRecordingStream:o(t.remoteRecordingStream,i)}:{enabledStreams:r}}catch(t){e.console.error("error retrieving getVideoStreamOptions",t)}return{}}},{getDefaultStream:e=>s(r.keys.defaultStream,e),getRemoteStream:e=>s(r.keys.remoteStream,e),getLowResolutionStream:e=>s(r.keys.lowResolutionStream,e),getRecordingStream:e=>s(r.keys.recordingStream,e),getRemoteRecordingStream:e=>s(r.keys.remoteRecordingStream,e),storageSettings:r}}const{mediaManager:we,log:Pe,systemManager:xe,deviceManager:Me}=t(),Te="Default",Ie="AAC or No Audio",Ce=`${Ie} (Copy)`,Re="Compatible Audio",Oe="Other Audio",De=["aac","mp3","mp2","opus"],Ee="-fflags +genpts",Ae="Scrypted",ke="FFmpeg (TCP)",Be="FFmpeg (UDP)",Ve="Default",Le=[Ie,Re,Oe],je=["mpegts","mp4","rtsp"];class He{prebuffers={mp4:[],mpegts:[],rtsp:[]};audioDisabled=!1;activeClients=0;needBitrateReset=!1;constructor(e,t,r){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const i="rtspServerPathKey-"+this.streamId;this.maxBitrateKey="maxBitrate-"+this.streamId,this.rtspServerPath=this.storage.getItem(i),this.rtspServerPath||(this.rtspServerPath=W().randomBytes(8).toString("hex"),this.storage.setItem(i,this.rtspServerPath))}getDetectedIdrInterval(){const e=[];if(this.prebuffers.mp4.length){let t;for(const r of this.prebuffers.mp4)"mdat"===r.type&&(t&&e.push(r.time-t),t=r.time)}else if(this.prebuffers.rtsp.length){let t;for(const r of this.prebuffers.rtsp)se(r,5)&&(t&&e.push(r.time-t),t=r.time)}if(!e.length)return;return e.reduce(((e,t)=>e+t),0)/e.length}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)||"";Le.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ie),r=-1!==e.indexOf(Re),i=-1!==e.indexOf(Oe);return{isUsingDefaultAudioConfig:!(t||r||i),aacAudio:t,compatibleAudio:r,reencodeAudio:i}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:r,compatibleAudio:i,aacAudio:s}=this.getAudioConfig();return r||i||s}getParser(e,t,r){if(!this.canUseRtspParser(t,r))return Ve;const i=e&&"scrypted"===r?.tool?Ae:Ve,s=this.storage.getItem(this.rtspParserKey);return s&&s!==Ve?s===Ae?Ae:s===ke?ke:s===Be?Be:i:i}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 r=e?.startsWith("RTSP");return{defaultMode:t,rtspMode:e?.startsWith("RTSP"),muxingMp4:!r||e?.includes("MP4")}}async getMixinSettings(){const t=[],r=this.parserSession;let i=0,s=0;const{muxingMp4:o,rtspMode:n,defaultMode:a}=this.getRebroadcastMode();for(const e of o?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunks)i+=t.byteLength}const c=Date.now()-s,d=Math.round(i/c*8),u=this.streamName?`Stream: ${this.streamName}`:"Stream";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)||Te,choices:[Te,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:[Ve,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ve});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:Ee,choices:[Ee,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(o,this.advertisedMediaStreamOptions)&&n&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(n,o,this.advertisedMediaStreamOptions),r=n&&"scrypted"===this.advertisedMediaStreamOptions?.tool?Ae:"FFmpeg";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 "${r}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||Ve,choices:[Ve,ke,Be,Ae]}),e!==Ae&&l()}else l();if(r){const e=r.inputVideoResolution?.width&&r.inputVideoResolution?.height?`${r.inputVideoResolution?.width}x${r.inputVideoResolution?.height}`:"unknown",i=this.getDetectedIdrInterval();t.push({key:"detectedResolution",group:u,title:"Detected Resolution and Bitrate",readonly:!0,value:`${e} @ ${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:(r?.inputVideoCodec?.toString()||"unknown")+"/"+(r?.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:(i||0)/1e3||"unknown"})}else t.push({title:"Status",group:u,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return n&&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(){let t;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];try{t=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===t?.audio,i=t?.audio?.codec,{isUsingDefaultAudioConfig:s,aacAudio:o,compatibleAudio:n,reencodeAudio:a}=this.getAudioConfig(),{rtspMode:c,muxingMp4:d}=this.getRebroadcastMode();let u=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===u&&(u=null);let l=!1;d&&!r&&!i&&s&&void 0===u&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),l=!0);const p=void 0===u?i?.toLowerCase():u?.toLowerCase(),m=!De.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Pe.a(`${this.mixin.name} is using the ${p} 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:",p));const h=["-bsf:a","aac_adtstoasc"],g=[];let y;this.audioDisabled=!1;const v=null===u;let P=!1;if(!l&&s&&m&&(!1===t?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",p):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),P=!0),r||l)y=["-an"],this.audioDisabled=!0;else if(a||P)y=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(o||v)y=["-acodec","copy"],y.push(...h);else if(n)y=["-acodec","copy"],y.push(...g);else{y=["-acodec","copy"];const e="aac"===p?h:g;y.push(...e)}const x=["-vcodec","copy"],M={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=M.parsers,this.console.log("rebroadcast mode:",c?"rtsp":"mpegts"),c){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,G.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){let t;for(let r=0;r<e.length;r++)se(e[r],7)&&(t=r);return void 0!==t?e.slice(t):e},sdp:new Promise((e=>t=e)),async*parse(e,r,i){const s=new ae(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:o,packet:n}of s.handleRecord())yield{chunks:[o,n],type:`${t?"rtcp":"rtp"}-${e}`,width:r,height:i}}}}({vcodec:x,acodec:s?["-acodec","copy"]:y});this.sdp=e.sdp,M.parsers.rtsp=e}else M.parsers.mpegts={container:"mpegts",outputArguments:[...(R={vcodec:x,acodec:y})?.vcodec||[],...R?.acodec||[],"-f","mpegts"],parse:(D=188,E=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],r=0;for(;;){const i=e.read();if(!i){await(0,w.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<D)continue;const s=Buffer.concat(t);E?.(s);const o=s.length%D,n=s.slice(0,s.length-o),a=s.slice(s.length-o);t=[a],r=a.length,yield{chunks:[n]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const s=r.chunks[i];let o=0;for(;o+188<s.length;){const r=s.subarray(o,o+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);o+=188}}}return e}};var R,D,E;d&&(M.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...C],async*parse(e){const t=I(e);yield*me(t)},findSyncFrame:pe}}({vcodec:x,acodec:y}));const k=await this.mixinDevice.getVideoStream(t),B="x-scrypted/x-rfc4571"===k.mimeType;let L,j;this.storage.removeItem(this.lastDetectedAudioCodecKey);let H=!1;if(c&&B){H=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await we.convertMediaObjectToJSON(k,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;L=await ve(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return O().connect(parseInt(t.port),t.hostname)}(t),r,i,M),this.sdp=L.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await we.convertMediaObjectToBuffer(k,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());j=i.mediaStreamOptions;const s=this.getParser(c,d,i.mediaStreamOptions);if(s===Ae){H=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new ne(i.url,this.console);let t=[];const s=()=>{for(const e of t)A(e)};try{e.requestTimeout=1e4,await e.options();let o=(await e.describe()).body.toString().trim();this.console.log("sdp",o);const n=Z(o);let a=0;const c={},d=!1,u=async(r,i)=>{let s=a;if(d){const e=a,{port:r,server:o}=await V();t.push(o),s=r,o.on("message",(t=>{const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(e,1),r.writeUInt16BE(t.length,2);const s={chunks:[r,t],type:i};L?.emit("rtsp",s),L?.resetActivityTimer?.()}))}await e.setup(s,r,d),c[a]=i,a+=2};if(!r){for(const e of n.msections.filter((e=>"audio"===e.type)))await u(e.control,e.codec);0===a&&this.console.warn("sdp did not contain audio track and audio was not reported as missing.")}const l=n.msections.find((e=>"video"===e.type));n.msections=n.msections.filter((e=>e===l||"audio"===e.type)),o=[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n"),this.sdp=Promise.resolve(o),await u(l.control,l.codec),e.writePlay(),L=await ve(this.console,e.client,o,i.mediaStreamOptions,M,{channelMap:c,handleRTSP:async()=>{await e.readMessage()},onLoop:()=>{e.needKeepAlive&&(e.needKeepAlive=!1,e.writeGetParameter())}});const p=L.kill.bind(L);let m=!1;if(L.kill=async()=>{try{s(),m||(m=!0,e.writeTeardown()),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500)}finally{e.client.destroy(),p()}},!L.isActive)throw new Error("parser was killed before rtsp client started");e.client.on("close",(()=>L.kill()))}catch(t){throw s(),e.client.destroy(),t}}else{s===Be?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===ke&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;i.inputArguments.unshift(...e.split(" ")),L=await q(i,M)}}H&&d&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async e=>{const t=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;e.inputArguments.unshift(...t.split(" "));const r=await async function(e,t,r,i){const s=e.slice();s.push(...r,...t,...C,"pipe:3"),s.unshift("-hide_banner"),_(i,s);const o=f().spawn(await T.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(i,o),{cp:o,generator:I(o.stdio[3])}}(e.inputArguments,y,x,this.console),i=()=>{S(r.cp),L.kill(),r.generator.throw(new Error("killed"))};if(!L.isActive)return void i();L.killed.finally(i);const{resetActivityTimer:s}=K("mp4",i,L,M.timeout);for await(const e of me(r.generator))s(),L.emit("mp4",e)})).catch((()=>{})),!r&&i&&void 0!==L.inputAudioCodec&&L.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const U=t?.video?.codec;if(U&&void 0!==L.inputVideoCodec&&L.inputVideoCodec!==U&&this.console.warn("Video codec plugin reported vs detected mismatch",U,L.inputVideoCodec),L.inputAudioCodec?De.includes(L.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",L.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",L.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,L.inputAudioCodec||"null"),"h264"!==L.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),l)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),L.kill(),this.startPrebufferSession();if(this.parserSession=L,Me.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),j?.refreshAt){let t,r=j;const i=async()=>{if(!L.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await we.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(i.toString());r=o.mediaStreamOptions,s(r)},s=e=>{const r=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),t=setTimeout(i,r)};s(r),L.killed.finally((()=>clearTimeout(t)))}L.killed.finally((()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===L&&(this.parserSession=void 0)}));for(const e of je){let t=0,r=this.prebuffers[e];L.on(e,(i=>{const s=Date.now();for(i.time=s,r.push(i);r.length&&r[0].time<s-1e4;)r.shift(),t++;t>1e5&&(r=r.slice(),this.prebuffers[e]=r,t=0)}))}return L}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t,r){this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(this.inactivityTimeout&&!r||(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:r,session:i,socketPromise:s,requestedPrebuffer:o}=e;o&&this.console.log("sending prebuffer",o),F(s,{connect:(s,n)=>{t&&(this.activeClients++,this.printActiveClients());const a=Date.now(),c=t=>{if(e.filter&&!(t=e.filter(t)))return;s(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{i.removeListener(r,c),i.removeListener("killed",d),n()};i.on(r,c),i.once("killed",d);const u=this.prebuffers[r];if("rtsp"!==r)for(const e of u)e.time<a-o||c(e);else{const e=this.parsers[r].findSyncFrame(u.filter((e=>e.time>=a-o)));for(const t of e)c(t)}return()=>{t&&(this.activeClients--,this.printActiveClients()),this.inactivityCheck(i,t),d()}}})}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,r=this.getDetectedIdrInterval();let i=e?.prebuffer;null==i&&(i=1.5*Math.max(4e3,r||4e3));const{rtspMode:s}=this.getRebroadcastMode(),o=s?"rtsp":"mpegts";let n=this.parsers[e?.container]?e?.container:o;e?.prebuffer&&"mp4"!==n&&"mp4"===e?.container&&(i+=1.5*(r||4e3));const a=t.negotiateMediaStream(e);let c,d,u,l=await this.sdp;const p=new Map;if("rtsp"===n){const e=Z(l);e.msections.length>2&&(e.msections=e.msections.filter((e=>e.codec===a.video?.codec||e.codec===a.audio?.codec)),l=e.toSdp(),u=e=>{const t=p.get(e.type);if(null==t)return;const r=e.chunks.slice(),i=Buffer.from(r[0]);return i.writeUInt8(t,1),r[0]=i,{startStream:e.startStream,chunks:r}});const t=await j();c=t.clientPromise.then((async e=>{l=z(l);const t=new ae(e,l);t.console=this.console,await t.handlePlayback();for(const e of Object.values(t.setupTracks))p.set(e.codec,e.destination);return e})),d=t.url.replace("tcp://","rtsp://")}else{const e=await j();c=e.clientPromise,d=`tcp://127.0.0.1:${e.port}`}a.sdp=l;const m=!1!==e?.refresh;this.handleRebroadcasterClient({isActiveClient:m,container:n,requestedPrebuffer:i,socketPromise:c,session:t,filter:u}),a.prebuffer=i;const{reencodeAudio:h}=this.getAudioConfig();this.audioDisabled?a.audio=null:h&&(a.audio={codec:"aac",encoder:"aac",profile:"aac_low"}),t.inputVideoResolution?.width&&t.inputVideoResolution?.height&&Object.assign(a.video,t.inputVideoResolution);const f=Date.now();let g=0;const y=this.prebuffers[n];for(const e of y)if(!(e.time<f-i))for(const t of e.chunks)g+=t.length;return{url:d,container:n,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,g).toString(),...this.parsers[n].inputArguments||[],"-f",this.parsers[n].container,"-i",d],mediaStreamOptions:a}}}class Ue extends le{released=!1;sessions=new Map;streamSettings=_e(this);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();let t,r,i=e?.id;const s=await this.mixinDevice.getVideoStreamOptions();let o;const n=2e6;if(!i){switch(e?.destination){case"medium-resolution":case"remote":o=this.streamSettings.getRemoteStream(s),r=this.plugin.storageSettings.values.remoteStreamingBitrate;break;case"low-resolution":o=this.streamSettings.getLowResolutionStream(s),r=512e3;break;case"local-recorder":o=this.streamSettings.getRecordingStream(s),r=n;break;case"remote-recorder":o=this.streamSettings.getRemoteRecordingStream(s),r=n;break;default:o=this.streamSettings.getDefaultStream(s),r=n}i=o.stream.id,this.console.log("Selected stream",o.stream.name),this.streamSettings.storageSettings.values.transcodeStreams?.includes(o.title)&&(t=this.plugin.storageSettings.values.h264EncoderArguments?.split(" "))}const a=this.sessions.get(i);if(!a)return this.mixinDevice.getVideoStream(e);const c=await a.getVideoStream(e);return c.h264EncoderArguments=t,c.destinationVideoBitrate=r,this.streamSettings.storageSettings.values.missingCodecParameters&&(c.h264FilterArguments=c.h264FilterArguments||[],c.h264FilterArguments.push("-bsf:v","dump_extra")),c.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" "),we.createFFmpegMediaObject(c,{sourceId:this.id})}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),r=this.getPrebufferedStreams(t),i=r?r.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"),Pe.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);let n=0;for(const e of s){let r=this.sessions.get(e);if(!r){const s=t?.find((t=>t.id===e));s?.prebuffer&&Pe.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const a=s?.name,c=i.includes(e);if(r=new He(this,s,o||!c),this.sessions.set(e,r),o){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)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{const t=await r.parserSessionPromise;n++,e=!0,this.online=!!n,await t.killed,this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&n--,e=!1,this.online=!!n}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.streamSettings.storageSettings.values.defaultStream;let r=this.sessions.get(t?.find((t=>t.name===e))?.id);r||(r=this.sessions.get(t?.find((e=>e.id===i[0]))?.id)),r||(r=this.sessions.get(t?.find((e=>e.id===s?.[0]))?.id)),r?(this.sessions.set(void 0,r),this.console.log("Default Stream:",r.advertisedMediaStreamOptions.id,r.advertisedMediaStreamOptions.name)):this.console.warn("Unable to find Default Stream?")}Me.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];e.push(...await this.streamSettings.storageSettings.getSettings());for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){this.console.error("error in prebuffer session getMixinSettings",e)}return e}async putMixinSetting(e,t){if(this.streamSettings.storageSettings.settings[e]?await this.streamSettings.storageSettings.putSetting(e,t):this.storage.setItem(e,t?.toString()),"Transcoding"===this.streamSettings.storageSettings.settings[e]?.group)return;const r=this.sessions;this.sessions=new Map;for(const e of r.values())e?.parserSessionPromise?.then((e=>e.kill()));this.ensurePrebufferSessions()}getPrebufferedStreams(e){return be(this.streamSettings.storageSettings,e)}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getPrebufferedStreams(e);for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=1e4);return e}setVideoStreamOptions(e){const t=this.sessions.get(e.id);if(t&&e?.video?.bitrate){t.needBitrateReset=!0;const r=t.maxBitrate;r&&e?.video?.bitrate>r&&(this.console.log("clamping max bitrate request",e.video.bitrate,r),e.video.bitrate=r)}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 Ne=new class extends n{storageSettings=new de(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"},remoteStreamingBitrate:{title:"Remote Streaming Bitrate",type:"number",defaultValue:1e6,description:"The bitrate to use when remote streaming. This setting will only be used when transcoding or adaptive bitrate is enabled on a camera."},h264EncoderArguments:{title:"H264 Encoder Arguments",description:"FFmpeg arguments used to encode h264 video. This is not camera specific and is used to setup the hardware accelerated encoder on your Scrypted server. This setting will only be used when transcoding is enabled on a camera.",choices:Object.keys(m()),defaultValue:m()["libx264 (Software)"].join(" "),combobox:!0,mapPut:(e,t)=>m()[t]?.join(" ")||t||m()["libx264 (Software)"]?.join(" ")}});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(xe.getSystemState())){const t=xe.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout((()=>Me.requestRestart()),r),this.startRtspServer()}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){A(this.rtspServer),this.rtspServer=new(O().Server)((async e=>{let t;const r=new ae(e,void 0,void 0,(async(e,i,s,o)=>{r.checkRequest=void 0;const n=new URL(i);for(const e of this.currentMixins.keys()){const i=this.currentMixins.get(e);for(const e of i.sessions.values())if(n.pathname.endsWith(e.rtspServerPath))return r.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,r.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),r.console=this.console;try{await r.handlePlayback();const i=await t.parserSessionPromise,s=t.getDetectedIdrInterval(),o=1.5*Math.max(4e3,s||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:o}),await r.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,r){const i=JSON.parse(e.toString()),{url:s,sdp:o}=i,n=Z(o),a=new Map;for(const e of n.msections)for(const t of e.payloadTypes)a.set(t,e.control);const c=new URL(s);if(!c.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:d,url:u}=await j(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new ae(e,o);await t.handlePlayback();const r=O().connect(parseInt(c.port),c.hostname);for(e.on("close",(()=>{r.destroy()})),r.on("close",(()=>{e.destroy()}));;){const i=(await P(r,2)).readInt16BE(0),s=await P(r,i),o=127&s[1],n=a.get(o);if(!n)throw e.destroy(),r.destroy(),new Error("unknown payload type "+o);t.sendTrack(n,s,!1)}})),Buffer.from(JSON.stringify(l))}async canMixin(t,r){if(!r.includes(e.ScryptedInterface.VideoCamera))return null;const i=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Ue(this,{mixinDevice:e,mixinDeviceState:r,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Stream Management",groupKey:"prebuffer"});return this.currentMixins.set(r.id,i),i}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var o in i)s[o]=i[o];i.__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
package/src/main.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
|
|
3
|
+
import { getH264EncoderArgs, LIBX264_ENCODER_TITLE } from '@scrypted/common/src/ffmpeg-hardware-acceleration';
|
|
3
4
|
import { startFFMPegFragmentedMP4Session } from '@scrypted/common/src/ffmpeg-mp4-parser-session';
|
|
4
5
|
import { handleRebroadcasterClient, ParserOptions, ParserSession, setupActivityTimer, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
|
|
5
6
|
import { closeQuiet, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
|
6
|
-
import {
|
|
7
|
+
import { safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
|
|
7
8
|
import { readLength } from '@scrypted/common/src/read-stream';
|
|
8
|
-
import { createRtspParser,
|
|
9
|
+
import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, RtspClient, RtspServer, RTSP_FRAME_MAGIC } from '@scrypted/common/src/rtsp-server';
|
|
9
10
|
import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
|
10
11
|
import { StorageSettings } from '@scrypted/common/src/settings';
|
|
11
12
|
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
|
@@ -18,7 +19,6 @@ import net from 'net';
|
|
|
18
19
|
import { Duplex } from 'stream';
|
|
19
20
|
import { connectRFC4571Parser, RtspChannelCodecMapping, startRFC4571Parser } from './rfc4571';
|
|
20
21
|
import { createStreamSettings, getPrebufferedStreams } from './stream-settings';
|
|
21
|
-
import { getH264EncoderArgs, LIBX264_ENCODER_TITLE } from '@scrypted/common/src/ffmpeg-hardware-acceleration';
|
|
22
22
|
|
|
23
23
|
const { mediaManager, log, systemManager, deviceManager } = sdk;
|
|
24
24
|
|
|
@@ -44,9 +44,8 @@ const VALID_AUDIO_CONFIGS = [
|
|
|
44
44
|
TRANSCODE_AUDIO,
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
-
interface PrebufferStreamChunk {
|
|
48
|
-
|
|
49
|
-
time: number;
|
|
47
|
+
interface PrebufferStreamChunk extends StreamChunk {
|
|
48
|
+
time?: number;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
interface Prebuffers {
|
|
@@ -70,7 +69,6 @@ class PrebufferSession {
|
|
|
70
69
|
parsers: { [container: string]: StreamParser };
|
|
71
70
|
sdp: Promise<string>;
|
|
72
71
|
|
|
73
|
-
detectedIdrInterval: number;
|
|
74
72
|
audioDisabled = false;
|
|
75
73
|
|
|
76
74
|
mixinDevice: VideoCamera & VideoCameraConfiguration;
|
|
@@ -107,6 +105,38 @@ class PrebufferSession {
|
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
|
|
108
|
+
getDetectedIdrInterval() {
|
|
109
|
+
const durations: number[] = [];
|
|
110
|
+
if (this.prebuffers.mp4.length) {
|
|
111
|
+
let last: number;
|
|
112
|
+
|
|
113
|
+
for (const chunk of this.prebuffers.mp4) {
|
|
114
|
+
if (chunk.type === 'mdat') {
|
|
115
|
+
if (last)
|
|
116
|
+
durations.push(chunk.time - last);
|
|
117
|
+
last = chunk.time;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (this.prebuffers.rtsp.length) {
|
|
122
|
+
let last: number;
|
|
123
|
+
|
|
124
|
+
for (const chunk of this.prebuffers.rtsp) {
|
|
125
|
+
if (findH264NaluType(chunk, H264_NAL_TYPE_IDR)) {
|
|
126
|
+
if (last)
|
|
127
|
+
durations.push(chunk.time - last);
|
|
128
|
+
last = chunk.time;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!durations.length)
|
|
134
|
+
return;
|
|
135
|
+
|
|
136
|
+
const total = durations.reduce((prev, current) => prev + current, 0);
|
|
137
|
+
return total / durations.length;
|
|
138
|
+
}
|
|
139
|
+
|
|
110
140
|
get maxBitrate() {
|
|
111
141
|
let ret = parseInt(this.storage.getItem(this.maxBitrateKey));
|
|
112
142
|
if (!ret) {
|
|
@@ -229,7 +259,7 @@ class PrebufferSession {
|
|
|
229
259
|
const { muxingMp4, rtspMode, defaultMode } = this.getRebroadcastMode();
|
|
230
260
|
for (const prebuffer of (muxingMp4 ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
|
|
231
261
|
start = start || prebuffer.time;
|
|
232
|
-
for (const chunk of prebuffer.
|
|
262
|
+
for (const chunk of prebuffer.chunks) {
|
|
233
263
|
total += chunk.byteLength;
|
|
234
264
|
}
|
|
235
265
|
}
|
|
@@ -327,6 +357,7 @@ class PrebufferSession {
|
|
|
327
357
|
? `${session.inputVideoResolution?.width}x${session.inputVideoResolution?.height}`
|
|
328
358
|
: 'unknown';
|
|
329
359
|
|
|
360
|
+
const idrInterval = this.getDetectedIdrInterval();
|
|
330
361
|
settings.push(
|
|
331
362
|
{
|
|
332
363
|
key: 'detectedResolution',
|
|
@@ -350,7 +381,7 @@ class PrebufferSession {
|
|
|
350
381
|
title: 'Detected Keyframe Interval',
|
|
351
382
|
description: "Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",
|
|
352
383
|
readonly: true,
|
|
353
|
-
value: (
|
|
384
|
+
value: (idrInterval || 0) / 1000 || 'unknown',
|
|
354
385
|
},
|
|
355
386
|
);
|
|
356
387
|
}
|
|
@@ -677,26 +708,41 @@ class PrebufferSession {
|
|
|
677
708
|
|
|
678
709
|
this.sdp = Promise.resolve(sdp);
|
|
679
710
|
await doSetup(videoSection.control, videoSection.codec);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
session = await startRFC4571Parser(this.console, rtspClient.
|
|
711
|
+
rtspClient.writePlay();
|
|
712
|
+
|
|
713
|
+
session = await startRFC4571Parser(this.console, rtspClient.client, sdp, ffmpegInput.mediaStreamOptions, rbo, {
|
|
714
|
+
channelMap: mapping,
|
|
715
|
+
handleRTSP: async () => {
|
|
716
|
+
await rtspClient.readMessage();
|
|
717
|
+
},
|
|
718
|
+
onLoop: () => {
|
|
719
|
+
if (rtspClient.needKeepAlive) {
|
|
720
|
+
rtspClient.needKeepAlive = false;
|
|
721
|
+
rtspClient.writeGetParameter();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
});
|
|
683
725
|
const sessionKill = session.kill.bind(session);
|
|
684
726
|
let issuedTeardown = false;
|
|
685
727
|
session.kill = async () => {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
issuedTeardown
|
|
690
|
-
|
|
728
|
+
try {
|
|
729
|
+
cleanupServers();
|
|
730
|
+
// issue a teardown to upstream to close gracefully but don't rely on it responding.
|
|
731
|
+
if (!issuedTeardown) {
|
|
732
|
+
issuedTeardown = true;
|
|
733
|
+
rtspClient.writeTeardown();
|
|
734
|
+
}
|
|
735
|
+
await sleep(500);
|
|
736
|
+
}
|
|
737
|
+
finally {
|
|
738
|
+
rtspClient.client.destroy();
|
|
739
|
+
sessionKill();
|
|
691
740
|
}
|
|
692
|
-
await sleep(500);
|
|
693
|
-
rtspClient.client.destroy();
|
|
694
|
-
sessionKill();
|
|
695
741
|
}
|
|
696
742
|
if (!session.isActive)
|
|
697
743
|
throw new Error('parser was killed before rtsp client started');
|
|
698
744
|
|
|
699
|
-
rtspClient.
|
|
745
|
+
rtspClient.client.on('close', () => session.kill());
|
|
700
746
|
}
|
|
701
747
|
catch (e) {
|
|
702
748
|
cleanupServers();
|
|
@@ -827,49 +873,22 @@ class PrebufferSession {
|
|
|
827
873
|
|
|
828
874
|
for (const container of PrebufferParserValues) {
|
|
829
875
|
let shifts = 0;
|
|
876
|
+
let prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
|
|
830
877
|
|
|
831
|
-
|
|
832
|
-
this.detectedIdrInterval = undefined;
|
|
833
|
-
session.on(container, (chunk: StreamChunk) => {
|
|
834
|
-
const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
|
|
878
|
+
session.on(container, (chunk: PrebufferStreamChunk) => {
|
|
835
879
|
const now = Date.now();
|
|
836
880
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const sendEvent = typeof this.detectedIdrInterval !== 'number';
|
|
840
|
-
this.detectedIdrInterval = now - prevIdr;
|
|
841
|
-
// only on the first idr update should we send a settings refresh.
|
|
842
|
-
if (sendEvent)
|
|
843
|
-
deviceManager.onMixinEvent(this.mixin.id, this.mixin.mixinProviderNativeId, ScryptedInterface.Settings, undefined);
|
|
844
|
-
}
|
|
845
|
-
prevIdr = now;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// this is only valid for mp4, so its no op for everything else
|
|
849
|
-
// used to detect idr interval.
|
|
850
|
-
if (chunk.type === 'mdat') {
|
|
851
|
-
updateIdr();
|
|
852
|
-
}
|
|
853
|
-
else if (chunk.type === 'h264') {
|
|
854
|
-
if (findH264NaluType(chunk, H264_NAL_TYPE_IDR)) {
|
|
855
|
-
// only update the rtsp computed idr once a minute.
|
|
856
|
-
// per packet bitscan is not great.
|
|
857
|
-
updateIdr();
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
prebufferContainer.push({
|
|
862
|
-
time: now,
|
|
863
|
-
chunk,
|
|
864
|
-
});
|
|
881
|
+
chunk.time = now;
|
|
882
|
+
prebufferContainer.push(chunk);
|
|
865
883
|
|
|
866
884
|
while (prebufferContainer.length && prebufferContainer[0].time < now - prebufferDurationMs) {
|
|
867
885
|
prebufferContainer.shift();
|
|
868
886
|
shifts++;
|
|
869
887
|
}
|
|
870
888
|
|
|
871
|
-
if (shifts >
|
|
872
|
-
|
|
889
|
+
if (shifts > 100000) {
|
|
890
|
+
prebufferContainer = prebufferContainer.slice();
|
|
891
|
+
this.prebuffers[container] = prebufferContainer;
|
|
873
892
|
shifts = 0;
|
|
874
893
|
}
|
|
875
894
|
});
|
|
@@ -960,17 +979,17 @@ class PrebufferSession {
|
|
|
960
979
|
|
|
961
980
|
const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
|
|
962
981
|
if (container !== 'rtsp') {
|
|
963
|
-
for (const
|
|
964
|
-
if (
|
|
982
|
+
for (const chunk of prebufferContainer) {
|
|
983
|
+
if (chunk.time < now - requestedPrebuffer)
|
|
965
984
|
continue;
|
|
966
985
|
|
|
967
|
-
safeWriteData(
|
|
986
|
+
safeWriteData(chunk);
|
|
968
987
|
}
|
|
969
988
|
}
|
|
970
989
|
else {
|
|
971
990
|
// for some reason this doesn't work as well as simply guessing and dumping.
|
|
972
991
|
const parser = this.parsers[container];
|
|
973
|
-
const availablePrebuffers = parser.findSyncFrame(prebufferContainer.filter(pb => pb.time >= now - requestedPrebuffer)
|
|
992
|
+
const availablePrebuffers = parser.findSyncFrame(prebufferContainer.filter(pb => pb.time >= now - requestedPrebuffer));
|
|
974
993
|
for (const prebuffer of availablePrebuffers) {
|
|
975
994
|
safeWriteData(prebuffer);
|
|
976
995
|
}
|
|
@@ -996,10 +1015,11 @@ class PrebufferSession {
|
|
|
996
1015
|
|
|
997
1016
|
const session = await this.parserSessionPromise;
|
|
998
1017
|
|
|
1018
|
+
const idrInterval = this.getDetectedIdrInterval();
|
|
999
1019
|
let requestedPrebuffer = options?.prebuffer;
|
|
1000
1020
|
if (requestedPrebuffer == null) {
|
|
1001
1021
|
// get into the general area of finding a sync frame.
|
|
1002
|
-
requestedPrebuffer = Math.max(4000, (
|
|
1022
|
+
requestedPrebuffer = Math.max(4000, (idrInterval || 4000)) * 1.5;
|
|
1003
1023
|
}
|
|
1004
1024
|
|
|
1005
1025
|
const { rtspMode } = this.getRebroadcastMode();
|
|
@@ -1010,7 +1030,7 @@ class PrebufferSession {
|
|
|
1010
1030
|
// If a mp4 prebuffer was explicitly requested, but an mp4 prebuffer is not available (rtsp mode),
|
|
1011
1031
|
// rewind a little bit earlier to gaurantee a valid full segment of that length is sent.
|
|
1012
1032
|
if (options?.prebuffer && container !== 'mp4' && options?.container === 'mp4') {
|
|
1013
|
-
requestedPrebuffer += (
|
|
1033
|
+
requestedPrebuffer += (idrInterval || 4000) * 1.5;
|
|
1014
1034
|
}
|
|
1015
1035
|
|
|
1016
1036
|
const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options);
|
|
@@ -1098,7 +1118,7 @@ class PrebufferSession {
|
|
|
1098
1118
|
for (const prebuffer of prebufferContainer) {
|
|
1099
1119
|
if (prebuffer.time < now - requestedPrebuffer)
|
|
1100
1120
|
continue;
|
|
1101
|
-
for (const chunk of prebuffer.
|
|
1121
|
+
for (const chunk of prebuffer.chunks) {
|
|
1102
1122
|
available += chunk.length;
|
|
1103
1123
|
}
|
|
1104
1124
|
}
|
|
@@ -1158,8 +1178,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1158
1178
|
|
|
1159
1179
|
const defaultLocalBitrate = 2000000;
|
|
1160
1180
|
const defaultLowResolutionBitrate = 512000;
|
|
1161
|
-
if (!
|
|
1162
|
-
switch (options
|
|
1181
|
+
if (!id) {
|
|
1182
|
+
switch (options?.destination) {
|
|
1163
1183
|
case 'medium-resolution':
|
|
1164
1184
|
case 'remote':
|
|
1165
1185
|
result = this.streamSettings.getRemoteStream(msos);
|
|
@@ -1494,7 +1514,8 @@ class RebroadcastPlugin extends AutoenableMixinProvider implements MixinProvider
|
|
|
1494
1514
|
await server.handlePlayback();
|
|
1495
1515
|
const session = await prebufferSession.parserSessionPromise;
|
|
1496
1516
|
|
|
1497
|
-
const
|
|
1517
|
+
const idrInterval = prebufferSession.getDetectedIdrInterval();
|
|
1518
|
+
const requestedPrebuffer = Math.max(4000, (idrInterval || 4000)) * 1.5;
|
|
1498
1519
|
|
|
1499
1520
|
prebufferSession.handleRebroadcasterClient({
|
|
1500
1521
|
isActiveClient: true,
|
package/src/rfc4571.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cloneDeep } from "@scrypted/common/src/clone-deep";
|
|
2
2
|
import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
|
3
|
-
import { readLength } from "@scrypted/common/src/read-stream";
|
|
3
|
+
import { read16BELengthLoop, readLength } from "@scrypted/common/src/read-stream";
|
|
4
4
|
import { findH264NaluType, H264_NAL_TYPE_SPS, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
|
|
5
5
|
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
|
6
6
|
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
|
@@ -20,7 +20,11 @@ export function connectRFC4571Parser(url: string) {
|
|
|
20
20
|
|
|
21
21
|
export type RtspChannelCodecMapping = { [key: number]: string };
|
|
22
22
|
|
|
23
|
-
export async function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: ResponseMediaStreamOptions, options?: ParserOptions<"rtsp">,
|
|
23
|
+
export async function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: ResponseMediaStreamOptions, options?: ParserOptions<"rtsp">, rtspOptions?: {
|
|
24
|
+
channelMap: RtspChannelCodecMapping,
|
|
25
|
+
handleRTSP: () => Promise<void>,
|
|
26
|
+
onLoop?: () => void,
|
|
27
|
+
}): Promise<ParserSession<"rtsp">> {
|
|
24
28
|
let isActive = true;
|
|
25
29
|
const events = new EventEmitter();
|
|
26
30
|
// need this to prevent kill from throwing due to uncaught Error during cleanup
|
|
@@ -79,71 +83,80 @@ export async function startRFC4571Parser(console: Console, socket: Readable, sdp
|
|
|
79
83
|
let startStream: Buffer;
|
|
80
84
|
|
|
81
85
|
(async () => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
prefix[0] = RTSP_FRAME_MAGIC;
|
|
104
|
-
if (pt === audioPt) {
|
|
105
|
-
prefix[1] = 0;
|
|
106
|
-
}
|
|
107
|
-
else if (pt === videoPt) {
|
|
108
|
-
prefix[1] = 2;
|
|
86
|
+
const headerLength = rtspOptions?.channelMap ? 4 : 2;
|
|
87
|
+
const offset = rtspOptions?.channelMap ? 2 : 0;
|
|
88
|
+
const skipHeader = (header: Buffer, resumeRead: () => void) => {
|
|
89
|
+
if (header.toString() !== 'RTSP')
|
|
90
|
+
return false;
|
|
91
|
+
socket.unshift(header);
|
|
92
|
+
rtspOptions.handleRTSP().then(resumeRead);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
await read16BELengthLoop(socket, {
|
|
96
|
+
headerLength,
|
|
97
|
+
offset,
|
|
98
|
+
skipHeader,
|
|
99
|
+
callback: (header, data) => {
|
|
100
|
+
let type: string;
|
|
101
|
+
|
|
102
|
+
rtspOptions?.onLoop?.();
|
|
103
|
+
|
|
104
|
+
if (rtspOptions?.channelMap) {
|
|
105
|
+
const channel = header.readUInt8(1);
|
|
106
|
+
type = rtspOptions?.channelMap[channel];
|
|
109
107
|
}
|
|
110
|
-
|
|
108
|
+
else {
|
|
109
|
+
const pt = data[1] & 0x7f;
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
const prefix = Buffer.alloc(2);
|
|
112
|
+
prefix[0] = RTSP_FRAME_MAGIC;
|
|
113
|
+
if (pt === audioPt) {
|
|
114
|
+
prefix[1] = 0;
|
|
115
|
+
}
|
|
116
|
+
else if (pt === videoPt) {
|
|
117
|
+
prefix[1] = 2;
|
|
118
|
+
}
|
|
119
|
+
header = Buffer.concat([prefix, header]);
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
if (pt === audioPt)
|
|
122
|
+
type = inputAudioCodec;
|
|
123
|
+
else if (pt === videoPt)
|
|
124
|
+
type = inputVideoCodec;
|
|
125
|
+
}
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
const chunk: StreamChunk = {
|
|
128
|
+
startStream,
|
|
129
|
+
chunks: [header, data],
|
|
130
|
+
type,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (!inputVideoResolution) {
|
|
134
|
+
const sps = findH264NaluType(chunk, H264_NAL_TYPE_SPS);
|
|
135
|
+
if (sps) {
|
|
136
|
+
try {
|
|
137
|
+
const parsedSps = spsParse(sps);
|
|
138
|
+
inputVideoResolution = getSpsResolution(parsedSps);
|
|
139
|
+
console.log(inputVideoResolution);
|
|
140
|
+
console.log('parsed bitstream sps', parsedSps);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.warn('sps parsing failed');
|
|
144
|
+
inputVideoResolution = {
|
|
145
|
+
width: NaN,
|
|
146
|
+
height: NaN,
|
|
147
|
+
}
|
|
138
148
|
}
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
|
-
}
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
events.emit('rtsp', chunk);
|
|
153
|
+
resetActivityTimer();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
146
156
|
})()
|
|
157
|
+
.catch(e => {
|
|
158
|
+
throw e;
|
|
159
|
+
})
|
|
147
160
|
.finally(kill);
|
|
148
161
|
|
|
149
162
|
|