@scrypted/prebuffer-mixin 0.1.255 → 0.1.258
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 +124 -96
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 n.default}});var i=a(r(257)),s=a(r(816)),n=a(r(243)),o=a(r(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},n=r(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password: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},n=r(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=r(761),n=(i=r(113))&&i.__esModule?i:{default:i};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const r=n.default.createHash(e);return r.update(t),r.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),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(n);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],n){if(-1===t.indexOf(r))throw new s.default("E_UNAUTHORIZED_KEY",n,r);return e[r]=i.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(r,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){return o(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 n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,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(268),t);const n=r(268);class o extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}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=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}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(n.ScryptedInterfaceProperty))Object.defineProperty(o.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},268:(e,t,r)=>{"use strict";r.r(t),r.d(t,{DeviceBase:()=>i,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,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 n={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 o,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"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(l||(l={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(p||(p={})),function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.Microphone="Microphone",e.Display="Display",e.VideoCamera="VideoCamera",e.VideoRecorder="VideoRecorder",e.VideoClips="VideoClips",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(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 n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(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)},o(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,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(i=(void 0===r?[]:[r]).concat(i),r=e,e=[]),o=this,(s=!(a=u(t).call(this,r))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=r||"E_UNEXPECTED",s.params=i,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var r,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),r=t,(o=[{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,o),a&&s(r,a),t}(o(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&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),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&r.push(e.message),i=new l(n,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 n=t[i]={exports:{}};return e[i].call(n.exports,n,n.exports,r),n.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,{RebroadcastPlugin:()=>Qe,default:()=>Ye});var e=r(510),t=r.n(e);const{systemManager:s}=t(),n="v4";class o 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]===n)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]!==n&&(this.hasEnabledMixin[e]=n,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 n(e){const i=n=>{const o=n.toString();for(const e of v)if(-1!==o.indexOf(e))return;if(!(s||r||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(o)};return i}t.stdout?.on("data",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const 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 _=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 o(),void r(s);(e.readableEnded||e.destroyed)&&i(new Error("stream ended during read"))},n=()=>{o(),i(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const 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 D=r.n(R);const O=require("dgram");var A=r.n(O);async function E(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function k(e,t,r){e.bind(t,r),await(0,_.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function B(e){return k(e,0)}async function V(){return async function(e){const t=A().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,_.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function U(){const e=new(D().Server),t=await async function(e){return e.listen(0),await(0,_.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 j(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:H}=t();async function N(e,t){return new Promise((r=>{const i=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",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 n=Date.now();function o(){n=Date.now()}return i&&(s=setInterval((()=>{Date.now()>n+i&&(clearInterval(s),s=void 0,console.error("timeout waiting for data, killing parser session",e),t())}),i)),r.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o,clearActivityTimer:function(){clearTimeout(s)}}}async function q(e,t){const{console:r}=t;let i=!0;const s=new _.EventEmitter;let n,o,a,c;s.on("error",(e=>r.error("rebroadcast error",e)));const d=new Promise((e=>{c=e}));function u(){i&&(s.emit("killed"),s.emit("error",new Error("killed"))),i=!1,c(),S(y)}const l=e.inputArguments.slice();let p=!1;const m=e=>{if(!i)throw e(),new Error("parser session was killed killed before ffmpeg connected");s.on("killed",e)},h=["pipe","pipe","pipe"];let g=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){p=!0;const r=A().createSocket("udp4"),n=await B(r),o=A().createSocket("udp4");await L(o,n.port+1),m((()=>{r.close(),o.close()})),l.push(...i.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:c}=K(e,u,s,t?.timeout);(async()=>{for await(const t of i.parseDatagram(r,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,t),c()})(),(async()=>{for await(const t of i.parseDatagram(o,parseInt(a?.[2]),parseInt(a?.[3]),"rtcp"))s.emit(e,t),c()})()}else if(i.tcpProtocol){const n=await U(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),l.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=K(e,u,s,t?.timeout);(async()=>{const t=await n.clientPromise;try{m((()=>t.destroy()));for await(const r of i.parse(t,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,r),c()}catch(e){r.error("rebroadcast parse error",e),u()}})()}else l.push(...i.outputArguments,"pipe:"+g++),h.push("pipe")}p&&(l.push("-sdp_file","pipe:"+g++),h.push("pipe")),l.unshift("-hide_banner"),w(r,l);const y=f().spawn(await H.getFFmpegPath(),l,{stdio:h});let v;b(r,y,void 0,t?.storage),y.on("exit",u),v=p?new Promise((e=>{const t=[];y.stdio[g-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let P=0;return Object.keys(t.parsers).forEach((async e=>{const i=t.parsers[e];if(!i.parse||i.tcpProtocol)return;const n=y.stdio[3+P];P++;try{const{resetActivityTimer:r}=K(e,u,s,t?.timeout);for await(const t of i.parse(n,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,t),r()}catch(e){r.error("rebroadcast parse error",e),u()}})),async function(e){return N(e,"Audio")}(y).then((e=>n=e)),await async function(e){return N(e,"Video")}(y).then((e=>o=e)),async function(e){return new Promise((t=>{const r=i=>{const s=i.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(n))};e.stdout.on("data",r),e.stderr.on("data",r)}))}(y).then((e=>a=e)),{sdp:v,get inputAudioCodec(){return n},get inputVideoCodec(){return o},get inputVideoResolution(){return{width:parseInt(a?.[1]),height:parseInt(a?.[2])}},get isActive(){return i},kill:u,killed:d,negotiateMediaStream:()=>{const t=j(e.mediaStreamOptions)||{id:void 0,name:void 0};return t.video||(t.video={}),t.video.codec=o,t.audio=t?.audio?.codec===n?e?.mediaStreamOptions?.audio:{codec:n},t},emit(e,t){return s.emit(e,t),this},on(e,t){return s.on(e,t),this},once(e,t){return s.once(e,t),this},removeListener(e,t){return s.removeListener(e,t),this}}}async function $(e,t){const r=await e;let i=!0;const s=()=>{r.removeAllListeners(),r.destroy();const e=n;n=void 0,e?.()};let n=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 F=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 n={};return i.split(";").map((e=>e.trim())).forEach((e=>{const[t,...r]=e.split("=");n[t]=r.join("=")})),{payloadType:s,parameters:n}})).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{...(n=e[0],{type:n.split(" ")[0].substring(2),payloadTypes:J(n)}),fmtp:Q(e),lines:e,contents:e.join("\r\n"),control:t,codec:i,direction:s};var n}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 n={header:{lines:r,contents:r.join("\r\n")},msections:i.map(X),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}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 ne(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}function oe(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class ae 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 F.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=D().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+=(s.endsWith("/")?"":"/")+r);const n=new URL(s);n.username="",n.password="";const o=`${e} ${n} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),t["User-Agent"]="Scrypted",this.wwwAuthenticate&&(t.Authorization=this.createAuthorizationHeader(e,new URL(s))),this.session&&(t.Session=this.session),this.write(o,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)}}createAuthorizationHeader(e,t){if(!this.wwwAuthenticate)throw new Error("no WWW-Authenticate found");if(this.wwwAuthenticate.includes("Basic")){return`Basic ${re.rh.computeHash(t)}`}const r=re.Nu.parseWWWAuthenticateRest(this.wwwAuthenticate),i=new URL(this.url),s=decodeURIComponent(i.username),n=decodeURIComponent(i.password),o=new URL(t);o.username="",o.password="";const a=W().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=W().createHash("md5").update(`${e}:${o}`).digest("hex"),d=W().createHash("md5").update(`${a}:${r.nonce}:${c}`).digest("hex"),u={username:s,realm:r.realm,nonce:r.nonce,uri:o.toString(),algorithm:"MD5",response:d};return`Digest ${Object.entries(u).map((([e,t])=>{return`${e}=${t&&(r=t,`"${r.replace(/"/g,'\\"')}"`)}`;var r})).join(", ")}`}async request(e,t,r,i,s){this.writeRequest(e,t,r,i);const n=this.requestTimeout?await(o=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new ie)),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=ne(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=d["www-authenticate"];if(u){if(s)throw new Error("auth failed");return this.wwwAuthenticate=u,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);let n;if(s.headers.session){const e=oe(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]}if(s.headers.transport){const e=s.headers.transport.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const[t,r,i]=e;r&&i&&(n={begin:parseInt(r),end:parseInt(i)})}}return Object.assign({interleaved:n},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 ce{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,n=Object.values(this.setupTracks).find((e=>e.destination===s));if(!n)throw new Error("RSTP Server received unknown channel: "+i);yield{type:n.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,n,o]=e;this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(n),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=ne(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 n=`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))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&(this.client.write(s),this.console?.log("response body",s.toString()))}}const{systemManager:de}=t();class ue{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 de.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:le}=t();class pe extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>le.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=le.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),le.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await le.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}async function me(e){await new Promise((t=>setTimeout(t,e)))}function he(e){return e}async function*fe(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 ge{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,n=-1,o=e.getUint8(i)<<s;do{r=128&o,o<<=1,n++,s++,8===s&&(s=0,i++,o=e.getUint8(i))}while(!r);return{zeros:n,skip:s,byt:o,byteoffset:i}}(e,this.bitoffset),n=1;for(;t>0;)n=n<<1|(128&i)>>>7,i<<=1,r++,t--,8===r&&(r=0,s++,i=e.getUint8(s));return this.bitoffset=s<<3|r,n-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 ye(e,t){let r=8,i=8;const s=[];for(let n=0;n<t;n++){if(0!==i){i=(r+e.SignedExpGolomb()+256)%256}i&&(r=i),s.push(i)}return s}function ve(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new ge(new DataView(e.buffer,e.byteOffset+4)),r=e[1],i=e[2],s=e[3],n=t.ExpGolomb();let o=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){o=t.ExpGolomb();let e=8;if(3===o&&(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(ye(t,16));for(;r<e;r++)t.readBit()&&p.push(ye(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(),w=t.readBit(),_=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(),D=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:n,profile_compatibility:i,profile_idc:r,level_idc:s,chroma_format_idc:o,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:w,pic_width_in_mbs:_,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:D}}function Se(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,n=(2-e.frame_mbs_only_flag)*s,o=0,a=0,c=0,d=0;return e.frame_cropping_flag&&(o=e.frame_cropping.left,a=e.frame_cropping.right,c=e.frame_cropping.top,d=e.frame_cropping.bottom),{width:16*i-t*(o+a),height:16*n-r*(2-e.frame_mbs_only_flag)*(c+d)}}Buffer.from("RTSP");function be(e){const t=e.rfc4571.read();t&&e.client.unshift(t)}function we(e,t,r,i,s,n){let o=!0;const a=new F.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=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!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"],w=b?.split(",")?.[0];if(w)try{const t=ve(Buffer.from(w,"base64"));S=Se(t),e.log("parsed sdp sps",t)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{if(await me(0),n?.udpSessionTimeout)for(;;)await me(1e3*n.udpSessionTimeout-5e3),await n.rtspClient.getParameter();const r=n?.channelMap?4:2,i=n?.channelMap?2:0;await async function(e,t){let r;const{skipHeader:i,callback:s}=t,n=t.offset||0,o=t.headerLength||2;let a,c;e.on("error",(e=>r=e));let d=0,u=0;const l=()=>{u++,p()},p=()=>{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(o),!a)return;if(i?.(a,l)){d++,a=void 0;continue}c=a.readUInt16BE(n)}}};throw p(),e.on("readable",p),await(0,_.once)(e,"end"),new Error("stream ended")}(t,{headerLength:r,offset:i,skipHeader:(r,i)=>!!n?.rtspClient.needKeepAlive&&(t.unshift(r),n.rtspClient.needKeepAlive=!1,n.rtspClient.getParameter().then((()=>{be(n.rtspClient),i()})).catch((t=>{e.error("error during RTSP keepalive",t),y()})),!0),callback:(t,r)=>{let i;if(n?.channelMap){const e=t.readUInt8(1);if(i=n?.channelMap[e],!i){const t=n?.channelMap[e-1];t&&(i=`rtcp-${t}`)}}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={chunks:[t,r],type:i};if(!S){const t=se(s,7);if(t)try{const r=ve(t);S=Se(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 o},kill(){y()},killed:g,resetActivityTimer:v,negotiateMediaStream:e=>{const t=j(i)||{id:void 0,name:void 0};if(void 0===t.video&&(t.video={}),null===e?.video&&(t.video=null),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}}}const{deviceManager:_e}=t(),Pe="transcode",xe="mixin:@scrypted/prebuffer-mixin";function Me(){if(!_e.getNativeIds().includes(Pe))return;return _e.getDeviceState(Pe)?.id}class Te extends e.ScryptedDeviceBase{constructor(e){super(Pe),this.plugin=e}getSettings(){return this.plugin.transcodeStorageSettings.getSettings()}putSetting(e,t){return this.plugin.transcodeStorageSettings.putSetting(e,t)}async canMixin(t,r){if(r.includes(xe))return[e.ScryptedInterface.Settings]}invalidateSettings(t){process.nextTick((()=>this.plugin.currentMixins.get(t)?.onDeviceEvent(e.ScryptedInterface.Settings,void 0)))}async getMixin(e,t,r){return this.invalidateSettings(r.id),e}async releaseMixin(e,t){this.invalidateSettings(e)}}function Ie(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}function Ce(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Ie(t)}function Re(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 ue(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)),hide:!0},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",hide:!0},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,hide:!0}});function i(e,t){const i=Ce(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 n=r.settings[e],o=r.values[e];let a="Default"===o,c=s?.find((e=>e.name===o));return!a&&c||(a=!0,c=i(n,s)),{title:t[e].title,isDefault:a,stream:c}}function n(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;const i=e.mixins?.includes(Me())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Ie(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream:n(t.defaultStream,i),remoteStream:n(t.remoteStream,i),lowResolutionStream:n(t.lowResolutionStream,i),recordingStream:n(t.recordingStream,i),remoteRecordingStream:n(t.remoteRecordingStream,i),...s}:{enabledStreams:r,...s}}catch(t){e.console.error("error retrieving getVideoStreamOptions",t)}return{...s}}},{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:De,log:Oe,systemManager:Ae,deviceManager:Ee}=t(),ke="Default",Be="AAC or No Audio",Ve=`${Be} (Copy)`,Le="Compatible Audio",Ue="Other Audio",je=["aac","mp3","mp2","opus"],He="-fflags +genpts",Ne="Scrypted (TCP)",Ke="Scrypted (UDP)",qe="FFmpeg (TCP)",$e="FFmpeg (UDP)",Fe="Default",Ge=[Be,Le,Ue],We=["mpegts","mp4","rtsp"];class ze{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)||"";Ge.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Be),r=-1!==e.indexOf(Le),i=-1!==e.indexOf(Ue);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 Fe;const i=e?Ne:Fe,s=this.storage.getItem(this.rtspParserKey);return s&&s!==Fe?s===Ne?Ne:s===Ke?Ke:s===qe?qe:s===$e?$e:i:i}getRebroadcastContainer(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default",t="RTSP";"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:n,rtspMode:o,defaultMode:a}=this.getRebroadcastContainer();for(const e of n?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:"Rebroadcast Container",group:u,description:`The container format to use when rebroadcasting. The default mode for this camera is ${a}.`,placeholder:"RTSP",choices:[Fe,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Fe});const l=()=>{t.push({title:"Audio Codec Transcoding",group:u,description:"Configuring your camera to output Opus, PCM, or AAC is recommended.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||ke,choices:[ke,Ve,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:u,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:He,choices:[He,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(!1,this.advertisedMediaStreamOptions)&&o&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(o,!1,this.advertisedMediaStreamOptions),r=o?Ne:qe;t.push({key:this.rtspParserKey,group:u,title:"RTSP Parser",description:`The RTSP Parser used to read the stream. The default is "${r}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||Fe,choices:[Fe,Ne,Ke,qe,$e]}),e!==Ne&&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 Opus, PCM, or AAC 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 o&&t.push({group:u,key:"rtspRebroadcastUrl",title:"RTSP Rebroadcast Url",description:"The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.",readonly:!0,value:`rtsp://localhost:${this.mixin.plugin.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`}),this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&t.push({group:u,key:this.maxBitrateKey,title:"Max Bitrate",description:"This camera supports Adaptive Bitrate. Set the maximum bitrate to be allowed while using adaptive bitrate streaming. This will also serve as the default bitrate.",type:"number",value:this.maxBitrate?.toString()}),t}async startPrebufferSession(){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:n,compatibleAudio:o,reencodeAudio:a}=this.getAudioConfig(),{rtspMode:c,muxingMp4:d}=this.getRebroadcastContainer();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=!je.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Oe.a(`${this.mixin.name} is using the ${p} audio codec. Configuring your Camera to use Opus, PCM, or AAC 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 Opus, PCM, or AAC 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(n||v)y=["-acodec","copy"],y.push(...h);else if(o)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);if(void 0!==t)return e.slice(t);for(let r=0;r<e.length;r++)se(e[r],5)&&(t=r);return void 0!==t?e.slice(t):void 0},sdp:new Promise((e=>t=e)),async*parse(e,r,i){const s=new ce(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp-":""}${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:(O=188,A=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,_.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<O)continue;const s=Buffer.concat(t);A?.(s);const n=s.length%O,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],r=a.length,yield{chunks:[o]}}}),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 n=0;for(;n+188<s.length;){const r=s.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}};var R,O,A;d&&(M.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...C],async*parse(e){const t=I(e);yield*fe(t)},findSyncFrame:he}}({vcodec:x,acodec:y}));const k=await this.mixinDevice.getVideoStream(t),B="x-scrypted/x-rfc4571"===k.mimeType;let L,U;this.storage.removeItem(this.lastDetectedAudioCodecKey);let j=!1;if(c&&B){j=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await De.convertMediaObjectToJSON(k,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;L=we(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return D().connect(parseInt(t.port),t.hostname)}(t),r,i,M),this.sdp=L.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await De.convertMediaObjectToBuffer(k,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());U=i.mediaStreamOptions||this.advertisedMediaStreamOptions;const s=this.getParser(c,d,U);if(s===Ne||s===Ke){j=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new ae(i.url,this.console);let t=[];const n=()=>{for(const e of t)E(e)};try{e.requestTimeout=1e4,await e.options();const o=await e.describe(),a=o.headers["content-base"];if(a){const t=new URL(a),r=new URL(e.url);t.username=r.username,t.password=r.password,e.url=t.toString()}let c=o.body.toString().trim();this.console.log("sdp",c);const d=Z(c);let u=0;const l={},p=s===Ke;let m;const h=e=>{if(p&&e.session&&!m){const t=oe(e.session);m=parseInt(t.timeout)}},f=async(r,i)=>{let s,n=u;if(p){const e=u,{port:r,server:o}=await V();s=o,t.push(o),n=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?.()}))}const o=await e.setup(n,r,p);if(h(o.headers),s){const t=Buffer.alloc(1),r=o.headers.transport.match(/.*?server_port=([0-9]+)-([0-9]+)/),[n,a,c]=r,{hostname:d}=new URL(e.url);s.send(t,parseInt(a),d),l[u]=i}else o.interleaved?l[o.interleaved.begin]=i:l[u]=i;u+=2};let g=!1;d.msections=d.msections.filter((e=>{if("video"===e.type){if(g)return this.console.warn("additional video section found. skipping."),!1;g=!0}else{if("audio"!==e.type)return this.console.warn("unknown section",e.type),!1;if(r)return!1}return!0}));for(const e of d.msections)await f(e.control,e.codec);c=[...d.header.lines,...d.msections.map((e=>e.lines)).flat()].join("\r\n"),this.sdp=Promise.resolve(c);const y=await e.play();h(y.headers),be(e),L=we(this.console,e.client,c,i.mediaStreamOptions,M,{channelMap:l,rtspClient:e,udpSessionTimeout:m});const v=L.kill.bind(L);let S=!1;if(L.kill=async()=>{try{n(),S||(S=!0,e.writeTeardown()),await me(500)}finally{e.client.destroy(),v()}},!L.isActive)throw new Error("parser was killed before rtsp client started");e.client.on("close",(()=>L.kill()))}catch(t){throw n(),e.client.destroy(),t}}else{s===$e?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===qe&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||He;i.inputArguments.unshift(...e.split(" ")),L=await q(i,M)}}j&&d&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async e=>{const t=this.storage.getItem(this.ffmpegInputArgumentsKey)||He;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"),w(i,s);const n=f().spawn(await T.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(i,n),{cp:n,generator:I(n.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 fe(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 H=t?.video?.codec;if(H&&void 0!==L.inputVideoCodec&&L.inputVideoCodec!==H&&this.console.warn("Video codec plugin reported vs detected mismatch",H,L.inputVideoCodec),L.inputAudioCodec?je.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,Ee.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),U?.refreshAt){let t,r=U;const i=async()=>{if(!L.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await De.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());r=n.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 We){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:n}=e;n&&this.console.log("sending prebuffer",n),$(s,{connect:(s,o)=>{t&&(this.activeClients++,this.printActiveClients());const a=Date.now(),c=(t,r)=>{if(e.filter&&!(t=e.filter(t,r)))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),o()};i.on(r,c),i.once("killed",d);const u=this.prebuffers[r];if("rtsp"!==r)for(const e of u)e.time<a-n||c(e,!0);else{const e=this.parsers[r],t=u.filter((e=>e.time>=a-n));let i=e.findSyncFrame(t);i||(this.console.warn("Unable to find sync frame in rtsp prebuffer."),i=t);for(const e of i)c(e,!0)}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=Math.max(4e3,this.getDetectedIdrInterval()||4e3);let i=e?.prebuffer;null==i&&(i=1.5*r);const{rtspMode:s}=this.getRebroadcastContainer(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;i&&"mp4"!==o&&"mp4"===e?.container&&(i+=1.5*r);const a=t.negotiateMediaStream(e);let c,d,u,l=await this.sdp;const p=new Map;if("rtsp"===o){const t=Z(l);t.msections=t.msections.filter((e=>e.codec===a.video?.codec||e.codec===a.audio?.codec));const r=void 0===e?.prebuffer,i=t.msections.find((e=>"video"===e.type))?.codec;l=t.toSdp(),u=(e,t)=>{const s=p.get(e.type);if(null==s)return;if(t&&r&&e.type!==i)return;const n=e.chunks.slice(),o=Buffer.from(n[0]);return o.writeUInt8(s,1),n[0]=o,{startStream:e.startStream,chunks:n}};const s=await U();c=s.clientPromise.then((async e=>{l=z(l);const t=new ce(e,l);await t.handlePlayback();for(const e of Object.values(t.setupTracks))p.set(e.codec,e.destination),p.set(`rtcp-${e.codec}`,e.destination+1);return e})),d=s.url.replace("tcp://","rtsp://")}else{const e=await U();c=e.clientPromise,d=`tcp://127.0.0.1:${e.port}`}a.sdp=l;const m=!1!==e?.refresh;this.handleRebroadcasterClient({isActiveClient:m,container:o,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&&a.video&&Object.assign(a.video,t.inputVideoResolution);const f=Date.now();let g=0;const y=this.prebuffers[o];for(const e of y)if(!(e.time<f-i))for(const t of e.chunks)g+=t.length;return{url:d,container:o,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,g).toString(),...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",d],mediaStreamOptions:a}}}class Je extends pe{released=!1;sessions=new Map;streamSettings=Re(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=this.mixins?.includes(Me()),n=await this.mixinDevice.getVideoStreamOptions();let o;const a=2e6;if(!i){switch(e?.destination){case"medium-resolution":case"remote":o=this.streamSettings.getRemoteStream(n),r=this.plugin.transcodeStorageSettings.values.remoteStreamingBitrate;break;case"low-resolution":o=this.streamSettings.getLowResolutionStream(n),r=512e3;break;case"local-recorder":o=this.streamSettings.getRecordingStream(n),r=a;break;case"remote-recorder":o=this.streamSettings.getRemoteRecordingStream(n),r=a;break;default:o=this.streamSettings.getDefaultStream(n),r=a}i=o.stream.id,this.console.log("Selected stream",o.stream.name),s&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(o.title)&&(t=this.plugin.transcodeStorageSettings.values.h264EncoderArguments?.split(" "))}const c=this.sessions.get(i);if(!c)return this.mixinDevice.getVideoStream(e);const d=await c.getVideoStream(e);return d.h264EncoderArguments=t,d.destinationVideoBitrate=r,s&&this.streamSettings.storageSettings.values.missingCodecParameters&&(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.push("-bsf:v","dump_extra")),s&&(d.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),De.createFFmpegMediaObject(d,{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"),Oe.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const n=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let o=0;for(const e of s){let r=this.sessions.get(e);if(!r){const s=t?.find((t=>t.id===e));s?.prebuffer&&Oe.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 ze(this,s,n||!c),this.sessions.set(e,r),n){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(!c){this.console.log("stream",a,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{const t=await r.parserSessionPromise;o++,e=!0,this.online=!!o,await t.killed,this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&o--,e=!1,this.online=!!o}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}if(!this.sessions.has(void 0)){const e=this.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?")}Ee.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 Ce(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()})))}}class Qe extends o{storageSettings=new ue(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});transcodeStorageSettings=new ue(this,{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(Ae.getSystemState())){const t=Ae.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((()=>Ee.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{Ee.onDeviceDiscovered({nativeId:Pe,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===Pe)return new Te(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){E(this.rtspServer),this.rtspServer=new(D().Server)((async e=>{let t;const r=new ce(e,void 0,void 0,(async(e,i,s,n)=>{r.checkRequest=void 0;const o=new URL(i);for(const e of this.currentMixins.keys()){const i=this.currentMixins.get(e);for(const e of i.sessions.values())if(o.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(),n=1.5*Math.max(4e3,s||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:n}),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:n}=i,o=Z(n),a=new Map;for(const e of o.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 U(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new ce(e,n);await t.handlePlayback();const r=D().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),n=127&s[1],o=a.get(n);if(!o)throw e.destroy(),r.destroy(),new Error("unknown payload type "+n);t.sendTrack(o,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,xe];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Je(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)}}const Ye=new Qe})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in i)s[n]=i[n];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 n.default}});var i=a(r(257)),s=a(r(816)),n=a(r(243)),o=a(r(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},n=r(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password: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},n=r(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=r(761),n=(i=r(113))&&i.__esModule?i:{default:i};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const r=n.default.createHash(e);return r.update(t),r.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),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(n);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],n){if(-1===t.indexOf(r))throw new s.default("E_UNAUTHORIZED_KEY",n,r);return e[r]=i.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(r,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){return o(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 n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,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(268),t);const n=r(268);class o extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}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=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}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(n.ScryptedInterfaceProperty))Object.defineProperty(o.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},268:(e,t,r)=>{"use strict";r.r(t),r.d(t,{DeviceBase:()=>i,ScryptedInterfaceProperty:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>l,AirQuality:()=>p,MediaPlayerState:()=>m,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>f});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.pm25Density="pm25Density",e.vocDensity="vocDensity",e.airQuality="airQuality",e.humiditySetting="humiditySetting",e.fan="fan"}(s||(s={}));const n={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","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"]},PM25Sensor:{name:"PM25Sensor",methods:[],properties:["pm25Density"]},VOCSensor:{name:"VOCSensor",methods:[],properties:["vocDensity"]},AirQualitySensor:{name:"AirQualitySensor",methods:[],properties:["airQuality"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,l,p,m,h,f;!function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.SmartDisplay="SmartDisplay",e.Speaker="Speaker",e.SmartSpeaker="SmartSpeaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(l||(l={})),function(e){e.Unknown="Unknown",e.Excellent="Excellent",e.Good="Good",e.Fair="Fair",e.Inferior="Inferior",e.Poor="Poor"}(p||(p={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(m||(m={})),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.PM25Sensor="PM25Sensor",e.VOCSensor="VOCSensor",e.AirQualitySensor="AirQualitySensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(f||(f={}))},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 n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(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)},o(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,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(i=(void 0===r?[]:[r]).concat(i),r=e,e=[]),o=this,(s=!(a=u(t).call(this,r))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=r||"E_UNEXPECTED",s.params=i,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var r,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),r=t,(o=[{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,o),a&&s(r,a),t}(o(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&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),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&r.push(e.message),i=new l(n,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 n=t[i]={exports:{}};return e[i].call(n.exports,n,n.exports,r),n.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,{RebroadcastPlugin:()=>We,default:()=>ze});var e=r(510),t=r.n(e);const{systemManager:s}=t(),n="v4";class o 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]===n)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]!==n&&(this.hasEnabledMixin[e]=n,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("net");var f=r.n(h);const g=require("child_process");var y=r.n(g);const S=require("events"),v=require("dgram");var b=r.n(v);async function w(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function _(e,t,r){e.bind(t,r),await(0,S.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function P(e){return _(e,0)}async function x(){return async function(e){const t=b().createSocket("udp4"),{port:r,url:i}=await _(t,e);return{server:t,port:r,url:i}}(0)}async function M(e,t){return e.bind(t),await(0,S.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function T(){const e=new(f().Server),t=await async function(e){return e.listen(0),await(0,S.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}}const I=require("process");var D=r.n(I);const C=["decode_slice_header error","no frame!","non-existing PPS"];function R(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:O}=t();async function E(e,t){return new Promise((r=>{const i=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),r(s.substring(0,a)))}};e.stdout.on("data",i),e.stderr.on("data",i)}))}function A(e,t,r,i){let s;let n=Date.now();function o(){n=Date.now()}return i&&(s=setInterval((()=>{Date.now()>n+i&&(clearInterval(s),s=void 0,console.error("timeout waiting for data, killing parser session",e),t())}),i)),r.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o,clearActivityTimer:function(){clearTimeout(s)}}}async function k(e,t){const{console:r}=t;let i=!0;const s=new S.EventEmitter;let n,o,a,c;s.on("error",(e=>r.error("rebroadcast error",e)));const d=new Promise((e=>{c=e}));function u(){i&&(s.emit("killed"),s.emit("error",new Error("killed"))),i=!1,c(),function(e){if(e){try{e.stdin.write("q\n")}catch(e){}setTimeout((()=>{e.kill(),setTimeout((()=>{e.kill("SIGKILL")}),2e3)}),2e3)}}(g)}const l=e.inputArguments.slice();let p=!1;const m=e=>{if(!i)throw e(),new Error("parser session was killed killed before ffmpeg connected");s.on("killed",e)},h=["pipe","pipe","pipe"];let f=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){p=!0;const r=b().createSocket("udp4"),n=await P(r),o=b().createSocket("udp4");await M(o,n.port+1),m((()=>{r.close(),o.close()})),l.push(...i.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:c}=A(e,u,s,t?.timeout);(async()=>{for await(const t of i.parseDatagram(r,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,t),c()})(),(async()=>{for await(const t of i.parseDatagram(o,parseInt(a?.[2]),parseInt(a?.[3]),"rtcp"))s.emit(e,t),c()})()}else if(i.tcpProtocol){const n=await T(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),l.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=A(e,u,s,t?.timeout);(async()=>{const t=await n.clientPromise;try{m((()=>t.destroy()));for await(const r of i.parse(t,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,r),c()}catch(e){r.error("rebroadcast parse error",e),u()}})()}else l.push(...i.outputArguments,"pipe:"+f++),h.push("pipe")}p&&(l.push("-sdp_file","pipe:"+f++),h.push("pipe")),l.unshift("-hide_banner"),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(" "))}(r,l);const g=y().spawn(await O.getFFmpegPath(),l,{stdio:h});let v;!function(e,t,r,i){const s=!!D().env.SCRYPTED_FFMPEG_NOISY||!!i?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const i=n=>{const o=n.toString();for(const e of C)if(-1!==o.indexOf(e))return;if(!(s||r||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(o)};return i}t.stdout?.on("data",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}(r,g,void 0,t?.storage),g.on("exit",u),v=p?new Promise((e=>{const t=[];g.stdio[f-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let w=0;return Object.keys(t.parsers).forEach((async e=>{const i=t.parsers[e];if(!i.parse||i.tcpProtocol)return;const n=g.stdio[3+w];w++;try{const{resetActivityTimer:r}=A(e,u,s,t?.timeout);for await(const t of i.parse(n,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(e,t),r()}catch(e){r.error("rebroadcast parse error",e),u()}})),async function(e){return E(e,"Audio")}(g).then((e=>n=e)),await async function(e){return E(e,"Video")}(g).then((e=>o=e)),async function(e){return new Promise((t=>{const r=i=>{const s=i.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(n))};e.stdout.on("data",r),e.stderr.on("data",r)}))}(g).then((e=>a=e)),{sdp:v,get inputAudioCodec(){return n},get inputVideoCodec(){return o},get inputVideoResolution(){return{width:parseInt(a?.[1]),height:parseInt(a?.[2])}},get isActive(){return i},kill:u,killed:d,negotiateMediaStream:()=>{const t=R(e.mediaStreamOptions)||{id:void 0,name:void 0};return t.video||(t.video={}),t.video.codec=o,t.audio=t?.audio?.codec===n?e?.mediaStreamOptions?.audio:{codec:n},t},emit(e,t){return s.emit(e,t),this},on(e,t){return s.on(e,t),this},once(e,t){return s.once(e,t),this},removeListener(e,t){return s.removeListener(e,t),this}}}async function B(e,t){const r=await e;let i=!0;const s=()=>{r.removeAllListeners(),r.destroy();const e=n;n=void 0,e?.()};let n=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")))}async function V(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 o(),void r(s);(e.readableEnded||e.destroyed)&&i(new Error("stream ended during read"))},n=()=>{o(),i(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const H="\n".charCodeAt(0);async function L(e){return async function(e,t){const r=[];let i=0;for(;;){const s=await V(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,H)}const U=require("stream");var j=r(113),N=r.n(j);function K(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 q(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function F(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 n={};return i.split(";").map((e=>e.trim())).forEach((e=>{const[t,...r]=e.split("=");n[t]=r.join("=")})),{payloadType:s,parameters:n}})).filter((e=>!!e))}const $="a=control:";function G(e){const t=e.find((e=>e.startsWith($)))?.substring($.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{...(n=e[0],{type:n.split(" ")[0].substring(2),payloadTypes:q(n)}),fmtp:F(e),lines:e,contents:e.join("\r\n"),control:t,codec:i,direction:s};var n}function W(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 n={header:{lines:r,contents:r.join("\r\n")},msections:i.map(G),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}const z=require("tls");var Q=r.n(z),J=r(747);class Y extends Error{constructor(){super("Operation Timed Out")}}function X(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 Z(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}function ee(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class te 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 L(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 U.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=Q().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=f().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+=(s.endsWith("/")?"":"/")+r);const n=new URL(s);n.username="",n.password="";const o=`${e} ${n} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),t["User-Agent"]="Scrypted",this.wwwAuthenticate&&(t.Authorization=this.createAuthorizationHeader(e,new URL(s))),this.session&&(t.Session=this.session),this.write(o,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 V(this.client,t);this.rfc4571.push(e),this.rfc4571.push(r)}async readDataPayload(){const e=await V(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 V(this.client,4);if("RTSP"===e.toString()){this.client.unshift(e);return await super.readMessage()}await this.handleDataPayload(e)}}createAuthorizationHeader(e,t){if(!this.wwwAuthenticate)throw new Error("no WWW-Authenticate found");if(this.wwwAuthenticate.includes("Basic")){return`Basic ${J.rh.computeHash(t)}`}const r=J.Nu.parseWWWAuthenticateRest(this.wwwAuthenticate),i=new URL(this.url),s=decodeURIComponent(i.username),n=decodeURIComponent(i.password),o=new URL(t);o.username="",o.password="";const a=N().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=N().createHash("md5").update(`${e}:${o}`).digest("hex"),d=N().createHash("md5").update(`${a}:${r.nonce}:${c}`).digest("hex"),u={username:s,realm:r.realm,nonce:r.nonce,uri:o.toString(),algorithm:"MD5",response:d};return`Digest ${Object.entries(u).map((([e,t])=>{return`${e}=${t&&(r=t,`"${r.replace(/"/g,'\\"')}"`)}`;var r})).join(", ")}`}async request(e,t,r,i,s){this.writeRequest(e,t,r,i);const n=this.requestTimeout?await(o=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new Y)),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=Z(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=d["www-authenticate"];if(u){if(s)throw new Error("auth failed");return this.wwwAuthenticate=u,this.request(e,t,r,i,!0)}const l=parseInt(d["content-length"]);return l?{headers:d,body:await V(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);let n;if(s.headers.session){const e=ee(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]}if(s.headers.transport){const e=s.headers.transport.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const[t,r,i]=e;r&&i&&(n={begin:parseInt(r),end:parseInt(i)})}}return Object.assign({interleaved:n},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 re{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,j.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await L(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 V(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 V(this.client,t),i=e.readUInt8(1),s=i-i%2,n=Object.values(this.setupTracks).find((e=>e.destination===s));if(!n)throw new Error("RSTP Server received unknown channel: "+i);yield{type:n.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=W(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,n,o]=e;this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(n),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 V(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=Z(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 n=`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))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&(this.client.write(s),this.console?.log("response body",s.toString()))}}const{systemManager:ie}=t();class se{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 ie.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:ne}=t();class oe extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>ne.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=ne.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),ne.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await ne.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}async function ae(e){await new Promise((t=>setTimeout(t,e)))}const{mediaManager:ce}=t();async function*de(e){for(;;){const t=await V(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),s=await V(e,r);yield{header:t,length:r,type:i,data:s}}}const ue=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function le(e){return e}function pe(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...ue],async*parse(e){const t=de(e);yield*async function*(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]))}(t)},findSyncFrame:le}}class me{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,n=-1,o=e.getUint8(i)<<s;do{r=128&o,o<<=1,n++,s++,8===s&&(s=0,i++,o=e.getUint8(i))}while(!r);return{zeros:n,skip:s,byt:o,byteoffset:i}}(e,this.bitoffset),n=1;for(;t>0;)n=n<<1|(128&i)>>>7,i<<=1,r++,t--,8===r&&(r=0,s++,i=e.getUint8(s));return this.bitoffset=s<<3|r,n-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 he(e,t){let r=8,i=8;const s=[];for(let n=0;n<t;n++){if(0!==i){i=(r+e.SignedExpGolomb()+256)%256}i&&(r=i),s.push(i)}return s}function fe(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new me(new DataView(e.buffer,e.byteOffset+4)),r=e[1],i=e[2],s=e[3],n=t.ExpGolomb();let o=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){o=t.ExpGolomb();let e=8;if(3===o&&(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(he(t,16));for(;r<e;r++)t.readBit()&&p.push(he(t,64))}}const m=t.ExpGolomb()+4,h=t.ExpGolomb();let f=0,g=0,y=0;const S=[];let v=0;if(0===h)v=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++)S.push(t.SignedExpGolomb())}const b=t.ExpGolomb(),w=t.readBit(),_=t.ExpGolomb()+1,P=t.ExpGolomb()+1,x=t.readBit();let M=0;x||(M=t.readBit());const T=t.readBit(),I=t.readBit(),D=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),C=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}(C,t);return{sps_id:n,profile_compatibility:i,profile_idc:r,level_idc:s,chroma_format_idc:o,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:S,log2_max_pic_order_cnt_lsb:v,max_num_ref_frames:b,gaps_in_frame_num_value_allowed_flag:w,pic_width_in_mbs:_,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:D,vui_parameters_present_flag:C,vui_parameters:R}}function ge(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,n=(2-e.frame_mbs_only_flag)*s,o=0,a=0,c=0,d=0;return e.frame_cropping_flag&&(o=e.frame_cropping.left,a=e.frame_cropping.right,c=e.frame_cropping.top,d=e.frame_cropping.bottom),{width:16*i-t*(o+a),height:16*n-r*(2-e.frame_mbs_only_flag)*(c+d)}}Buffer.from("RTSP");function ye(e){const t=e.rfc4571.read();t&&e.client.unshift(t)}function Se(e,t,r,i,s,n){let o=!0;const a=new U.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=W(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=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!1,f(),t.destroy()};t.on("close",(()=>{y()})),t.on("error",(()=>{y()}));const{resetActivityTimer:v}=A("rtsp",y,a,s?.timeout);let b;const w=u?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],_=w?.split(",")?.[0];if(_)try{const t=fe(Buffer.from(_,"base64"));b=ge(t),e.log("parsed sdp sps",t)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{if(await ae(0),n?.udpSessionTimeout)for(;;)await ae(1e3*n.udpSessionTimeout-5e3),await n.rtspClient.getParameter();const r=n?.channelMap?4:2,i=n?.channelMap?2:0;await async function(e,t){let r;const{skipHeader:i,callback:s}=t,n=t.offset||0,o=t.headerLength||2;let a,c;e.on("error",(e=>r=e));let d=0,u=0;const l=()=>{u++,p()},p=()=>{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(o),!a)return;if(i?.(a,l)){d++,a=void 0;continue}c=a.readUInt16BE(n)}}};throw p(),e.on("readable",p),await(0,S.once)(e,"end"),new Error("stream ended")}(t,{headerLength:r,offset:i,skipHeader:(r,i)=>!!n?.rtspClient.needKeepAlive&&(t.unshift(r),n.rtspClient.needKeepAlive=!1,n.rtspClient.getParameter().then((()=>{ye(n.rtspClient),i()})).catch((t=>{e.error("error during RTSP keepalive",t),y()})),!0),callback:(t,r)=>{let i;if(n?.channelMap){const e=t.readUInt8(1);if(i=n?.channelMap[e],!i){const t=n?.channelMap[e-1];t&&(i=`rtcp-${t}`)}}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={chunks:[t,r],type:i};if(!b){const t=X(s,7);if(t)try{const r=fe(t);b=ge(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",s),v()}})})().catch((e=>{throw e})).finally((()=>{y()})),{sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:m,inputVideoCodec:h,get inputVideoResolution(){return b},get isActive(){return o},kill(){y()},killed:g,resetActivityTimer:v,negotiateMediaStream:e=>{const t=R(i)||{id:void 0,name:void 0};if(void 0===t.video&&(t.video={}),null===e?.video&&(t.video=null),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}}}const{deviceManager:ve}=t(),be="transcode",we="mixin:@scrypted/prebuffer-mixin";function _e(){if(!ve.getNativeIds().includes(be))return;return ve.getDeviceState(be)?.id}class Pe extends e.ScryptedDeviceBase{constructor(e){super(be),this.plugin=e}getSettings(){return this.plugin.transcodeStorageSettings.getSettings()}putSetting(e,t){return this.plugin.transcodeStorageSettings.putSetting(e,t)}async canMixin(t,r){if(r.includes(we))return[e.ScryptedInterface.Settings]}invalidateSettings(t){process.nextTick((()=>this.plugin.currentMixins.get(t)?.onDeviceEvent(e.ScryptedInterface.Settings,void 0)))}async getMixin(e,t,r){return this.invalidateSettings(r.id),e}async releaseMixin(e,t){this.invalidateSettings(e)}}function xe(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}function Me(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):xe(t)}function Te(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 se(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)),hide:!0},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",hide:!0},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,hide:!0}});function i(e,t){const i=Me(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 n=r.settings[e],o=r.values[e];let a="Default"===o,c=s?.find((e=>e.name===o));return!a&&c||(a=!0,c=i(n,s)),{title:t[e].title,isDefault:a,stream:c}}function n(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;const i=e.mixins?.includes(_e())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:xe(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream:n(t.defaultStream,i),remoteStream:n(t.remoteStream,i),lowResolutionStream:n(t.lowResolutionStream,i),recordingStream:n(t.recordingStream,i),remoteRecordingStream:n(t.remoteRecordingStream,i),...s}:{enabledStreams:r,...s}}catch(t){e.console.error("error retrieving getVideoStreamOptions",t)}return{...s}}},{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:Ie,log:De,systemManager:Ce,deviceManager:Re}=t(),Oe="Default",Ee="AAC or No Audio",Ae=`${Ee} (Copy)`,ke="Compatible Audio",Be="Other Audio",Ve=["aac","mp3","mp2","opus"],He="-fflags +genpts",Le="Scrypted (TCP)",Ue="Scrypted (UDP)",je="FFmpeg (TCP)",Ne="FFmpeg (UDP)",Ke="Default",qe=[Ee,ke,Be],Fe=["mpegts","mp4","rtsp"];class $e{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.lastH264ProbeKey="lastH264Probe-"+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=N().randomBytes(8).toString("hex"),this.storage.setItem(i,this.rtspServerPath))}getLastH264Probe(){const e=this.storage.getItem(this.lastH264ProbeKey);if(!e)return{};try{return JSON.parse(e)}catch(e){return{}}}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)X(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)||"";qe.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ee),r=-1!==e.indexOf(ke),i=-1!==e.indexOf(Be);return{isUsingDefaultAudioConfig:!(t||r||i),aacAudio:t,compatibleAudio:r,reencodeAudio:i}}canUseRtspParser(e){return e?.container?.startsWith("rtsp")}getParser(e,t){let r;const i=this.storage.getItem(this.rtspParserKey);return this.canUseRtspParser(t)?(i===je&&(r=je),i===Ne&&(r=Ne),e&&!r&&(i&&i!==Ke||(r=Le),i===Le&&(r=Le),i===Ue&&(r=Ue)),r||(r=je)):r=Ke,{parser:r,isDefault:!i||"Default"===i}}getRebroadcastContainer(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default";"Default"===e&&(e="RTSP");const t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),muxingMp4:!t||e?.includes("MP4")}}async getMixinSettings(){const t=[],r=this.parserSession;let i=0,s=0;const{muxingMp4:n,rtspMode:o}=this.getRebroadcastContainer();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunks)i+=t.byteLength}const a=Date.now()-s,c=Math.round(i/a*8),d=this.streamName?`Stream: ${this.streamName}`:"Stream";t.push({title:"Rebroadcast Container",group:d,description:"The container format to use when rebroadcasting. The default mode for this camera is RTSP.",placeholder:"RTSP",choices:[Ke,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ke});const u=()=>{t.push({title:"Audio Codec Transcoding",group:d,description:"Configuring your camera to output Opus, PCM, or AAC is recommended.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Oe,choices:[Oe,Ae,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:d,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:He,choices:[He,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Probe()?.seiDetected?Le:je,i=e?[Le,Ue]:[];t.push({key:this.rtspParserKey,group:d,title:"RTSP Parser",description:`The RTSP Parser used to read the stream. The default is "${r}" for this container.`,value:this.storage.getItem(this.rtspParserKey)||Ke,choices:[Ke,...i,je,Ne]})}else u();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:d,title:"Detected Resolution and Bitrate",readonly:!0,value:`${e} @ ${c||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:d,title:"Detected Video/Audio Codecs",readonly:!0,value:(r?.inputVideoCodec?.toString()||"unknown")+"/"+(r?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and Opus, PCM, or AAC audio is recommended."},{key:"detectedKeyframe",group:d,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"},{key:"detectedSEI",group:d,title:"Detected H264 SEI",readonly:!0,value:`${!!this.getLastH264Probe().seiDetected}`,description:"Cameras with SEI in the H264 video stream may not function correctly with Scrypted RTSP Parsers."})}else t.push({title:"Status",group:d,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return o&&t.push({group:d,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:d,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:n,compatibleAudio:o,reencodeAudio:a}=this.getAudioConfig(),{rtspMode:c,muxingMp4:d}=this.getRebroadcastContainer();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=!Ve.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&De.a(`${this.mixin.name} is using the ${p} audio codec. Configuring your Camera to use Opus, PCM, or AAC 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 Opus, PCM, or AAC audio. Suboptimal audio codec in use:",p));const h=["-bsf:a","aac_adtstoasc"],g=[];let y;this.audioDisabled=!1;const v=null===u;let b=!1;if(d&&!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."),b=!0),r||l)y=["-an"],this.audioDisabled=!0;else if(a||b)y=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(n||v)y=["-acodec","copy"],y.push(...h);else if(o)y=["-acodec","copy"],y.push(...g);else{y=["-acodec","copy"];const e="aac"===p?h:g;y.push(...e)}const _=["-vcodec","copy"],P={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=P.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,j.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++)X(e[r],7)&&(t=r);if(void 0!==t)return e.slice(t);for(let r=0;r<e.length;r++)X(e[r],5)&&(t=r);return void 0!==t?e.slice(t):void 0},sdp:new Promise((e=>t=e)),async*parse(e,r,i){const s=new re(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp-":""}${e}`,width:r,height:i}}}}({vcodec:_,acodec:["-acodec","copy"]});this.sdp=e.sdp,P.parsers.rtsp=e}else P.parsers.mpegts={container:"mpegts",outputArguments:[...(M={vcodec:_,acodec:y})?.vcodec||[],...M?.acodec||[],"-f","mpegts"],parse:(T=188,I=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,S.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<T)continue;const s=Buffer.concat(t);I?.(s);const n=s.length%T,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],r=a.length,yield{chunks:[o]}}}),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 n=0;for(;n+188<s.length;){const r=s.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}};var M,T,I;d&&(P.parsers.mp4=pe({vcodec:_,acodec:y}));const D=await this.mixinDevice.getVideoStream(t),C="x-scrypted/x-rfc4571"===D.mimeType;let R,O;this.storage.removeItem(this.lastDetectedAudioCodecKey);let E=!1;if(c&&C){E=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Ie.convertMediaObjectToJSON(D,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;R=Se(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return f().connect(parseInt(t.port),t.hostname)}(t),r,i,P),this.sdp=R.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Ie.convertMediaObjectToBuffer(D,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());O=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,O);if(E=s===Le||s===Ue,n&&(s===Le||s===Ue)&&this.getLastH264Probe().seiDetected&&(this.console.warn("SEI packet detected was in video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted."),E=!1,s=je),E){this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new te(i.url,this.console);let t=[];const n=()=>{for(const e of t)w(e)};try{e.requestTimeout=1e4,await e.options();const o=await e.describe(),a=o.headers["content-base"];if(a){const t=new URL(a),r=new URL(e.url);t.username=r.username,t.password=r.password,e.url=t.toString()}let c=o.body.toString().trim();this.console.log("sdp",c);const d=W(c);let u=0;const l={},p=s===Ue;let m;const h=e=>{if(p&&e.session&&!m){const t=ee(e.session);m=parseInt(t.timeout)}},f=async(r,i)=>{let s,n=u;if(p){const e=u,{port:r,server:o}=await x();s=o,t.push(o),n=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};R?.emit("rtsp",s),R?.resetActivityTimer?.()}))}const o=await e.setup(n,r,p);if(h(o.headers),s){const t=Buffer.alloc(1),r=o.headers.transport.match(/.*?server_port=([0-9]+)-([0-9]+)/),[n,a,c]=r,{hostname:d}=new URL(e.url);s.send(t,parseInt(a),d),l[u]=i}else o.interleaved?l[o.interleaved.begin]=i:l[u]=i;u+=2};let g=!1;d.msections=d.msections.filter((e=>{if("video"===e.type){if(g)return this.console.warn("additional video section found. skipping."),!1;g=!0}else{if("audio"!==e.type)return this.console.warn("unknown section",e.type),!1;if(r)return!1}return!0}));for(const e of d.msections)await f(e.control,e.codec);c=[...d.header.lines,...d.msections.map((e=>e.lines)).flat()].join("\r\n"),this.sdp=Promise.resolve(c);const y=await e.play();h(y.headers),ye(e),R=Se(this.console,e.client,c,i.mediaStreamOptions,P,{channelMap:l,rtspClient:e,udpSessionTimeout:m});const S=R.kill.bind(R);let v=!1;if(R.kill=async()=>{try{n(),v||(v=!0,e.writeTeardown()),await ae(500)}finally{e.client.destroy(),S()}},!R.isActive)throw new Error("parser was killed before rtsp client started");e.client.on("close",(()=>R.kill()))}catch(t){throw n(),e.client.destroy(),t}}else{s===Ne?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===je&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||He;i.inputArguments.unshift(...e.split(" ")),R=await k(i,P)}}const A=e=>{if("h264"!==e.type)return;const t=!!X(e,6);if(t){B(),clearTimeout(V);const e={seiDetected:t};if(this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),!E)return;let{isDefault:r}=this.getParser(c,O);if(!r)return void this.console.warn("SEI packet detected while operating with Scrypted Parser. If there are issues streaming, consider using the Default parser.");this.console.warn("SEI packet detected while operating with Scrypted Parser as default. Restarting rebroadcast."),R.kill(),this.startPrebufferSession()}},B=()=>R.removeListener("rtsp",A);R.on("rtsp",A);const V=setTimeout((()=>{B();this.storage.setItem(this.lastH264ProbeKey,JSON.stringify({seiDetected:!1}))}),1e4);!r&&i&&void 0!==R.inputAudioCodec&&R.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const H=t?.video?.codec;if(H&&void 0!==R.inputVideoCodec&&R.inputVideoCodec!==H&&this.console.warn("Video codec plugin reported vs detected mismatch",H,R.inputVideoCodec),R.inputAudioCodec?Ve.includes(R.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,R.inputAudioCodec||"null"),"h264"!==R.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."),R.kill(),this.startPrebufferSession();if(this.parserSession=R,Re.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),O?.refreshAt){let t,r=O;const i=async()=>{if(!R.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await Ie.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());r=n.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),R.killed.finally((()=>clearTimeout(t)))}R.killed.finally((()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===R&&(this.parserSession=void 0)}));for(const e of Fe){let t=0,r=this.prebuffers[e];R.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 R}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:n}=e;n&&this.console.log("sending prebuffer",n),B(s,{connect:(s,o)=>{t&&(this.activeClients++,this.printActiveClients());const a=Date.now(),c=(t,r)=>{if(e.filter&&!(t=e.filter(t,r)))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),o()};i.on(r,c),i.once("killed",d);const u=this.prebuffers[r];if("rtsp"!==r)for(const e of u)e.time<a-n||c(e,!0);else{const e=this.parsers[r],t=u.filter((e=>e.time>=a-n));let i=e.findSyncFrame(t);i||(this.console.warn("Unable to find sync frame in rtsp prebuffer."),i=t);for(const e of i)c(e,!0)}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=Math.max(4e3,this.getDetectedIdrInterval()||4e3);let i=e?.prebuffer;null==i&&(i=1.5*r);const{rtspMode:s}=this.getRebroadcastContainer(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;i&&"mp4"!==o&&"mp4"===e?.container&&(i+=1.5*r);const a=t.negotiateMediaStream(e);let c,d,u,l=await this.sdp;const p=new Map;if("rtsp"===o){const t=W(l);t.msections=t.msections.filter((e=>e.codec===a.video?.codec||e.codec===a.audio?.codec));const r=void 0===e?.prebuffer,i=t.msections.find((e=>"video"===e.type))?.codec;l=t.toSdp(),u=(e,t)=>{const s=p.get(e.type);if(null==s)return;if(t&&r&&e.type!==i)return;const n=e.chunks.slice(),o=Buffer.from(n[0]);return o.writeUInt8(s,1),n[0]=o,{startStream:e.startStream,chunks:n}};const s=await T();c=s.clientPromise.then((async e=>{l=K(l);const t=new re(e,l);await t.handlePlayback();for(const e of Object.values(t.setupTracks))p.set(e.codec,e.destination),p.set(`rtcp-${e.codec}`,e.destination+1);return e})),d=s.url.replace("tcp://","rtsp://")}else{const e=await T();c=e.clientPromise,d=`tcp://127.0.0.1:${e.port}`}a.sdp=l;const m=!1!==e?.refresh;this.handleRebroadcasterClient({isActiveClient:m,container:o,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&&a.video&&Object.assign(a.video,t.inputVideoResolution);const f=Date.now();let g=0;const y=this.prebuffers[o];for(const e of y)if(!(e.time<f-i))for(const t of e.chunks)g+=t.length;return{url:d,container:o,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,g).toString(),...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",d],mediaStreamOptions:a}}}class Ge extends oe{released=!1;sessions=new Map;streamSettings=Te(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=this.mixins?.includes(_e()),n=await this.mixinDevice.getVideoStreamOptions();let o;const a=2e6;if(!i){switch(e?.destination){case"medium-resolution":case"remote":o=this.streamSettings.getRemoteStream(n),r=this.plugin.transcodeStorageSettings.values.remoteStreamingBitrate;break;case"low-resolution":o=this.streamSettings.getLowResolutionStream(n),r=512e3;break;case"local-recorder":o=this.streamSettings.getRecordingStream(n),r=a;break;case"remote-recorder":o=this.streamSettings.getRemoteRecordingStream(n),r=a;break;default:o=this.streamSettings.getDefaultStream(n),r=a}i=o.stream.id,this.console.log("Selected stream",o.stream.name),s&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(o.title)&&(t=this.plugin.transcodeStorageSettings.values.h264EncoderArguments?.split(" "))}const c=this.sessions.get(i);if(!c)return this.mixinDevice.getVideoStream(e);const d=await c.getVideoStream(e);return d.h264EncoderArguments=t,d.destinationVideoBitrate=r,s&&this.streamSettings.storageSettings.values.missingCodecParameters&&(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.push("-bsf:v","dump_extra")),s&&(d.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),Ie.createFFmpegMediaObject(d,{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"),De.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const n=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let o=0;for(const e of s){let r=this.sessions.get(e);if(!r){const s=t?.find((t=>t.id===e));s?.prebuffer&&De.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 $e(this,s,n||!c),this.sessions.set(e,r),n){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(!c){this.console.log("stream",a,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{const t=await r.parserSessionPromise;o++,e=!0,this.online=!!o,await t.killed,this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&o--,e=!1,this.online=!!o}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}if(!this.sessions.has(void 0)){const e=this.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?")}Re.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 Me(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()})))}}class We extends o{storageSettings=new se(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});transcodeStorageSettings=new se(this,{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(Ce.getSystemState())){const t=Ce.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((()=>Re.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{Re.onDeviceDiscovered({nativeId:be,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===be)return new Pe(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){w(this.rtspServer),this.rtspServer=new(f().Server)((async e=>{let t;const r=new re(e,void 0,void 0,(async(e,i,s,n)=>{r.checkRequest=void 0;const o=new URL(i);for(const e of this.currentMixins.keys()){const i=this.currentMixins.get(e);for(const e of i.sessions.values())if(o.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(),n=1.5*Math.max(4e3,s||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:n}),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:n}=i,o=W(n),a=new Map;for(const e of o.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 T(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new re(e,n);await t.handlePlayback();const r=f().connect(parseInt(c.port),c.hostname);for(e.on("close",(()=>{r.destroy()})),r.on("close",(()=>{e.destroy()}));;){const i=(await V(r,2)).readInt16BE(0),s=await V(r,i),n=127&s[1],o=a.get(n);if(!o)throw e.destroy(),r.destroy(),new Error("unknown payload type "+n);t.sendTrack(o,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,we];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Ge(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)}}const ze=new We})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in i)s[n]=i[n];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,17 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
|
|
3
3
|
import { getH264EncoderArgs, LIBX264_ENCODER_TITLE } from '@scrypted/common/src/ffmpeg-hardware-acceleration';
|
|
4
|
-
import {
|
|
5
|
-
import { handleRebroadcasterClient, ParserOptions, ParserSession, setupActivityTimer, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
|
|
4
|
+
import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
|
|
6
5
|
import { closeQuiet, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
|
7
|
-
import { safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
|
|
8
6
|
import { readLength } from '@scrypted/common/src/read-stream';
|
|
9
|
-
import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, parseSemicolonDelimited, RtspClient, RtspServer, RTSP_FRAME_MAGIC } from '@scrypted/common/src/rtsp-server';
|
|
7
|
+
import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, H264_NAL_TYPE_SEI, parseSemicolonDelimited, RtspClient, RtspServer, RTSP_FRAME_MAGIC } from '@scrypted/common/src/rtsp-server';
|
|
10
8
|
import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
|
11
9
|
import { StorageSettings } from '@scrypted/common/src/settings';
|
|
12
10
|
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
|
13
11
|
import { sleep } from '@scrypted/common/src/sleep';
|
|
14
|
-
import { createFragmentedMp4Parser, createMpegTsParser,
|
|
12
|
+
import { createFragmentedMp4Parser, createMpegTsParser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
|
15
13
|
import sdk, { BufferConverter, DeviceProvider, FFmpegInput, MediaObject, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
|
|
16
14
|
import crypto from 'crypto';
|
|
17
15
|
import dgram from 'dgram';
|
|
@@ -59,6 +57,10 @@ interface Prebuffers {
|
|
|
59
57
|
type PrebufferParsers = 'mpegts' | 'mp4' | 'rtsp';
|
|
60
58
|
const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 'rtsp'];
|
|
61
59
|
|
|
60
|
+
interface H264Probe {
|
|
61
|
+
seiDetected?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
62
64
|
class PrebufferSession {
|
|
63
65
|
|
|
64
66
|
parserSessionPromise: Promise<ParserSession<PrebufferParsers>>;
|
|
@@ -82,6 +84,7 @@ class PrebufferSession {
|
|
|
82
84
|
audioConfigurationKey: string;
|
|
83
85
|
ffmpegInputArgumentsKey: string;
|
|
84
86
|
lastDetectedAudioCodecKey: string;
|
|
87
|
+
lastH264ProbeKey: string;
|
|
85
88
|
rebroadcastModeKey: string;
|
|
86
89
|
rtspParserKey: string;
|
|
87
90
|
maxBitrateKey: string;
|
|
@@ -96,6 +99,7 @@ class PrebufferSession {
|
|
|
96
99
|
this.ffmpegInputArgumentsKey = 'ffmpegInputArguments-' + this.streamId;
|
|
97
100
|
this.rebroadcastModeKey = 'rebroadcastMode-' + this.streamId;
|
|
98
101
|
this.lastDetectedAudioCodecKey = 'lastDetectedAudioCodec-' + this.streamId;
|
|
102
|
+
this.lastH264ProbeKey = 'lastH264Probe-' + this.streamId;
|
|
99
103
|
this.rtspParserKey = 'rtspParser-' + this.streamId;
|
|
100
104
|
const rtspServerPathKey = 'rtspServerPathKey-' + this.streamId;
|
|
101
105
|
this.maxBitrateKey = 'maxBitrate-' + this.streamId;
|
|
@@ -107,6 +111,20 @@ class PrebufferSession {
|
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
getLastH264Probe(): H264Probe {
|
|
115
|
+
const str = this.storage.getItem(this.lastH264ProbeKey);
|
|
116
|
+
if (!str) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(str);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
getDetectedIdrInterval() {
|
|
111
129
|
const durations: number[] = [];
|
|
112
130
|
if (this.prebuffers.mp4.length) {
|
|
@@ -202,52 +220,52 @@ class PrebufferSession {
|
|
|
202
220
|
}
|
|
203
221
|
}
|
|
204
222
|
|
|
205
|
-
canUseRtspParser(
|
|
206
|
-
|
|
207
|
-
return false;
|
|
208
|
-
if (mediaStreamOptions?.container !== 'rtsp')
|
|
209
|
-
return false;
|
|
210
|
-
// The RTSP demuxer can only be used when not transcoding audio.
|
|
211
|
-
const { isUsingDefaultAudioConfig, compatibleAudio, aacAudio } = this.getAudioConfig();
|
|
212
|
-
const canUseRtspParser = isUsingDefaultAudioConfig || compatibleAudio || aacAudio;
|
|
213
|
-
return canUseRtspParser;
|
|
223
|
+
canUseRtspParser(mediaStreamOptions: MediaStreamOptions) {
|
|
224
|
+
return mediaStreamOptions?.container?.startsWith('rtsp');
|
|
214
225
|
}
|
|
215
226
|
|
|
216
|
-
getParser(rtspMode: boolean,
|
|
217
|
-
|
|
218
|
-
return STRING_DEFAULT;
|
|
219
|
-
|
|
220
|
-
const defaultValue = rtspMode
|
|
221
|
-
? SCRYPTED_PARSER_TCP
|
|
222
|
-
: STRING_DEFAULT;
|
|
227
|
+
getParser(rtspMode: boolean, mediaStreamOptions: MediaStreamOptions) {
|
|
228
|
+
let parser: string;
|
|
223
229
|
const rtspParser = this.storage.getItem(this.rtspParserKey);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
|
|
231
|
+
if (!this.canUseRtspParser(mediaStreamOptions)) {
|
|
232
|
+
parser = STRING_DEFAULT;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
|
|
236
|
+
if (rtspParser === FFMPEG_PARSER_TCP)
|
|
237
|
+
parser = FFMPEG_PARSER_TCP;
|
|
238
|
+
if (rtspParser === FFMPEG_PARSER_UDP)
|
|
239
|
+
parser = FFMPEG_PARSER_UDP;
|
|
240
|
+
|
|
241
|
+
// scrypted parser can only be used in rtsp mode.
|
|
242
|
+
if (rtspMode && !parser) {
|
|
243
|
+
if (!rtspParser || rtspParser === STRING_DEFAULT)
|
|
244
|
+
parser = SCRYPTED_PARSER_TCP;
|
|
245
|
+
if (rtspParser === SCRYPTED_PARSER_TCP)
|
|
246
|
+
parser = SCRYPTED_PARSER_TCP;
|
|
247
|
+
if (rtspParser === SCRYPTED_PARSER_UDP)
|
|
248
|
+
parser = SCRYPTED_PARSER_UDP;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// bad config, fall back to ffmpeg tcp parsing.
|
|
252
|
+
if (!parser)
|
|
253
|
+
parser = FFMPEG_PARSER_TCP;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
parser,
|
|
258
|
+
isDefault: !rtspParser || rtspParser === 'Default',
|
|
259
|
+
}
|
|
235
260
|
}
|
|
236
261
|
|
|
237
262
|
getRebroadcastContainer() {
|
|
238
263
|
let mode = this.storage.getItem(this.rebroadcastModeKey) || 'Default';
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
&& this.advertisedMediaStreamOptions?.container?.startsWith('rtsp')) {
|
|
242
|
-
defaultMode = 'RTSP';
|
|
243
|
-
}
|
|
244
|
-
if (mode === 'Default') {
|
|
245
|
-
mode = defaultMode;
|
|
246
|
-
}
|
|
264
|
+
if (mode === 'Default')
|
|
265
|
+
mode = 'RTSP';
|
|
247
266
|
const rtspMode = mode?.startsWith('RTSP');
|
|
248
267
|
|
|
249
268
|
return {
|
|
250
|
-
defaultMode,
|
|
251
269
|
rtspMode: mode?.startsWith('RTSP'),
|
|
252
270
|
muxingMp4: !rtspMode || mode?.includes('MP4'),
|
|
253
271
|
};
|
|
@@ -260,7 +278,7 @@ class PrebufferSession {
|
|
|
260
278
|
|
|
261
279
|
let total = 0;
|
|
262
280
|
let start = 0;
|
|
263
|
-
const { muxingMp4, rtspMode
|
|
281
|
+
const { muxingMp4, rtspMode } = this.getRebroadcastContainer();
|
|
264
282
|
for (const prebuffer of (muxingMp4 ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
|
|
265
283
|
start = start || prebuffer.time;
|
|
266
284
|
for (const chunk of prebuffer.chunks) {
|
|
@@ -276,7 +294,7 @@ class PrebufferSession {
|
|
|
276
294
|
{
|
|
277
295
|
title: 'Rebroadcast Container',
|
|
278
296
|
group,
|
|
279
|
-
description: `The container format to use when rebroadcasting. The default mode for this camera is
|
|
297
|
+
description: `The container format to use when rebroadcasting. The default mode for this camera is RTSP.`,
|
|
280
298
|
placeholder: 'RTSP',
|
|
281
299
|
choices: [
|
|
282
300
|
STRING_DEFAULT,
|
|
@@ -321,35 +339,31 @@ class PrebufferSession {
|
|
|
321
339
|
)
|
|
322
340
|
};
|
|
323
341
|
|
|
324
|
-
if (this.canUseRtspParser(
|
|
325
|
-
|
|
326
|
-
&& this.
|
|
327
|
-
|
|
328
|
-
const value = this.getParser(rtspMode, false, this.advertisedMediaStreamOptions);
|
|
329
|
-
const defaultValue = rtspMode ?
|
|
342
|
+
if (this.canUseRtspParser(this.advertisedMediaStreamOptions)) {
|
|
343
|
+
const canUseScryptedParser = rtspMode;
|
|
344
|
+
const defaultValue = canUseScryptedParser && !this.getLastH264Probe()?.seiDetected ?
|
|
330
345
|
SCRYPTED_PARSER_TCP : FFMPEG_PARSER_TCP;
|
|
331
346
|
|
|
347
|
+
const scryptedOptions = canUseScryptedParser ? [
|
|
348
|
+
SCRYPTED_PARSER_TCP,
|
|
349
|
+
SCRYPTED_PARSER_UDP,
|
|
350
|
+
] : [];
|
|
351
|
+
|
|
332
352
|
settings.push(
|
|
333
353
|
{
|
|
334
354
|
key: this.rtspParserKey,
|
|
335
355
|
group,
|
|
336
356
|
title: 'RTSP Parser',
|
|
337
|
-
description: `The RTSP Parser used to read the stream. The default is "${defaultValue}" for this
|
|
357
|
+
description: `The RTSP Parser used to read the stream. The default is "${defaultValue}" for this container.`,
|
|
338
358
|
value: this.storage.getItem(this.rtspParserKey) || STRING_DEFAULT,
|
|
339
359
|
choices: [
|
|
340
360
|
STRING_DEFAULT,
|
|
341
|
-
|
|
342
|
-
SCRYPTED_PARSER_UDP,
|
|
361
|
+
...scryptedOptions,
|
|
343
362
|
FFMPEG_PARSER_TCP,
|
|
344
363
|
FFMPEG_PARSER_UDP,
|
|
345
364
|
],
|
|
346
365
|
}
|
|
347
366
|
);
|
|
348
|
-
|
|
349
|
-
if (value !== SCRYPTED_PARSER_TCP) {
|
|
350
|
-
// ffmpeg parser is being used, so add ffmpeg input arguments option.
|
|
351
|
-
addFFmpegSettings();
|
|
352
|
-
}
|
|
353
367
|
}
|
|
354
368
|
else {
|
|
355
369
|
addFFmpegSettings();
|
|
@@ -386,6 +400,14 @@ class PrebufferSession {
|
|
|
386
400
|
readonly: true,
|
|
387
401
|
value: (idrInterval || 0) / 1000 || 'unknown',
|
|
388
402
|
},
|
|
403
|
+
{
|
|
404
|
+
key: 'detectedSEI',
|
|
405
|
+
group,
|
|
406
|
+
title: 'Detected H264 SEI',
|
|
407
|
+
readonly: true,
|
|
408
|
+
value: `${!!this.getLastH264Probe().seiDetected}`,
|
|
409
|
+
description: 'Cameras with SEI in the H264 video stream may not function correctly with Scrypted RTSP Parsers.',
|
|
410
|
+
}
|
|
389
411
|
);
|
|
390
412
|
}
|
|
391
413
|
else {
|
|
@@ -511,7 +533,7 @@ class PrebufferSession {
|
|
|
511
533
|
// enable transcoding by default. however, still allow the user to change the settings
|
|
512
534
|
// in case something changed.
|
|
513
535
|
let mustTranscode = false;
|
|
514
|
-
if (!probingAudioCodec && isUsingDefaultAudioConfig && audioIncompatible) {
|
|
536
|
+
if (muxingMp4 && !probingAudioCodec && isUsingDefaultAudioConfig && audioIncompatible) {
|
|
515
537
|
if (mso?.userConfigurable === false)
|
|
516
538
|
this.console.log('camera reports it is not user configurable. transcoding due to incompatible codec', assumedAudioCodec);
|
|
517
539
|
else
|
|
@@ -603,9 +625,7 @@ class PrebufferSession {
|
|
|
603
625
|
const parser = createRtspParser({
|
|
604
626
|
vcodec,
|
|
605
627
|
// the rtsp parser should always stream copy unless audio is soft muted.
|
|
606
|
-
acodec:
|
|
607
|
-
? ['-acodec', 'copy']
|
|
608
|
-
: acodec,
|
|
628
|
+
acodec: ['-acodec', 'copy'],
|
|
609
629
|
});
|
|
610
630
|
this.sdp = parser.sdp;
|
|
611
631
|
rbo.parsers.rtsp = parser;
|
|
@@ -643,9 +663,15 @@ class PrebufferSession {
|
|
|
643
663
|
const ffmpegInput = JSON.parse(moBuffer.toString()) as FFmpegInput;
|
|
644
664
|
sessionMso = ffmpegInput.mediaStreamOptions || this.advertisedMediaStreamOptions;
|
|
645
665
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
666
|
+
let { parser, isDefault } = this.getParser(rtspMode, sessionMso);
|
|
667
|
+
usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
|
|
668
|
+
if (isDefault && (parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP) && this.getLastH264Probe().seiDetected) {
|
|
669
|
+
this.console.warn('SEI packet detected was in video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted.');
|
|
670
|
+
usingScryptedParser = false;
|
|
671
|
+
parser = FFMPEG_PARSER_TCP;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (usingScryptedParser) {
|
|
649
675
|
this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser');
|
|
650
676
|
const rtspClient = new RtspClient(ffmpegInput.url, this.console);
|
|
651
677
|
|
|
@@ -807,40 +833,42 @@ class PrebufferSession {
|
|
|
807
833
|
}
|
|
808
834
|
}
|
|
809
835
|
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
if (!session.isActive) {
|
|
829
|
-
kill();
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
session.killed.finally(kill);
|
|
834
|
-
|
|
835
|
-
const { resetActivityTimer } = setupActivityTimer('mp4', kill, session, rbo.timeout);
|
|
836
|
+
// watch the stream for 10 seconds to see if an sei packet is encountered.
|
|
837
|
+
// if one is found and using scrypted parser as default, will need to restart rebroadcast to prevent
|
|
838
|
+
// downstream issues.
|
|
839
|
+
const seiProbe = (chunk: StreamChunk) => {
|
|
840
|
+
if (chunk.type !== 'h264')
|
|
841
|
+
return;
|
|
842
|
+
const seiDetected = !!findH264NaluType(chunk, H264_NAL_TYPE_SEI);
|
|
843
|
+
if (seiDetected) {
|
|
844
|
+
removeSeiProbe();
|
|
845
|
+
clearTimeout(seiTimeout);
|
|
846
|
+
const h264Probe: H264Probe = {
|
|
847
|
+
seiDetected,
|
|
848
|
+
}
|
|
849
|
+
this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
|
|
850
|
+
if (!usingScryptedParser)
|
|
851
|
+
return;
|
|
836
852
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
.
|
|
853
|
+
let { isDefault } = this.getParser(rtspMode, sessionMso);
|
|
854
|
+
if (!isDefault) {
|
|
855
|
+
this.console.warn('SEI packet detected while operating with Scrypted Parser. If there are issues streaming, consider using the Default parser.');
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
this.console.warn('SEI packet detected while operating with Scrypted Parser as default. Restarting rebroadcast.');
|
|
859
|
+
session.kill();
|
|
860
|
+
this.startPrebufferSession();
|
|
861
|
+
}
|
|
843
862
|
}
|
|
863
|
+
const removeSeiProbe = () => session.removeListener('rtsp', seiProbe);
|
|
864
|
+
session.on('rtsp', seiProbe);
|
|
865
|
+
const seiTimeout = setTimeout(() => {
|
|
866
|
+
removeSeiProbe();
|
|
867
|
+
const h264Probe: H264Probe = {
|
|
868
|
+
seiDetected: false,
|
|
869
|
+
}
|
|
870
|
+
this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
|
|
871
|
+
}, 10000);
|
|
844
872
|
|
|
845
873
|
// complain to the user about the codec if necessary. upstream may send a audio
|
|
846
874
|
// stream but report none exists (to request muting).
|