@scrypted/prebuffer-mixin 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- (()=>{var e={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)}))}},254:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.Bitstream=class{constructor(e){this.view=e,this.bitoffset=0}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}readNibble(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=4;const r=this.view.getUint8(t);if(0===e)return r>>>4;if(e<=4)return r>>>4-e&15;return 15&(r<<e-4|this.view.getUint8(t+1)>>>12-e)}read5(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=5;const r=this.view.getUint8(t);if(0===e)return r>>>3;if(e<=3)return r>>>3-e&31;return 31&(r<<e-3|this.view.getUint8(t+1)>>>11-e)}readWord(){const e=7&this.bitoffset,t=this.bitoffset>>>3;this.bitoffset+=32;const{view:r}=this,i=65536*r.getUint16(t)+r.getUint16(t+2);return 0===e?i:i*2**e+(r.getUint8(t+4)>>>8-e)}more_rbsp_data(){const e=7&this.bitoffset;let t=this.bitoffset>>3;const r=this.view.byteLength;if(t>=r)return!1;let i=this.view.getUint8(t)<<e&255,s=i>0;if(s&&!Number.isInteger(Math.log2(i)))return!0;for(;++t<r;){i=this.view.getUint8(t);const e=i>0;if(s&&e)return!0;if(e&&!Number.isInteger(Math.log2(i)))return!0;s=s||e}return!1}}},415:(e,t,r)=>{"use strict";const i=r(254);i.Bitstream;const s=r(494);function n(e,t,r,i){let s=8,n=8;const o=[];for(let a=0;a<t;a++){if(0!==n){n=(s+e.SignedExpGolomb()+256)%256,r[i]=+(0===a&&0===n)}n&&(s=n),o[a]=s}return o}t.Qc=function(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new i.Bitstream(new DataView(e.buffer,e.byteOffset+4)),r=e[1],o=e[2],a=e[3],c=t.ExpGolomb();let d=1,u=0,l=0,p=0,m=0,h=0;const f=[],g=[],y=[],S=[];if(100===r||110===r||122===r||244===r||44===r||83===r||86===r||118===r||128===r||138===r||139===r||134===r||135===r){d=t.ExpGolomb();let e=8;if(3===d&&(e=12,p=t.readBit()),u=t.ExpGolomb()+8,l=t.ExpGolomb()+8,m=t.readBit(),h=t.readBit(),h){let r=0;for(;r<6;r++)t.readBit()&&f.push(n(t,16,y,r));for(;r<e;r++)t.readBit()&&g.push(n(t,64,S,r-6))}}const v=t.ExpGolomb()+4,b=t.ExpGolomb();let _=0,w=0,P=0;const x=[];let T=0;if(0===b)T=t.ExpGolomb()+4;else if(1===b){_=t.readBit(),w=t.SignedExpGolomb(),P=t.SignedExpGolomb();const e=t.ExpGolomb();for(let r=0;r<e;r++)x.push(t.SignedExpGolomb())}const M=t.ExpGolomb(),I=t.readBit(),D=t.ExpGolomb()+1,E=t.ExpGolomb()+1,C=t.readBit();let R=0;C||(R=t.readBit());const A=t.readBit(),O=t.readBit(),k=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}}(O,t),B=t.readBit();return{sps_id:c,profile_compatibility:o,profile_idc:r,level_idc:a,chroma_format_idc:d,bit_depth_luma:u,bit_depth_chroma:l,color_plane_flag:p,qpprime_y_zero_transform_bypass_flag:m,seq_scaling_matrix_present_flag:h,seq_scaling_matrix:f,log2_max_frame_num:v,pic_order_cnt_type:b,delta_pic_order_always_zero_flag:_,offset_for_non_ref_pic:w,offset_for_top_to_bottom_field:P,offset_for_ref_frame:x,log2_max_pic_order_cnt_lsb:T,max_num_ref_frames:M,gaps_in_frame_num_value_allowed_flag:I,pic_width_in_mbs:D,pic_height_in_map_units:E,frame_mbs_only_flag:C,mb_adaptive_frame_field_flag:R,direct_8x8_inference_flag:A,frame_cropping_flag:O,frame_cropping:k,vui_parameters_present_flag:B,vui_parameters:s.getVUIParams(B,t)}}},494:(e,t)=>{"use strict";function r(e,t){const r=t.ExpGolomb();e.cpb_cnt=r+1,e.bit_rate_scale=t.readNibble(),e.cpb_size_scale=t.readNibble();for(let i=0;i<=r;i++)e.bit_rate_value[i]=t.ExpGolomb()+1,e.cpb_size_value[i]=t.ExpGolomb()+1,e.cbr_flag[i]=t.readBit();e.initial_cpb_removal_delay_length=t.read5()+1,e.cpb_removal_delay_length=t.read5()+1,e.dpb_output_delay_length=t.read5()+1,e.time_offset_length=t.read5()}Object.defineProperty(t,"__esModule",{value:!0}),t.getVUIParams=function(e,t){const i={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,timing_info_present_flag:0,num_units_in_tick:0,time_scale:0,fixed_frame_rate_flag:0,nal_hrd_parameters_present_flag:0,vcl_hrd_parameters_present_flag:0,hrd_params:{cpb_cnt:0,bit_rate_scale:0,cpb_size_scale:0,bit_rate_value:[],cpb_size_value:[],cbr_flag:[],initial_cpb_removal_delay_length:0,cpb_removal_delay_length:0,dpb_output_delay_length:0,time_offset_length: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?(i.aspect_ratio_info_present_flag=t.readBit(),i.aspect_ratio_info_present_flag&&(i.aspect_ratio_idc=t.ExpGolomb(),255===i.aspect_ratio_idc&&(i.sar_width=t.readByte(),i.sar_height=t.readByte())),i.overscan_info_present_flag=t.readBit(),i.overscan_info_present_flag&&(i.overscan_appropriate_flag=t.readBit()),i.video_signal_type_present_flag=t.readBit(),i.video_signal_type_present_flag&&(i.video_format=t.readBit()<<2|t.readBit()<<1|t.readBit(),i.video_full_range_flag=t.readBit(),i.colour_description_present_flag=t.readBit(),i.colour_description_present_flag&&(i.colour_primaries=t.readByte(),i.transfer_characteristics=t.readByte(),i.matrix_coefficients=t.readByte())),i.chroma_loc_info_present_flag=t.readBit(),i.chroma_loc_info_present_flag&&(i.chroma_sample_loc_type_top_field=t.ExpGolomb(),i.chroma_sample_loc_type_bottom_field=t.ExpGolomb()),i.timing_info_present_flag=t.readBit(),i.timing_info_present_flag&&(i.num_units_in_tick=t.readWord(),i.time_scale=t.readWord(),i.fixed_frame_rate_flag=t.readBit()),i.nal_hrd_parameters_present_flag=t.readBit(),i.nal_hrd_parameters_present_flag&&r(i.hrd_params,t),i.vcl_hrd_parameters_present_flag=t.readBit(),i.vcl_hrd_parameters_present_flag&&r(i.hrd_params,t),(i.nal_hrd_parameters_present_flag||i.vcl_hrd_parameters_present_flag)&&(i.low_delay_hrd_flag=t.readBit()),i.pic_struct_present_flag=t.readBit(),i.bitstream_restriction_flag=t.readBit(),i.bitstream_restriction_flag&&(i.motion_vectors_over_pic_boundaries_flag=t.readBit(),i.max_bytes_per_pic_denom=t.ExpGolomb(),i.max_bits_per_mb_denom=t.ExpGolomb(),i.log2_max_mv_length_horizontal=t.ExpGolomb(),i.log2_max_mv_length_vertical=t.ExpGolomb(),i.num_reorder_frames=t.ExpGolomb(),i.max_dec_frame_buffering=t.ExpGolomb()),i):i}},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,SecuritySystemMode:()=>m,SecuritySystemObstruction:()=>h,MediaPlayerState:()=>f,ScryptedInterface:()=>g,ScryptedMimeTypes:()=>y});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.tampered="tampered",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.securitySystemState="securitySystemState",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"]},TamperSensor:{name:"TamperSensor",methods:[],properties:["tampered"]},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"]},SecuritySystem:{name:"SecuritySystem",methods:["armSecuritySystem","disarmSecuritySystem"],properties:["securitySystemState"]},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,g,y;!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.SecuritySystem="SecuritySystem",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.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(m||(m={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(h||(h={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(f||(f={})),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.TamperSensor="TamperSensor",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.SecuritySystem="SecuritySystem",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"}(g||(g={})),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"}(y||(y={}))},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:()=>$e,default:()=>Ge});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","-pix_fmt","yuvj420p","-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 _(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function w(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 w(e,0)}async function x(){return async function(e){const t=b().createSocket("udp4"),{port:r,url:i}=await w(t,e);return{server:t,port:r,url:i}}(0)}async function T(e,t){return e.bind(t),await(0,S.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function M(){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 E=["decode_slice_header error","no frame!","non-existing PPS"];function C(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:R}=t();async function A(e,t){return new Promise(((r,i)=>{e.on("exit",(()=>i(new Error("ffmpeg exited while waiting to parse stream information: "+t))));const s=i=>{const n=i.toString().split("Output ")[0],o=n.lastIndexOf(`${t}: `);if(-1!==o){const i=n.substring(o+t.length+1).trim();let a=i.indexOf(" ");const c=i.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",s),e.stderr.removeListener("data",s),r(i.substring(0,a)))}};e.stdout.on("data",s),e.stderr.on("data",s)}))}function O(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,function(){const r="timeout waiting for data, killing parser session";console.error(r,e),t(new Error(r))}())}),i)),r.once("killed",(()=>clearInterval(s))),o(),{resetActivityTimer:o,clearActivityTimer:function(){clearInterval(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(e){i&&(s.emit("killed"),s.emit("error",e||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 T(o,n.port+1),m((()=>{r.close(),o.close()})),l.push(...i.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:c}=O(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 M(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),l.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=O(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(e)}})()}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 R.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 E)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(new Error("ffmpeg exited")))),v=p?new Promise((e=>{const t=[];g.stdio[f-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let _=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+_];_++;try{const{resetActivityTimer:r}=O(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(e)}})),async function(e){return A(e,"Audio")}(g).then((e=>n=e)),async function(e){return new Promise(((t,r)=>{e.on("exit",(()=>r(new Error("ffmpeg exited while waiting to parse stream resolution"))));const i=r=>{const s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(g).then((e=>a=e)),await async function(e){return A(e,"Video")}(g).then((e=>o=e)),{sdp:v,get inputAudioCodec(){return n},get inputVideoCodec(){return o},get inputVideoResolution(){return{width:parseInt(a?.[2]),height:parseInt(a?.[3])}},get isActive(){return i},kill(e){u(e)},killed:d,negotiateMediaStream:()=>{const t=C(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 U="\n".charCodeAt(0);async function H(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,U)}var L=r(113),j=r.n(L),N=r(747);const F=require("tls");var q=r.n(F);class K extends Error{constructor(e){super("Operation Timed Out"),this.promise=e}}function $(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 G(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function W(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 z="a=control:";function Q(e){const t=e.find((e=>e.startsWith(z)))?.substring(z.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase(),i=function(e){return{type:e.split(" ")[0].substring(2),payloadTypes:G(e)}}(e[0]);let s,n;r?.includes("mpeg4")?s="aac":r?.includes("opus")?s="opus":r?.includes("pcma")?s="pcm_alaw":r?.includes("pcmu")?s="pcm_ulaw":r?.includes("pcm")?s="pcm":r?.includes("h264")?s="h264":r?.includes("h265")?s="h265":r||"audio"!==i.type||(s="pcm_alaw");for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){n=t;break}}return{...i,fmtp:W(e),lines:e,contents:e.join("\r\n"),control:t,codec:s,direction:n}}function J(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(Q),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}async function Y(e){await new Promise((t=>setTimeout(t,e)))}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 H(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;needKeepAlive=!1;setupOptions=new Map;issuedTeardown=!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)}async safeTeardown(){if(!this.issuedTeardown){this.issuedTeardown=!0;try{this.writeTeardown(),await Y(500)}catch(e){}finally{this.client.destroy()}}}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 received invalid frame magic. This may be a bug in your camera firmware. If this error persists, switch your RTSP Parser to FFmpeg or Scrypted (UDP): "+e.toString());const t=e.readUInt8(1),r=e.readUInt16BE(2),i=await V(this.client,r);this.setupOptions.get(t)?.onRtp?.(e,i)}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),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 ${N.rh.computeHash(t)}`}const r=N.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=j().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=j().createHash("md5").update(`${e}:${o}`).digest("hex"),d=j().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 K(a))),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){const t={Transport:`RTP/AVP/${"udp"===e.type?"UDP":"TCP"};unicast;${"udp"===e.type?"client_port":"interleaved"}=${e.port}-${e.port+1}`},r=await this.request("SETUP",t,e.path);let i;if(r.headers.session){const e=ee(r.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=r.headers.session.split(";")[0]}if(r.headers.transport){const e=r.headers.transport.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const[t,r,s]=e;r&&s&&(i={begin:parseInt(r),end:parseInt(s)})}}return"tcp"===e.type&&this.setupOptions.set(i.begin,e),Object.assign({interleaved:i},r)}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,L.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await H(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=J(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(e,t){const r=this.settings[e];let i;return r&&(i=this.getItemInternal(e,r)),this.putSettingInternal(r,i,e,t)}putSettingInternal(t,r,i,s){t?.noStore||(t.mapPut&&(s=t.mapPut(r,s)),"object"==typeof s?this.device.storage.setItem(i,JSON.stringify(s)):this.device.storage.setItem(i,s?.toString())),t?.onPut?.(r,s),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItemInternal(e,t){if(!t)return this.device.storage.getItem(e);const r=function(e,t,r){const 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,(()=>t.persistedDefaultValue?(this.putSettingInternal(t,void 0,e,t.persistedDefaultValue),t.persistedDefaultValue):t.defaultValue));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)}}const{mediaManager:ae}=t();async function*ce(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 de=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function ue(e){return e}function le(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...de],async*parse(e){const t=ce(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:ue}}var pe=r(415);const me=require("stream");function he(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)}}function fe(e,t,r,i,s){const n=J(e),o=C(t)||{id:void 0,name:void 0};if(void 0===o.video&&(o.video={}),null===s?.video&&(o.video=null),o.video&&(o.video.codec=r),s?.audio?.codec&&s?.audio?.codec!==i){if(n.msections.find((e=>"audio"===e.type&&e.codec===s?.audio?.codec)))return o.audio={codec:s?.audio?.codec},o}return o.audio=o?.audio?.codec===i?t?.audio:{codec:i},o}function ge(e,t,r,i,s){let n=!0;const o=new me.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=J(r),c=a.msections.find((e=>"audio"===e.type)),d=a.msections.find((e=>"video"===e.type)),u=c?.payloadTypes?.[0],l=d?.payloadTypes?.[0],p=c?.codec,m=d.codec;let h;const f=new Promise((e=>{h=e})),g=e=>{n&&(o.emit("killed"),o.emit("error",e||new Error("killed"))),n=!1,h(),t.destroy()};t.on("close",(()=>{g(new Error("rfc4751 socket closed"))})),t.on("error",(e=>{g(e)}));const{resetActivityTimer:y}=O("rtsp",g,o,s?.timeout);let v;const b=d?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],_=b?.split(",")?.[0];if(_)try{const t=Buffer.from(_,"base64"),r=(0,pe.Qc)(t);v=he(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{await Y(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:2,skipHeader:void 0,callback:(t,r)=>{let i;const s=127&r[1],n=Buffer.alloc(2);n[0]=36,s===u?n[1]=0:s===l&&(n[1]=2),t=Buffer.concat([n,t]),s===u?i=p:s===l&&(i=m);const a={chunks:[t,r],type:i};if(!v){const t=X(a,7);if(t)try{const r=(0,pe.Qc)(t);v=he(r),e.log(v),e.log("parsed bitstream sps",r)}catch(t){e.warn("sps parsing failed"),v={width:NaN,height:NaN}}}o.emit("rtsp",a),y()}})})().catch((e=>{throw e})).finally((()=>{g(new Error("parser exited"))})),{sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:p,inputVideoCodec:m,get inputVideoResolution(){return v},get isActive(){return n},kill(e){g(e)},killed:f,resetActivityTimer:y,negotiateMediaStream:e=>fe(r,i,m,p,e),emit(e,t){return o.emit(e,t),this},on(e,t){return o.on(e,t),this},once(e,t){return o.once(e,t),this},removeListener(e,t){return o.removeListener(e,t),this}}}const{deviceManager:ye}=t(),Se="transcode",ve="mixin:@scrypted/prebuffer-mixin";function be(){if(!ye.getNativeIds().includes(Se))return;return ye.getDeviceState(Se)?.id}class _e extends e.ScryptedDeviceBase{constructor(e){super(Se),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(ve))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 we(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function Pe(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):we(t)}function xe(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},videoFilterArguments:{group:"Transcoding",title:"Video Filter Arguments",description:"FFmpeg arguments used to filter input video when transcoding a stream. This can be used to crops, scale, rotates, etc.",placeholder:"transpose=1",hide:!0}});function i(e,t){const i=Pe(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(be())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:we(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:Te,log:Me,systemManager:Ie,deviceManager:De}=t(),Ee="Default",Ce="AAC or No Audio",Re=`${Ce} (Copy)`,Ae="Compatible Audio",Oe="Other Audio",ke=["aac","mp3","mp2","opus"],Be="-fflags +genpts",Ve="Scrypted (TCP)",Ue="Scrypted (UDP)",He="FFmpeg (TCP)",Le="FFmpeg (UDP)",je="Default",Ne=[Ce,Ae,Oe],Fe=["mpegts","mp4","rtsp"];class qe{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=j().randomBytes(8).toString("hex"),this.storage.setItem(i,this.rtspServerPath))}get canPrebuffer(){return"rawvideo"!==this.advertisedMediaStreamOptions.container&&"ffmpeg"!==this.advertisedMediaStreamOptions.container}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(){for(const e of Fe)this.prebuffers[e]=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((e=>this.parserSessionPromise=void 0)),this.parserSessionPromise.then((e=>e.killed.finally((()=>this.parserSessionPromise=void 0)))))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";Ne.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),r=-1!==e.indexOf(Ae),i=-1!==e.indexOf(Oe);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===He&&(r=He),i===Le&&(r=Le),e&&!r&&(i&&i!==je||(r=Ve),i===Ve&&(r=Ve),i===Ue&&(r=Ue)),r||(r=He)):r=je,{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}}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:[je,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||je});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)||Ee,choices:[Ee,Re,"Compatible Audio (Copy)","Other Audio (Transcode)"]})},l=()=>{t.push({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:Be,choices:[Be,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Probe()?.seiDetected?Ve:He,i=e?[Ve,Ue]:[],s=this.storage.getItem(this.rtspParserKey)||je;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:s,choices:[je,...i,He,Le]}),(s===je?r:s).includes("Scrypted")||(p=!0)}if(n&&u(),p&&l(),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.clearPrebuffers();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=!ke.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Me.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 w=["-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,L.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){let t,r={};const i=()=>e.slice(t);for(let i=0;i<e.length;i++){const s=e[i];"h264"===s.type?X(s,7)&&(t=i):r[s.type]=s}if(void 0!==t)return i();r={};for(let i=0;i<e.length;i++){const s=e[i];"h264"===s.type?X(s,5)&&(t=i):r[s.type]=s}return void 0!==t?i():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:w,acodec:r?y:["-acodec","copy"]});this.sdp=e.sdp,P.parsers.rtsp=e}else P.parsers.mpegts={container:"mpegts",outputArguments:[...(T={vcodec:w,acodec:y})?.vcodec||[],...T?.acodec||[],"-f","mpegts"],parse:(M=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<M)continue;const s=Buffer.concat(t);I?.(s);const n=s.length%M,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 T,M,I;d&&(P.parsers.mp4=le({vcodec:w,acodec:y}));const D=await this.mixinDevice.getVideoStream(t),E="x-scrypted/x-rfc4571"===D.mimeType;let C,R;this.storage.removeItem(this.lastDetectedAudioCodecKey);let A=!1,B=!1;if(c&&E){A=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Te.convertMediaObjectToJSON(D,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;C=ge(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=C.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Te.convertMediaObjectToBuffer(D,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());R=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,R);if(A=s===Ve||s===Ue,B=A&&n&&"scrypted"!==R.tool,n&&A&&this.getLastH264Probe().seiDetected&&("scrypted"===R.tool?this.console.warn("SEI packet detected was in video stream, but stream is marked safe by Scrypted. The Default Scrypted RTSP Parser will be used. This can be overriden by setting the RTSP Parser to Scrypted."):(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."),A=!1,s=He)),A)C=await async function(e,t,r,i){let s=!0;const n=new me.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new te(t,e);a.requestTimeout=i.rtspRequestTimeout;const c=()=>{for(const e of o)_(e);a.safeTeardown()};let d;const u=new Promise((e=>{d=e})),l=e=>{s&&(n.emit("killed"),n.emit("error",e||new Error("killed"))),s=!1,d(),c()};a.client.on("close",(()=>{l(new Error("rtsp socket closed"))})),a.client.on("error",(e=>{l(e)}));const{resetActivityTimer:p}=O("rtsp",l,n,i?.rtspRequestTimeout);try{await a.options();const t=await a.describe(),c=t.headers["content-base"];if(c){const e=new URL(c),t=new URL(a.url);e.username=t.username,e.password=t.password,a.url=e.toString()}let d=t.body.toString().trim();e.log("sdp",d);const m=J(d);let h=0;const f={};let g;const{useUdp:y}=i,S=e=>{if(y&&e.session&&!g){const t=ee(e.session);g=parseInt(t.timeout)}},v=async(e,t)=>{let r,i=h;if(y){const s=h,{port:c,server:d}=await x();r=d,o.push(d),i=c,d.on("message",(e=>{const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(s,1),r.writeUInt16BE(e.length,2);const i={chunks:[r,e],type:t};n.emit("rtsp",i),p?.()}));const u=await a.setup({path:e,type:"udp",port:i});S(u.headers);const l=Buffer.alloc(1),m=u.headers.transport.match(/.*?server_port=([0-9]+)-([0-9]+)/),[g,y,v]=m,{hostname:b}=new URL(a.url);r.send(l,parseInt(y),b),f[h]=t}else{const r=await a.setup({path:e,type:"tcp",port:i,onRtp:(e,r)=>{const i={chunks:[e,r],type:t};n.emit("rtsp",i),p?.()}});r.interleaved?f[r.interleaved.begin]=t:f[h]=t}h+=2};let b=!1;m.msections=m.msections.filter((t=>{if("video"===t.type){if(b)return e.warn("additional video section found. skipping."),!1;b=!0}else{if("audio"!==t.type)return e.warn("unknown section",t.type),!1;if(i.audioSoftMuted)return!1}return!0}));for(const e of m.msections)await v(e.control,e.codec);return d=[...m.header.lines,...m.msections.map((e=>e.lines)).flat()].join("\r\n"),await a.play(),process.nextTick((()=>a.readLoop().catch((e=>l(e))).finally((()=>l(new Error("rtsp read loop exited")))))),(()=>{const t=m.msections.find((e=>"audio"===e.type)),i=m.msections.find((e=>"video"===e.type)),o=t?.codec,a=i.codec;let c;const h=i?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],f=h?.split(",")?.[0];if(f)try{const t=Buffer.from(f,"base64"),r=(0,pe.Qc)(t);c=he(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return{sdp:Promise.resolve([Buffer.from(d)]),inputAudioCodec:o,inputVideoCodec:a,get inputVideoResolution(){return c},get isActive(){return s},kill(e){l(e)},killed:u,resetActivityTimer:p,negotiateMediaStream:e=>fe(d,r,a,o,e),emit(e,t){return n.emit(e,t),this},on(e,t){return n.on(e,t),this},once(e,t){return n.once(e,t),this},removeListener(e,t){return n.removeListener(e,t),this}}})()}catch(e){throw c(),e}}(this.console,i.url,i.mediaStreamOptions,{useUdp:s===Ue,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=C.sdp.then((e=>Buffer.concat(e).toString()));else{s===Le?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===He&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Be;i.inputArguments.unshift(...e.split(" ")),C=await k(i,P)}}const V=e=>{if("h264"!==e.type)return;const t=!!X(e,6);if(t){U(),clearTimeout(H);const e={seiDetected:t};if(this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),!A)return;let{isDefault:r}=this.getParser(c,R);if(!r||"scrypted"===R.tool)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."),C.kill(new Error("restarting due to SEI packet detection")),this.startPrebufferSession()}},U=()=>C.removeListener("rtsp",V);C.killed.finally((()=>clearTimeout(H))),C.on("rtsp",V);const H=setTimeout((()=>{U();this.storage.setItem(this.lastH264ProbeKey,JSON.stringify({seiDetected:!1}))}),this.getLastH264Probe().seiDetected?6e4:1e4);!r&&i&&void 0!==C.inputAudioCodec&&C.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const j=t?.video?.codec;if(j&&void 0!==C.inputVideoCodec&&C.inputVideoCodec!==j&&this.console.warn("Video codec plugin reported vs detected mismatch",j,C.inputVideoCodec),C.inputAudioCodec?ke.includes(C.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",C.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",C.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,C.inputAudioCodec||"null"),"h264"!==C.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."),C.kill(new Error("audio probe completed, restarting")),this.startPrebufferSession();if(this.parserSession=C,C.killed.finally((()=>{this.parserSession===C&&(this.parserSession=void 0)})),C.killed.finally((()=>{clearTimeout(this.inactivityTimeout)})),De.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),R?.refreshAt){let t,r=R;const i=async()=>{if(!C.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await Te.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),C.killed.finally((()=>clearTimeout(t)))}for(const e of Fe){let t=0,r=this.prebuffers[e];C.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 C}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(new Error("stream inactivity")))}),3e4))))}async handleRebroadcasterClient(e){const{isActiveClient:t,container:r,session:i,socketPromise:s,requestedPrebuffer:n}=e;n&&this.console.log("sending prebuffer",n),s.catch((()=>this.inactivityCheck(i,!1))),s.then((e=>{t&&(this.activeClients++,this.printActiveClients()),e.once("close",(()=>{t&&(this.activeClients--,this.printActiveClients()),this.inactivityCheck(i,t)}))})),B(s,{connect:(t,s)=>{const o=Date.now(),a=(r,i)=>{if(e.filter&&!(r=e.filter(r,i)))return;t(r)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{i.removeListener(r,a),i.removeListener("killed",c),s()};i.on(r,a),i.once("killed",c);const d=this.prebuffers[r];if("rtsp"===r&&e.findSyncFrame){const e=this.parsers[r],t=d.filter((e=>e.time>=o-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)a(e,!0)}else for(const e of d)e.time<o-n||a(e,!0);return c}})}async getVideoStream(e,t){if(!1===t?.refresh&&!this.parserSessionPromise)throw new Error("Stream is currently unavailable and will not be started for this request. RequestMediaStreamOptions.refresh === false");this.ensurePrebufferSession();const r=await this.parserSessionPromise;let i=t?.prebuffer;null==i&&(i=Math.max(4e3,this.getDetectedIdrInterval()||4e3));const{rtspMode:s,muxingMp4:n}=this.getRebroadcastContainer(),o=s?"rtsp":"mpegts";let a=this.parsers[t?.container]?t?.container:o;const c=r.negotiateMediaStream(t);let d,u,l,p=await this.sdp;const m=new Map;if("rtsp"===a){const e=J(p),r=e.msections.find((e=>e.codec&&e.codec===c.video?.codec))||e.msections.find((e=>"video"===e.type));let i=e.msections.find((e=>e.codec&&e.codec===c.audio?.codec))||e.msections.find((e=>"audio"===e.type));null===c.audio&&(i=void 0),e.msections=e.msections.filter((e=>e===r||e===i));const s=void 0===t?.prebuffer,n=e.msections.find((e=>"video"===e.type))?.codec;p=e.toSdp(),l=(e,t)=>{const r=m.get(e.type);if(null==r)return;if(t&&s&&e.type!==n)return;const i=e.chunks.slice(),o=Buffer.from(i[0]);return o.writeUInt8(r,1),i[0]=o,{startStream:e.startStream,chunks:i}};const o=await M();d=o.clientPromise.then((async e=>{p=$(p);const t=new re(e,p);await t.handlePlayback();for(const e of Object.values(t.setupTracks))m.set(e.codec,e.destination),m.set(`rtcp-${e.codec}`,e.destination+1);return e})),u=o.url.replace("tcp://","rtsp://")}else{const e=await M();d=e.clientPromise,u=`tcp://127.0.0.1:${e.port}`}c.sdp=p;const h=!1!==t?.refresh;this.handleRebroadcasterClient({findSyncFrame:e,isActiveClient:h,container:a,requestedPrebuffer:i,socketPromise:d,session:r,filter:l}),c.prebuffer=i;const{reencodeAudio:f}=this.getAudioConfig();this.audioDisabled?c.audio=null:f&&n&&(c.audio={codec:"aac",encoder:"aac",profile:"aac_low"}),r.inputVideoResolution?.width&&r.inputVideoResolution?.height&&c.video&&Object.assign(c.video,r.inputVideoResolution);const g=Date.now();let y=0;const S=this.prebuffers[a];for(const e of S)if(!(e.time<g-i))for(const t of e.chunks)y+=t.length;return{url:u,container:a,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,y).toString(),...this.parsers[a].inputArguments||[],"-f",this.parsers[a].container,"-i",u],mediaStreamOptions:c}}}class Ke extends oe{released=!1;sessions=new Map;streamSettings=xe(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(t){if(t?.directMediaStream)return this.mixinDevice.getVideoStream(t);await this.ensurePrebufferSessions();let r,i,s=t?.id;const n=this.mixins?.includes(be()),o=await this.mixinDevice.getVideoStreamOptions();let a;const c=2e6;if(!s){switch(t?.destination){case"medium-resolution":case"remote":a=this.streamSettings.getRemoteStream(o),i=this.plugin.transcodeStorageSettings.values.remoteStreamingBitrate;break;case"low-resolution":a=this.streamSettings.getLowResolutionStream(o),i=512e3;break;case"local-recorder":a=this.streamSettings.getRecordingStream(o),i=c;break;case"remote-recorder":a=this.streamSettings.getRemoteRecordingStream(o),i=c;break;default:a=this.streamSettings.getDefaultStream(o),i=c}s=a.stream.id,this.console.log("Selected stream",a.stream.name),n&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(a.title)&&(r=this.plugin.transcodeStorageSettings.values.h264EncoderArguments?.split(" "))}let d,u=this.sessions.get(s);if(u.canPrebuffer||(this.console.log("Source container can not be prebuffered. Using a direct media stream."),u=void 0),u){const e=!(n||t?.container&&"rtsp"!==t?.container||"ffmpeg"===t?.tool);d=await u.getVideoStream(e,t)}else{const r=await this.mixinDevice.getVideoStream(t);if(!n)return r;d=await Te.convertMediaObjectToJSON(r,e.ScryptedMimeTypes.FFmpegInput)}if(d.h264EncoderArguments=r,d.destinationVideoBitrate=i,n&&this.streamSettings.storageSettings.values.missingCodecParameters&&(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.push("-bsf:v","dump_extra")),n&&this.streamSettings.storageSettings.values.videoFilterArguments)if(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.length){const e=d.h264FilterArguments?.findIndex((e=>"-filter_complex"===e));void 0!==e&&-1!==e?d.h264FilterArguments[e+1]=d.h264FilterArguments[e+1]+`[prefilter] ; [prefilter] ${this.streamSettings.storageSettings.values.videoFilterArguments}`:d.h264FilterArguments.push("-filter_complex",this.streamSettings.storageSettings.values.videoFilterArguments)}else d.h264FilterArguments.push("-filter_complex",this.streamSettings.storageSettings.values.videoFilterArguments);return n&&(d.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),Te.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"),Me.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);i.length||(this.online=!0);let o=0;for(const e of s){let r=this.sessions.get(e);if(r)continue;const s=t?.find((t=>t.id===e));s?.prebuffer&&Me.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);r=new qe(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."):c?(async()=>{for(;this.sessions.get(e)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{this.console.log("prebuffer session starting");const t=await r.parserSessionPromise;this.console.log("prebuffer session started"),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)")})():this.console.log("stream",a,"will be rebroadcast on demand.")}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?")}De.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(new Error("rebroadcast settings changed"))));this.ensurePrebufferSessions()}getPrebufferedStreams(e){return Pe(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(new Error("rebroadcast disabled")),e.clearPrebuffers()})))}}class $e 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(Ie.getSystemState())){const t=Ie.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const 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((()=>De.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{De.onDeviceDiscovered({nativeId:Se,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===Se)return new _e(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){_(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=Math.max(4e3,t.getDetectedIdrInterval()||4e3);t.handleRebroadcasterClient({findSyncFrame:!0,isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await r.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,r){const i=JSON.parse(e.toString()),{url:s,sdp:n}=i,o=J(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 M(),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,ve];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Ke(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 Ge=new $e})();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)}))}},254:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.Bitstream=class{constructor(e){this.view=e,this.bitoffset=0}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}readNibble(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=4;const r=this.view.getUint8(t);if(0===e)return r>>>4;if(e<=4)return r>>>4-e&15;return 15&(r<<e-4|this.view.getUint8(t+1)>>>12-e)}read5(){const e=7&this.bitoffset,t=this.bitoffset>>3;this.bitoffset+=5;const r=this.view.getUint8(t);if(0===e)return r>>>3;if(e<=3)return r>>>3-e&31;return 31&(r<<e-3|this.view.getUint8(t+1)>>>11-e)}readWord(){const e=7&this.bitoffset,t=this.bitoffset>>>3;this.bitoffset+=32;const{view:r}=this,i=65536*r.getUint16(t)+r.getUint16(t+2);return 0===e?i:i*2**e+(r.getUint8(t+4)>>>8-e)}more_rbsp_data(){const e=7&this.bitoffset;let t=this.bitoffset>>3;const r=this.view.byteLength;if(t>=r)return!1;let i=this.view.getUint8(t)<<e&255,s=i>0;if(s&&!Number.isInteger(Math.log2(i)))return!0;for(;++t<r;){i=this.view.getUint8(t);const e=i>0;if(s&&e)return!0;if(e&&!Number.isInteger(Math.log2(i)))return!0;s=s||e}return!1}}},415:(e,t,r)=>{"use strict";const i=r(254);i.Bitstream;const s=r(494);function n(e,t,r,i){let s=8,n=8;const o=[];for(let a=0;a<t;a++){if(0!==n){n=(s+e.SignedExpGolomb()+256)%256,r[i]=+(0===a&&0===n)}n&&(s=n),o[a]=s}return o}t.Qc=function(e){if(7!=(31&e[0]))throw new Error("Not an SPS unit");const t=new i.Bitstream(new DataView(e.buffer,e.byteOffset+4)),r=e[1],o=e[2],a=e[3],c=t.ExpGolomb();let d=1,u=0,l=0,p=0,m=0,h=0;const f=[],g=[],y=[],S=[];if(100===r||110===r||122===r||244===r||44===r||83===r||86===r||118===r||128===r||138===r||139===r||134===r||135===r){d=t.ExpGolomb();let e=8;if(3===d&&(e=12,p=t.readBit()),u=t.ExpGolomb()+8,l=t.ExpGolomb()+8,m=t.readBit(),h=t.readBit(),h){let r=0;for(;r<6;r++)t.readBit()&&f.push(n(t,16,y,r));for(;r<e;r++)t.readBit()&&g.push(n(t,64,S,r-6))}}const v=t.ExpGolomb()+4,b=t.ExpGolomb();let _=0,w=0,P=0;const x=[];let T=0;if(0===b)T=t.ExpGolomb()+4;else if(1===b){_=t.readBit(),w=t.SignedExpGolomb(),P=t.SignedExpGolomb();const e=t.ExpGolomb();for(let r=0;r<e;r++)x.push(t.SignedExpGolomb())}const M=t.ExpGolomb(),I=t.readBit(),O=t.ExpGolomb()+1,D=t.ExpGolomb()+1,C=t.readBit();let A=0;C||(A=t.readBit());const R=t.readBit(),E=t.readBit(),k=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}}(E,t),B=t.readBit();return{sps_id:c,profile_compatibility:o,profile_idc:r,level_idc:a,chroma_format_idc:d,bit_depth_luma:u,bit_depth_chroma:l,color_plane_flag:p,qpprime_y_zero_transform_bypass_flag:m,seq_scaling_matrix_present_flag:h,seq_scaling_matrix:f,log2_max_frame_num:v,pic_order_cnt_type:b,delta_pic_order_always_zero_flag:_,offset_for_non_ref_pic:w,offset_for_top_to_bottom_field:P,offset_for_ref_frame:x,log2_max_pic_order_cnt_lsb:T,max_num_ref_frames:M,gaps_in_frame_num_value_allowed_flag:I,pic_width_in_mbs:O,pic_height_in_map_units:D,frame_mbs_only_flag:C,mb_adaptive_frame_field_flag:A,direct_8x8_inference_flag:R,frame_cropping_flag:E,frame_cropping:k,vui_parameters_present_flag:B,vui_parameters:s.getVUIParams(B,t)}}},494:(e,t)=>{"use strict";function r(e,t){const r=t.ExpGolomb();e.cpb_cnt=r+1,e.bit_rate_scale=t.readNibble(),e.cpb_size_scale=t.readNibble();for(let i=0;i<=r;i++)e.bit_rate_value[i]=t.ExpGolomb()+1,e.cpb_size_value[i]=t.ExpGolomb()+1,e.cbr_flag[i]=t.readBit();e.initial_cpb_removal_delay_length=t.read5()+1,e.cpb_removal_delay_length=t.read5()+1,e.dpb_output_delay_length=t.read5()+1,e.time_offset_length=t.read5()}Object.defineProperty(t,"__esModule",{value:!0}),t.getVUIParams=function(e,t){const i={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,timing_info_present_flag:0,num_units_in_tick:0,time_scale:0,fixed_frame_rate_flag:0,nal_hrd_parameters_present_flag:0,vcl_hrd_parameters_present_flag:0,hrd_params:{cpb_cnt:0,bit_rate_scale:0,cpb_size_scale:0,bit_rate_value:[],cpb_size_value:[],cbr_flag:[],initial_cpb_removal_delay_length:0,cpb_removal_delay_length:0,dpb_output_delay_length:0,time_offset_length: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?(i.aspect_ratio_info_present_flag=t.readBit(),i.aspect_ratio_info_present_flag&&(i.aspect_ratio_idc=t.ExpGolomb(),255===i.aspect_ratio_idc&&(i.sar_width=t.readByte(),i.sar_height=t.readByte())),i.overscan_info_present_flag=t.readBit(),i.overscan_info_present_flag&&(i.overscan_appropriate_flag=t.readBit()),i.video_signal_type_present_flag=t.readBit(),i.video_signal_type_present_flag&&(i.video_format=t.readBit()<<2|t.readBit()<<1|t.readBit(),i.video_full_range_flag=t.readBit(),i.colour_description_present_flag=t.readBit(),i.colour_description_present_flag&&(i.colour_primaries=t.readByte(),i.transfer_characteristics=t.readByte(),i.matrix_coefficients=t.readByte())),i.chroma_loc_info_present_flag=t.readBit(),i.chroma_loc_info_present_flag&&(i.chroma_sample_loc_type_top_field=t.ExpGolomb(),i.chroma_sample_loc_type_bottom_field=t.ExpGolomb()),i.timing_info_present_flag=t.readBit(),i.timing_info_present_flag&&(i.num_units_in_tick=t.readWord(),i.time_scale=t.readWord(),i.fixed_frame_rate_flag=t.readBit()),i.nal_hrd_parameters_present_flag=t.readBit(),i.nal_hrd_parameters_present_flag&&r(i.hrd_params,t),i.vcl_hrd_parameters_present_flag=t.readBit(),i.vcl_hrd_parameters_present_flag&&r(i.hrd_params,t),(i.nal_hrd_parameters_present_flag||i.vcl_hrd_parameters_present_flag)&&(i.low_delay_hrd_flag=t.readBit()),i.pic_struct_present_flag=t.readBit(),i.bitstream_restriction_flag=t.readBit(),i.bitstream_restriction_flag&&(i.motion_vectors_over_pic_boundaries_flag=t.readBit(),i.max_bytes_per_pic_denom=t.ExpGolomb(),i.max_bits_per_mb_denom=t.ExpGolomb(),i.log2_max_mv_length_horizontal=t.ExpGolomb(),i.log2_max_mv_length_vertical=t.ExpGolomb(),i.num_reorder_frames=t.ExpGolomb(),i.max_dec_frame_buffering=t.ExpGolomb()),i):i}},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,SecuritySystemMode:()=>m,SecuritySystemObstruction:()=>h,MediaPlayerState:()=>f,ScryptedInterface:()=>g,ScryptedMimeTypes:()=>y});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.tampered="tampered",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.securitySystemState="securitySystemState",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"]},TamperSensor:{name:"TamperSensor",methods:[],properties:["tampered"]},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"]},SecuritySystem:{name:"SecuritySystem",methods:["armSecuritySystem","disarmSecuritySystem"],properties:["securitySystemState"]},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,g,y;!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.SecuritySystem="SecuritySystem",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.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(m||(m={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(h||(h={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(f||(f={})),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.TamperSensor="TamperSensor",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.SecuritySystem="SecuritySystem",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"}(g||(g={})),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"}(y||(y={}))},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:()=>$e,default:()=>Ge});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","-pix_fmt","yuvj420p","-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 _(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function w(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 w(e,0)}async function x(){return async function(e){const t=b().createSocket("udp4"),{port:r,url:i}=await w(t,e);return{server:t,port:r,url:i}}(0)}async function T(e,t){return e.bind(t),await(0,S.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function M(){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 O=r.n(I);const D=["decode_slice_header error","no frame!","non-existing PPS"];function C(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:A}=t();async function R(e,t){return new Promise(((r,i)=>{e.on("exit",(()=>i(new Error("ffmpeg exited while waiting to parse stream information: "+t))));const s=i=>{const n=i.toString().split("Output ")[0],o=n.lastIndexOf(`${t}: `);if(-1!==o){const i=n.substring(o+t.length+1).trim();let a=i.indexOf(" ");const c=i.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",s),e.stderr.removeListener("data",s),r(i.substring(0,a)))}};e.stdout.on("data",s),e.stderr.on("data",s)}))}function E(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,function(){const r="timeout waiting for data, killing parser session";console.error(r,e),t(new Error(r))}())}),i)),r.once("killed",(()=>clearInterval(s))),o(),{resetActivityTimer:o,clearActivityTimer:function(){clearInterval(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(e){i&&(s.emit("killed"),s.emit("error",e||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 T(o,n.port+1),m((()=>{r.close(),o.close()})),l.push(...i.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:c}=E(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 M(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),l.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=E(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(e)}})()}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 A.getFFmpegPath(),l,{stdio:h});let v;!function(e,t,r,i){const s=!!O().env.SCRYPTED_FFMPEG_NOISY||!!i?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const i=n=>{const o=n.toString();for(const e of D)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(new Error("ffmpeg exited")))),v=p?new Promise((e=>{const t=[];g.stdio[f-1].on("data",(r=>{t.push(r),e(t)}))})):Promise.resolve([]);let _=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+_];_++;try{const{resetActivityTimer:r}=E(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(e)}})),async function(e){return R(e,"Audio")}(g).then((e=>n=e)),async function(e){return new Promise(((t,r)=>{e.on("exit",(()=>r(new Error("ffmpeg exited while waiting to parse stream resolution"))));const i=r=>{const s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(g).then((e=>a=e)),await async function(e){return R(e,"Video")}(g).then((e=>o=e)),{sdp:v,get inputAudioCodec(){return n},get inputVideoCodec(){return o},get inputVideoResolution(){return{width:parseInt(a?.[2]),height:parseInt(a?.[3])}},get isActive(){return i},kill(e){u(e)},killed:d,negotiateMediaStream:()=>{const t=C(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 U="\n".charCodeAt(0);async function H(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,U)}var L=r(113),j=r.n(L),N=r(747);const F=require("tls");var q=r.n(F);class K extends Error{constructor(e){super("Operation Timed Out"),this.promise=e}}function $(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 G(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function W(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 z="a=control:";function Q(e){const t=e.find((e=>e.startsWith(z)))?.substring(z.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase(),i=function(e){return{type:e.split(" ")[0].substring(2),payloadTypes:G(e)}}(e[0]);let s,n;r?.includes("mpeg4")?s="aac":r?.includes("opus")?s="opus":r?.includes("pcma")?s="pcm_alaw":r?.includes("pcmu")?s="pcm_ulaw":r?.includes("pcm")?s="pcm":r?.includes("h264")?s="h264":r?.includes("h265")?s="h265":r||"audio"!==i.type||(s="pcm_alaw");for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){n=t;break}}return{...i,fmtp:W(e),lines:e,contents:e.join("\r\n"),control:t,codec:s,direction:n}}function J(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(Q),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}async function Y(e){await new Promise((t=>setTimeout(t,e)))}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 H(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;needKeepAlive=!1;setupOptions=new Map;issuedTeardown=!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)}async safeTeardown(){if(!this.issuedTeardown){this.issuedTeardown=!0;try{this.writeTeardown(),await Y(500)}catch(e){}finally{this.client.destroy()}}}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 received invalid frame magic. This may be a bug in your camera firmware. If this error persists, switch your RTSP Parser to FFmpeg or Scrypted (UDP): "+e.toString());const t=e.readUInt8(1),r=e.readUInt16BE(2),i=await V(this.client,r);this.setupOptions.get(t)?.onRtp?.(e,i)}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),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 ${N.rh.computeHash(t)}`}const r=N.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=j().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=j().createHash("md5").update(`${e}:${o}`).digest("hex"),d=j().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 K(a))),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){const t={Transport:`RTP/AVP/${"udp"===e.type?"UDP":"TCP"};unicast;${"udp"===e.type?"client_port":"interleaved"}=${e.port}-${e.port+1}`},r=await this.request("SETUP",t,e.path);let i;if(r.headers.session){const e=ee(r.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=r.headers.session.split(";")[0]}if(r.headers.transport){const e=r.headers.transport.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const[t,r,s]=e;r&&s&&(i={begin:parseInt(r),end:parseInt(s)})}}return"tcp"===e.type&&this.setupOptions.set(i.begin,e),Object.assign({interleaved:i},r)}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,L.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await H(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=J(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(e,t){const r=this.settings[e];let i;return r&&(i=this.getItemInternal(e,r)),this.putSettingInternal(r,i,e,t)}putSettingInternal(t,r,i,s){t?.noStore||(t.mapPut&&(s=t.mapPut(r,s)),"object"==typeof s?this.device.storage.setItem(i,JSON.stringify(s)):this.device.storage.setItem(i,s?.toString())),t?.onPut?.(r,s),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItemInternal(e,t){if(!t)return this.device.storage.getItem(e);const r=function(e,t,r){const 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,(()=>t.persistedDefaultValue?(this.putSettingInternal(t,void 0,e,t.persistedDefaultValue),t.persistedDefaultValue):t.defaultValue));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)}}const{mediaManager:ae}=t();async function*ce(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 de=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function ue(e){return e}function le(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...de],async*parse(e){const t=ce(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:ue}}var pe=r(415);const me=require("stream");function he(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)}}function fe(e,t,r,i,s){const n=J(e),o=C(t)||{id:void 0,name:void 0};if(void 0===o.video&&(o.video={}),null===s?.video&&(o.video=null),o.video&&(o.video.codec=r),s?.audio?.codec&&s?.audio?.codec!==i){if(n.msections.find((e=>"audio"===e.type&&e.codec===s?.audio?.codec)))return o.audio={codec:s?.audio?.codec},o}return o.audio=o?.audio?.codec===i?t?.audio:{codec:i},o}function ge(e,t,r,i,s){let n=!0;const o=new me.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=J(r),c=a.msections.find((e=>"audio"===e.type)),d=a.msections.find((e=>"video"===e.type)),u=c?.payloadTypes?.[0],l=d?.payloadTypes?.[0],p=c?.codec,m=d.codec;let h;const f=new Promise((e=>{h=e})),g=e=>{n&&(o.emit("killed"),o.emit("error",e||new Error("killed"))),n=!1,h(),t.destroy()};t.on("close",(()=>{g(new Error("rfc4751 socket closed"))})),t.on("error",(e=>{g(e)}));const{resetActivityTimer:y}=E("rtsp",g,o,s?.timeout);let v;const b=d?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],_=b?.split(",")?.[0];if(_)try{const t=Buffer.from(_,"base64"),r=(0,pe.Qc)(t);v=he(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{await Y(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:2,skipHeader:void 0,callback:(t,r)=>{let i;const s=127&r[1],n=Buffer.alloc(2);n[0]=36,s===u?n[1]=0:s===l&&(n[1]=2),t=Buffer.concat([n,t]),s===u?i=p:s===l&&(i=m);const a={chunks:[t,r],type:i};if(!v){const t=X(a,7);if(t)try{const r=(0,pe.Qc)(t);v=he(r),e.log(v),e.log("parsed bitstream sps",r)}catch(t){e.warn("sps parsing failed"),v={width:NaN,height:NaN}}}o.emit("rtsp",a),y()}})})().catch((e=>{throw e})).finally((()=>{g(new Error("parser exited"))})),{sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:p,inputVideoCodec:m,get inputVideoResolution(){return v},get isActive(){return n},kill(e){g(e)},killed:f,resetActivityTimer:y,negotiateMediaStream:e=>fe(r,i,m,p,e),emit(e,t){return o.emit(e,t),this},on(e,t){return o.on(e,t),this},once(e,t){return o.once(e,t),this},removeListener(e,t){return o.removeListener(e,t),this}}}const{deviceManager:ye}=t(),Se="transcode",ve="mixin:@scrypted/prebuffer-mixin";function be(){if(!ye.getNativeIds().includes(Se))return;return ye.getDeviceState(Se)?.id}class _e extends e.ScryptedDeviceBase{constructor(e){super(Se),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(ve))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 we(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function Pe(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):we(t)}function xe(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},videoFilterArguments:{group:"Transcoding",title:"Video Filter Arguments",description:"FFmpeg arguments used to filter input video when transcoding a stream. This can be used to crops, scale, rotates, etc.",placeholder:"transpose=1",hide:!0}});function i(e,t){const i=Pe(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(be())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:we(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:Te,log:Me,systemManager:Ie,deviceManager:Oe}=t(),De="Default",Ce="AAC or No Audio",Ae=`${Ce} (Copy)`,Re="Compatible Audio",Ee="Other Audio",ke=["aac","mp3","mp2","opus"],Be="-fflags +genpts",Ve="Scrypted (TCP)",Ue="Scrypted (UDP)",He="FFmpeg (TCP)",Le="FFmpeg (UDP)",je="Default",Ne=[Ce,Re,Ee],Fe=["mpegts","mp4","rtsp"];class qe{prebuffers={mp4:[],mpegts:[],rtsp:[]};usingScryptedParser=!1;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=j().randomBytes(8).toString("hex"),this.storage.setItem(i,this.rtspServerPath))}get canPrebuffer(){return"rawvideo"!==this.advertisedMediaStreamOptions.container&&"ffmpeg"!==this.advertisedMediaStreamOptions.container}getLastH264Probe(){const e=this.storage.getItem(this.lastH264ProbeKey);if(!e)return{};try{return JSON.parse(e)}catch(e){return{}}}getLastH264Oddities(){const e=this.getLastH264Probe();return e.fuab||e.mtap16||e.mtap32||e.sei||e.stapb}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(){for(const e of Fe)this.prebuffers[e]=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((e=>this.parserSessionPromise=void 0)),this.parserSessionPromise.then((e=>e.killed.finally((()=>this.parserSessionPromise=void 0)))))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";Ne.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),r=-1!==e.indexOf(Re),i=-1!==e.indexOf(Ee);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===He&&(r=He),i===Le&&(r=Le),e&&!r&&(i&&i!==je||(r=Ve),i===Ve&&(r=Ve),i===Ue&&(r=Ue)),r||(r=He)):r=je,{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}}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:[je,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||je});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)||De,choices:[De,Ae,"Compatible Audio (Copy)","Other Audio (Transcode)"]})},l=()=>{t.push({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:Be,choices:[Be,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Oddities()?Ve:He,i=e?[Ve,Ue]:[],s=this.storage.getItem(this.rtspParserKey)||je;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:s,choices:[je,...i,He,Le]}),(s===je?r:s).includes("Scrypted")||(p=!0)}n&&u(),p&&l();const m=()=>{t.push({key:"detectedOddities",group:d,title:"Detected H264 Oddities",readonly:!0,value:JSON.stringify(this.getLastH264Probe()),description:"Cameras with oddities in the H264 video stream may not function correctly with Scrypted RTSP Parsers or Senders."})};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"}),m()}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}),m();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.clearPrebuffers();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=!ke.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Me.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 w=["-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,L.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){let t,r={};const i=()=>e.slice(t);for(let i=0;i<e.length;i++){const s=e[i];"h264"===s.type?X(s,7)&&(t=i):r[s.type]=s}if(void 0!==t)return i();r={};for(let i=0;i<e.length;i++){const s=e[i];"h264"===s.type?X(s,5)&&(t=i):r[s.type]=s}return void 0!==t?i():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:w,acodec:r?y:["-acodec","copy"]});this.sdp=e.sdp,P.parsers.rtsp=e}else P.parsers.mpegts={container:"mpegts",outputArguments:[...(T={vcodec:w,acodec:y})?.vcodec||[],...T?.acodec||[],"-f","mpegts"],parse:(M=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<M)continue;const s=Buffer.concat(t);I?.(s);const n=s.length%M,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 T,M,I;d&&(P.parsers.mp4=le({vcodec:w,acodec:y}));const O=await this.mixinDevice.getVideoStream(t),D="x-scrypted/x-rfc4571"===O.mimeType;let C,A;this.storage.removeItem(this.lastDetectedAudioCodecKey),this.usingScryptedParser=!1;const R=this.getLastH264Oddities();if(c&&D){this.usingScryptedParser=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Te.convertMediaObjectToJSON(O,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;C=ge(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=C.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Te.convertMediaObjectToBuffer(O,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());A=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,A);if(this.usingScryptedParser=s===Ve||s===Ue,this.usingScryptedParser)C=await async function(e,t,r,i){let s=!0;const n=new me.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new te(t,e);a.requestTimeout=i.rtspRequestTimeout;const c=()=>{for(const e of o)_(e);a.safeTeardown()};let d;const u=new Promise((e=>{d=e})),l=e=>{s&&(n.emit("killed"),n.emit("error",e||new Error("killed"))),s=!1,d(),c()};a.client.on("close",(()=>{l(new Error("rtsp socket closed"))})),a.client.on("error",(e=>{l(e)}));const{resetActivityTimer:p}=E("rtsp",l,n,i?.rtspRequestTimeout);try{await a.options();const t=await a.describe(),c=t.headers["content-base"];if(c){const e=new URL(c),t=new URL(a.url);e.username=t.username,e.password=t.password,a.url=e.toString()}let d=t.body.toString().trim();e.log("sdp",d);const m=J(d);let h=0;const f={};let g;const{useUdp:y}=i,S=e=>{if(y&&e.session&&!g){const t=ee(e.session);g=parseInt(t.timeout)}},v=async(e,t)=>{let r,i=h;if(y){const s=h,{port:c,server:d}=await x();r=d,o.push(d),i=c,d.on("message",(e=>{const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(s,1),r.writeUInt16BE(e.length,2);const i={chunks:[r,e],type:t};n.emit("rtsp",i),p?.()}));const u=await a.setup({path:e,type:"udp",port:i});S(u.headers);const l=Buffer.alloc(1),m=u.headers.transport.match(/.*?server_port=([0-9]+)-([0-9]+)/),[g,y,v]=m,{hostname:b}=new URL(a.url);r.send(l,parseInt(y),b),f[h]=t}else{const r=await a.setup({path:e,type:"tcp",port:i,onRtp:(e,r)=>{const i={chunks:[e,r],type:t};n.emit("rtsp",i),p?.()}});r.interleaved?f[r.interleaved.begin]=t:f[h]=t}h+=2};let b=!1;m.msections=m.msections.filter((t=>{if("video"===t.type){if(b)return e.warn("additional video section found. skipping."),!1;b=!0}else{if("audio"!==t.type)return e.warn("unknown section",t.type),!1;if(i.audioSoftMuted)return!1}return!0}));for(const e of m.msections)await v(e.control,e.codec);return d=[...m.header.lines,...m.msections.map((e=>e.lines)).flat()].join("\r\n"),await a.play(),process.nextTick((()=>a.readLoop().catch((e=>l(e))).finally((()=>l(new Error("rtsp read loop exited")))))),(()=>{const t=m.msections.find((e=>"audio"===e.type)),i=m.msections.find((e=>"video"===e.type)),o=t?.codec,a=i.codec;let c;const h=i?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],f=h?.split(",")?.[0];if(f)try{const t=Buffer.from(f,"base64"),r=(0,pe.Qc)(t);c=he(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return{sdp:Promise.resolve([Buffer.from(d)]),inputAudioCodec:o,inputVideoCodec:a,get inputVideoResolution(){return c},get isActive(){return s},kill(e){l(e)},killed:u,resetActivityTimer:p,negotiateMediaStream:e=>fe(d,r,a,o,e),emit(e,t){return n.emit(e,t),this},on(e,t){return n.on(e,t),this},once(e,t){return n.once(e,t),this},removeListener(e,t){return n.removeListener(e,t),this}}})()}catch(e){throw c(),e}}(this.console,i.url,i.mediaStreamOptions,{useUdp:s===Ue,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=C.sdp.then((e=>Buffer.concat(e).toString()));else{s===Le?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===He&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Be;i.inputArguments.unshift(...e.split(" ")),C=await k(i,P)}}if(this.usingScryptedParser){const e={};let t=!1;const r=r=>{if("h264"!==r.type)return;const i=function(e){const t=new Set;if("h264"!==e.type)return t;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;const s=31&r[e];t.add(s),e+=i}}else if(28===i){const e=31&r[1];t.add(e)}else t.add(i);return t}(r);e.fuab||=i.has(29),e.stapb||=i.has(25),e.mtap16||=i.has(26),e.mtap32||=i.has(27),e.sei||=i.has(6);if((e.fuab||e.stapb||e.mtap16||e.mtap32||e.sei)&&!t){t=!0;let{isDefault:e}=this.getParser(c,A);if(this.console.warn("H264 oddity detected."),!e)return void this.console.warn("If there are issues streaming, consider using the Default parser.");if("scrypted"===A.tool)return void this.console.warn('Stream tool is marked safe as "scrypted", ignoring oddity. If there are issues streaming, consider switching to FFmpeg parser.')}},i=()=>C.removeListener("rtsp",r);C.killed.finally((()=>clearTimeout(s))),C.on("rtsp",r);const s=setTimeout((()=>{i(),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e))}),R?6e4:1e4)}!r&&i&&void 0!==C.inputAudioCodec&&C.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const B=t?.video?.codec;if(B&&void 0!==C.inputVideoCodec&&C.inputVideoCodec!==B&&this.console.warn("Video codec plugin reported vs detected mismatch",B,C.inputVideoCodec),C.inputAudioCodec?ke.includes(C.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",C.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",C.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,C.inputAudioCodec||"null"),"h264"!==C.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."),C.kill(new Error("audio probe completed, restarting")),this.startPrebufferSession();if(this.parserSession=C,C.killed.finally((()=>{this.parserSession===C&&(this.parserSession=void 0)})),C.killed.finally((()=>clearTimeout(this.inactivityTimeout))),Oe.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),A?.refreshAt){let t,r=A;const i=async()=>{if(!C.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await Te.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),C.killed.finally((()=>clearTimeout(t)))}for(const e of Fe){let t=0,r=this.prebuffers[e];C.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 C}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(new Error("stream inactivity")))}),3e4))))}async handleRebroadcasterClient(e){const{isActiveClient:t,container:r,session:i,socketPromise:s,requestedPrebuffer:n}=e;n&&this.console.log("sending prebuffer",n),s.catch((()=>this.inactivityCheck(i,!1))),s.then((e=>{t&&(this.activeClients++,this.printActiveClients()),e.once("close",(()=>{t&&(this.activeClients--,this.printActiveClients()),this.inactivityCheck(i,t)}))})),B(s,{connect:(t,s)=>{const o=Date.now(),a=(r,i)=>{if(e.filter&&!(r=e.filter(r,i)))return;t(r)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{i.removeListener(r,a),i.removeListener("killed",c),s()};i.on(r,a),i.once("killed",c);const d=this.prebuffers[r];if("rtsp"===r&&e.findSyncFrame){const e=this.parsers[r],t=d.filter((e=>e.time>=o-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)a(e,!0)}else for(const e of d)e.time<o-n||a(e,!0);return c}})}async getVideoStream(e,t){if(!1===t?.refresh&&!this.parserSessionPromise)throw new Error("Stream is currently unavailable and will not be started for this request. RequestMediaStreamOptions.refresh === false");this.ensurePrebufferSession();const r=await this.parserSessionPromise;let i=t?.prebuffer;null==i&&(i=Math.max(4e3,this.getDetectedIdrInterval()||4e3));const{rtspMode:s,muxingMp4:n}=this.getRebroadcastContainer(),o=s?"rtsp":"mpegts";let a=this.parsers[t?.container]?t?.container:o;const c=r.negotiateMediaStream(t);let d,u,l,p=await this.sdp;!c.video?.h264Info&&this.usingScryptedParser&&(c.video.h264Info=this.getLastH264Probe());const m=new Map;if("rtsp"===a){const e=J(p),r=e.msections.find((e=>e.codec&&e.codec===c.video?.codec))||e.msections.find((e=>"video"===e.type));let i=e.msections.find((e=>e.codec&&e.codec===c.audio?.codec))||e.msections.find((e=>"audio"===e.type));null===c.audio&&(i=void 0),e.msections=e.msections.filter((e=>e===r||e===i));const s=void 0===t?.prebuffer,n=e.msections.find((e=>"video"===e.type))?.codec;p=e.toSdp(),l=(e,t)=>{const r=m.get(e.type);if(null==r)return;if(t&&s&&e.type!==n)return;const i=e.chunks.slice(),o=Buffer.from(i[0]);return o.writeUInt8(r,1),i[0]=o,{startStream:e.startStream,chunks:i}};const o=await M();d=o.clientPromise.then((async e=>{p=$(p);const t=new re(e,p);await t.handlePlayback();for(const e of Object.values(t.setupTracks))m.set(e.codec,e.destination),m.set(`rtcp-${e.codec}`,e.destination+1);return e})),u=o.url.replace("tcp://","rtsp://")}else{const e=await M();d=e.clientPromise,u=`tcp://127.0.0.1:${e.port}`}c.sdp=p;const h=!1!==t?.refresh;this.handleRebroadcasterClient({findSyncFrame:e,isActiveClient:h,container:a,requestedPrebuffer:i,socketPromise:d,session:r,filter:l}),c.prebuffer=i;const{reencodeAudio:f}=this.getAudioConfig();this.audioDisabled?c.audio=null:f&&n&&(c.audio={codec:"aac",encoder:"aac",profile:"aac_low"}),r.inputVideoResolution?.width&&r.inputVideoResolution?.height&&c.video&&Object.assign(c.video,r.inputVideoResolution);const g=Date.now();let y=0;const S=this.prebuffers[a];for(const e of S)if(!(e.time<g-i))for(const t of e.chunks)y+=t.length;return{url:u,container:a,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,y).toString(),...this.parsers[a].inputArguments||[],"-f",this.parsers[a].container,"-i",u],mediaStreamOptions:c}}}class Ke extends oe{released=!1;sessions=new Map;streamSettings=xe(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(t){if(t?.directMediaStream)return this.mixinDevice.getVideoStream(t);await this.ensurePrebufferSessions();let r,i,s=t?.id;const n=this.mixins?.includes(be()),o=await this.mixinDevice.getVideoStreamOptions();let a;const c=2e6;if(!s){switch(t?.destination){case"medium-resolution":case"remote":a=this.streamSettings.getRemoteStream(o),i=this.plugin.transcodeStorageSettings.values.remoteStreamingBitrate;break;case"low-resolution":a=this.streamSettings.getLowResolutionStream(o),i=512e3;break;case"local-recorder":a=this.streamSettings.getRecordingStream(o),i=c;break;case"remote-recorder":a=this.streamSettings.getRemoteRecordingStream(o),i=c;break;default:a=this.streamSettings.getDefaultStream(o),i=c}s=a.stream.id,this.console.log("Selected stream",a.stream.name),n&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(a.title)&&(r=this.plugin.transcodeStorageSettings.values.h264EncoderArguments?.split(" "))}let d,u=this.sessions.get(s);if(u.canPrebuffer||(this.console.log("Source container can not be prebuffered. Using a direct media stream."),u=void 0),u){const e=!(n||t?.container&&"rtsp"!==t?.container||"ffmpeg"===t?.tool);d=await u.getVideoStream(e,t)}else{const r=await this.mixinDevice.getVideoStream(t);if(!n)return r;d=await Te.convertMediaObjectToJSON(r,e.ScryptedMimeTypes.FFmpegInput)}if(d.h264EncoderArguments=r,d.destinationVideoBitrate=i,n&&this.streamSettings.storageSettings.values.missingCodecParameters&&(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.push("-bsf:v","dump_extra")),n&&this.streamSettings.storageSettings.values.videoFilterArguments)if(d.h264FilterArguments=d.h264FilterArguments||[],d.h264FilterArguments.length){const e=d.h264FilterArguments?.findIndex((e=>"-filter_complex"===e));void 0!==e&&-1!==e?d.h264FilterArguments[e+1]=d.h264FilterArguments[e+1]+`[prefilter] ; [prefilter] ${this.streamSettings.storageSettings.values.videoFilterArguments}`:d.h264FilterArguments.push("-filter_complex",this.streamSettings.storageSettings.values.videoFilterArguments)}else d.h264FilterArguments.push("-filter_complex",this.streamSettings.storageSettings.values.videoFilterArguments);return n&&(d.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),Te.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"),Me.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);i.length||(this.online=!0);let o=0;for(const e of s){let r=this.sessions.get(e);if(r)continue;const s=t?.find((t=>t.id===e));s?.prebuffer&&Me.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);r=new qe(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."):c?(async()=>{for(;this.sessions.get(e)===r&&!this.released;){r.ensurePrebufferSession();let e=!1;try{this.console.log("prebuffer session starting");const t=await r.parserSessionPromise;this.console.log("prebuffer session started"),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)")})():this.console.log("stream",a,"will be rebroadcast on demand.")}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?")}Oe.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(new Error("rebroadcast settings changed"))));this.ensurePrebufferSessions()}getPrebufferedStreams(e){return Pe(this.streamSettings.storageSettings,e)}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getPrebufferedStreams(e);for(const r of e){const e=this.sessions.get(r.id);(e?.parserSession||t.includes(r))&&(r.prebuffer=1e4),e&&!r.video?.h264Info&&(r.video.h264Info=e.getLastH264Probe())}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(new Error("rebroadcast disabled")),e.clearPrebuffers()})))}}class $e 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(Ie.getSystemState())){const t=Ie.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const 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((()=>Oe.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{Oe.onDeviceDiscovered({nativeId:Se,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===Se)return new _e(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){_(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=Math.max(4e3,t.getDetectedIdrInterval()||4e3);t.handleRebroadcasterClient({findSyncFrame:!0,isActiveClient:!0,container:"rtsp",session:i,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await r.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,r){const i=JSON.parse(e.toString()),{url:s,sdp:n}=i,o=J(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 M(),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,ve];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new Ke(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 Ge=new $e})();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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -4,12 +4,12 @@ import { getH264EncoderArgs, LIBX264_ENCODER_TITLE } from '@scrypted/common/src/
4
4
  import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
5
5
  import { closeQuiet, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
6
6
  import { readLength } from '@scrypted/common/src/read-stream';
7
- import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, H264_NAL_TYPE_SEI, RtspServer } from '@scrypted/common/src/rtsp-server';
7
+ import { createRtspParser, findH264NaluType, getNaluTypes, H264_NAL_TYPE_FU_B, H264_NAL_TYPE_IDR, H264_NAL_TYPE_MTAP16, H264_NAL_TYPE_MTAP32, H264_NAL_TYPE_SEI, H264_NAL_TYPE_STAP_B, RtspServer } from '@scrypted/common/src/rtsp-server';
8
8
  import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
9
9
  import { StorageSettings } from '@scrypted/common/src/settings';
10
10
  import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
11
11
  import { createFragmentedMp4Parser, createMpegTsParser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
12
- import sdk, { BufferConverter, DeviceProvider, FFmpegInput, MediaObject, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
12
+ import sdk, { BufferConverter, DeviceProvider, FFmpegInput, H264Info, MediaObject, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
13
13
  import crypto from 'crypto';
14
14
  import net from 'net';
15
15
  import { Duplex } from 'stream';
@@ -54,10 +54,6 @@ type Prebuffers<T extends string> = {
54
54
  type PrebufferParsers = 'mpegts' | 'mp4' | 'rtsp';
55
55
  const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 'rtsp'];
56
56
 
57
- interface H264Probe {
58
- seiDetected?: boolean;
59
- }
60
-
61
57
  class PrebufferSession {
62
58
 
63
59
  parserSessionPromise: Promise<ParserSession<PrebufferParsers>>;
@@ -69,6 +65,7 @@ class PrebufferSession {
69
65
  };
70
66
  parsers: { [container: string]: StreamParser };
71
67
  sdp: Promise<string>;
68
+ usingScryptedParser = false;
72
69
 
73
70
  audioDisabled = false;
74
71
 
@@ -112,7 +109,7 @@ class PrebufferSession {
112
109
  return this.advertisedMediaStreamOptions.container !== 'rawvideo' && this.advertisedMediaStreamOptions.container !== 'ffmpeg';
113
110
  }
114
111
 
115
- getLastH264Probe(): H264Probe {
112
+ getLastH264Probe(): H264Info {
116
113
  const str = this.storage.getItem(this.lastH264ProbeKey);
117
114
  if (!str) {
118
115
  return {};
@@ -126,6 +123,16 @@ class PrebufferSession {
126
123
  }
127
124
  }
128
125
 
126
+ getLastH264Oddities() {
127
+ const lastProbe = this.getLastH264Probe();
128
+ const h264Oddities = lastProbe.fuab
129
+ || lastProbe.mtap16
130
+ || lastProbe.mtap32
131
+ || lastProbe.sei
132
+ || lastProbe.stapb;
133
+ return h264Oddities;
134
+ }
135
+
129
136
  getDetectedIdrInterval() {
130
137
  const durations: number[] = [];
131
138
  if (this.prebuffers.mp4.length) {
@@ -350,7 +357,7 @@ class PrebufferSession {
350
357
 
351
358
  if (this.canUseRtspParser(this.advertisedMediaStreamOptions)) {
352
359
  const canUseScryptedParser = rtspMode;
353
- const defaultValue = canUseScryptedParser && !this.getLastH264Probe()?.seiDetected ?
360
+ const defaultValue = canUseScryptedParser && !this.getLastH264Oddities() ?
354
361
  SCRYPTED_PARSER_TCP : FFMPEG_PARSER_TCP;
355
362
 
356
363
  const scryptedOptions = canUseScryptedParser ? [
@@ -388,6 +395,19 @@ class PrebufferSession {
388
395
  addFFmpegInputSettings();
389
396
  }
390
397
 
398
+ const addOddities = () => {
399
+ settings.push(
400
+ {
401
+ key: 'detectedOddities',
402
+ group,
403
+ title: 'Detected H264 Oddities',
404
+ readonly: true,
405
+ value: JSON.stringify(this.getLastH264Probe()),
406
+ description: 'Cameras with oddities in the H264 video stream may not function correctly with Scrypted RTSP Parsers or Senders.',
407
+ }
408
+ )
409
+ };
410
+
391
411
  if (session) {
392
412
  const resolution = session.inputVideoResolution?.width && session.inputVideoResolution?.height
393
413
  ? `${session.inputVideoResolution?.width}x${session.inputVideoResolution?.height}`
@@ -419,15 +439,8 @@ class PrebufferSession {
419
439
  readonly: true,
420
440
  value: (idrInterval || 0) / 1000 || 'unknown',
421
441
  },
422
- {
423
- key: 'detectedSEI',
424
- group,
425
- title: 'Detected H264 SEI',
426
- readonly: true,
427
- value: `${!!this.getLastH264Probe().seiDetected}`,
428
- description: 'Cameras with SEI in the H264 video stream may not function correctly with Scrypted RTSP Parsers.',
429
- }
430
442
  );
443
+ addOddities();
431
444
  }
432
445
  else {
433
446
  settings.push(
@@ -439,7 +452,8 @@ class PrebufferSession {
439
452
  value: 'Idle',
440
453
  readonly: true,
441
454
  },
442
- )
455
+ );
456
+ addOddities();
443
457
  }
444
458
 
445
459
  if (rtspMode) {
@@ -664,11 +678,12 @@ class PrebufferSession {
664
678
  // before launching the parser session, clear out the last detected codec.
665
679
  // an erroneous cached codec could cause ffmpeg to fail to start.
666
680
  this.storage.removeItem(this.lastDetectedAudioCodecKey);
667
- let usingScryptedParser = false;
668
- let restartOnSei = false;
681
+ this.usingScryptedParser = false;
682
+
683
+ const h264Oddities = this.getLastH264Oddities();
669
684
 
670
685
  if (rtspMode && isRfc4571) {
671
- usingScryptedParser = true;
686
+ this.usingScryptedParser = true;
672
687
  this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
673
688
  const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
674
689
  const { url, sdp, mediaStreamOptions } = json;
@@ -682,24 +697,20 @@ class PrebufferSession {
682
697
  sessionMso = ffmpegInput.mediaStreamOptions || this.advertisedMediaStreamOptions;
683
698
 
684
699
  let { parser, isDefault } = this.getParser(rtspMode, sessionMso);
685
- usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
686
-
687
- // if the stream is not marked as scrypted parser compatible by the plugin,
688
- // play it safe and restart the stream when an sei packet is detected.
689
- restartOnSei = usingScryptedParser && isDefault && sessionMso.tool !== 'scrypted';
690
-
691
- if (isDefault && usingScryptedParser && this.getLastH264Probe().seiDetected) {
692
- if (sessionMso.tool === 'scrypted') {
693
- this.console.warn('SEI packet detected was in video stream, but stream is marked safe by Scrypted. The Default Scrypted RTSP Parser will be used. This can be overriden by setting the RTSP Parser to Scrypted.');
694
- }
695
- else {
696
- 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.');
697
- usingScryptedParser = false;
698
- parser = FFMPEG_PARSER_TCP;
699
- }
700
- }
701
-
702
- if (usingScryptedParser) {
700
+ this.usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
701
+
702
+ // if (isDefault && this.usingScryptedParser && h264Oddities) {
703
+ // if (sessionMso.tool === 'scrypted') {
704
+ // this.console.warn('H264 oddities were detected in video stream, but stream is marked safe by Scrypted. The Default Scrypted RTSP Parser will be used. This can be overriden by setting the RTSP Parser to Scrypted.');
705
+ // }
706
+ // else {
707
+ // this.console.warn('H264 oddities were detected 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.');
708
+ // this.usingScryptedParser = false;
709
+ // parser = FFMPEG_PARSER_TCP;
710
+ // }
711
+ // }
712
+
713
+ if (this.usingScryptedParser) {
703
714
  session = await startRtspSession(this.console, ffmpegInput.url, ffmpegInput.mediaStreamOptions, {
704
715
  useUdp: parser === SCRYPTED_PARSER_UDP,
705
716
  audioSoftMuted,
@@ -719,43 +730,58 @@ class PrebufferSession {
719
730
  }
720
731
  }
721
732
 
722
- // watch the stream for 10 seconds to see if an sei packet is encountered.
723
- // if one is found and using scrypted parser as default, will need to restart rebroadcast to prevent
724
- // downstream issues.
725
- const seiProbe = (chunk: StreamChunk) => {
726
- if (chunk.type !== 'h264')
727
- return;
728
- const seiDetected = !!findH264NaluType(chunk, H264_NAL_TYPE_SEI);
729
- if (seiDetected) {
730
- removeSeiProbe();
731
- clearTimeout(seiTimeout);
732
- const h264Probe: H264Probe = {
733
- seiDetected,
734
- }
735
- this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
736
- if (!usingScryptedParser)
733
+ if (this.usingScryptedParser) {
734
+ // watch the stream for 10 seconds to see if an weird nalu is encountered.
735
+ // if one is found and using scrypted parser as default, will need to restart rebroadcast to prevent
736
+ // downstream issues.
737
+ const h264Probe: H264Info = {};
738
+ let reportedOddity = false;
739
+ const oddityProbe = (chunk: StreamChunk) => {
740
+ if (chunk.type !== 'h264')
737
741
  return;
738
742
 
739
- let { isDefault } = this.getParser(rtspMode, sessionMso);
740
- if (!isDefault || sessionMso.tool === 'scrypted') {
741
- this.console.warn('SEI packet detected while operating with Scrypted Parser. If there are issues streaming, consider using the Default parser.');
742
- return;
743
+ const types = getNaluTypes(chunk);
744
+ h264Probe.fuab ||= types.has(H264_NAL_TYPE_FU_B);
745
+ h264Probe.stapb ||= types.has(H264_NAL_TYPE_STAP_B);
746
+ h264Probe.mtap16 ||= types.has(H264_NAL_TYPE_MTAP16);
747
+ h264Probe.mtap32 ||= types.has(H264_NAL_TYPE_MTAP32);
748
+ h264Probe.sei ||= types.has(H264_NAL_TYPE_SEI);
749
+ const oddity = h264Probe.fuab || h264Probe.stapb || h264Probe.mtap16 || h264Probe.mtap32 || h264Probe.sei;
750
+ if (oddity && !reportedOddity) {
751
+ reportedOddity = true;
752
+ let { isDefault } = this.getParser(rtspMode, sessionMso);
753
+ this.console.warn('H264 oddity detected.');
754
+ if (!isDefault) {
755
+ this.console.warn('If there are issues streaming, consider using the Default parser.');
756
+ return;
757
+ }
758
+
759
+ if (sessionMso.tool === 'scrypted') {
760
+ this.console.warn('Stream tool is marked safe as "scrypted", ignoring oddity. If there are issues streaming, consider switching to FFmpeg parser.');
761
+ return;
762
+ }
763
+
764
+ // if (!this.inactivityTimeout) {
765
+ // this.console.warn('Oddity in prebuffered stream. Restarting rebroadcast to use FFmpeg instead.');
766
+ // session.kill(new Error('restarting due to H264 oddity detection'));
767
+ // this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
768
+ // removeOddityProbe();
769
+ // this.startPrebufferSession();
770
+ // return;
771
+ // }
772
+
773
+ // this.console.warn('Oddity in non prebuffered stream. Next restart will use FFmpeg instead.');
743
774
  }
744
- this.console.warn('SEI packet detected while operating with Scrypted Parser as default. Restarting rebroadcast.');
745
- session.kill(new Error('restarting due to SEI packet detection'));
746
- this.startPrebufferSession();
747
775
  }
776
+ const removeOddityProbe = () => session.removeListener('rtsp', oddityProbe);
777
+ session.killed.finally(() => clearTimeout(oddityTimeout));
778
+ session.on('rtsp', oddityProbe);
779
+ const oddityTimeout = setTimeout(() => {
780
+ removeOddityProbe();
781
+ this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
782
+ }, h264Oddities ? 60000 : 10000);
783
+
748
784
  }
749
- const removeSeiProbe = () => session.removeListener('rtsp', seiProbe);
750
- session.killed.finally(() => clearTimeout(seiTimeout));
751
- session.on('rtsp', seiProbe);
752
- const seiTimeout = setTimeout(() => {
753
- removeSeiProbe();
754
- const h264Probe: H264Probe = {
755
- seiDetected: false,
756
- }
757
- this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
758
- }, this.getLastH264Probe().seiDetected ? 60000 : 10000);
759
785
 
760
786
  // complain to the user about the codec if necessary. upstream may send a audio
761
787
  // stream but report none exists (to request muting).
@@ -798,9 +824,7 @@ class PrebufferSession {
798
824
  if (this.parserSession === session)
799
825
  this.parserSession = undefined;
800
826
  });
801
- session.killed.finally(() => {
802
- clearTimeout(this.inactivityTimeout)
803
- });
827
+ session.killed.finally(() => clearTimeout(this.inactivityTimeout));
804
828
 
805
829
  // settings ui refresh
806
830
  deviceManager.onMixinEvent(this.mixin.id, this.mixin.mixinProviderNativeId, ScryptedInterface.Settings, undefined);
@@ -1002,6 +1026,8 @@ class PrebufferSession {
1002
1026
 
1003
1027
  const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options);
1004
1028
  let sdp = await this.sdp;
1029
+ if (!mediaStreamOptions.video?.h264Info && this.usingScryptedParser)
1030
+ mediaStreamOptions.video.h264Info = this.getLastH264Probe();
1005
1031
 
1006
1032
  let socketPromise: Promise<Duplex>;
1007
1033
  let url: string;
@@ -1391,8 +1417,11 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
1391
1417
  let enabledStreams = this.getPrebufferedStreams(ret);
1392
1418
 
1393
1419
  for (const mso of ret) {
1394
- if (this.sessions.get(mso.id)?.parserSession || enabledStreams.includes(mso))
1420
+ const session = this.sessions.get(mso.id);
1421
+ if (session?.parserSession || enabledStreams.includes(mso))
1395
1422
  mso.prebuffer = prebufferDurationMs;
1423
+ if (session && !mso.video?.h264Info)
1424
+ mso.video.h264Info = session.getLastH264Probe();
1396
1425
  }
1397
1426
 
1398
1427
  return ret;