@scrypted/prebuffer-mixin 0.9.11 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +21 -5
- package/src/rfc4571.ts +11 -11
- package/src/rtsp-session.ts +22 -25
package/dist/main.nodejs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{var e={747:(e,t,r)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return n.default}});var i=a(r(257)),s=a(r(816)),n=a(r(243)),o=a(r(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},n=r(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:r}=c.decodeHash(e);return{hash:e,username:t,password:r}},buildAuthorizationRest:function({hash:e,username:t,password:r}){if(t&&r)return c.computeHash({username:t,password:r});if(!e)throw new s.default("E_NO_HASH");return e},computeHash:function({username:e,password:t}){return Buffer.from(e+":"+t).toString("base64")},decodeHash:function(e){const[t,...r]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:r.join(":")}}};var d=c;t.default=d},828:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=(i=r(257))&&i.__esModule?i:{default:i},n=r(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var i,s=r(761),n=(i=r(113))&&i.__esModule?i:{default:i};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const r=n.default.createHash(e);return r.update(t),r.digest("hex")}var l={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),r=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,r].join(":"))}};t.default=l},761:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){const i=e.trim().match(n);if(!i)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=i.map(((e,t)=>{const r=e.split("=");if(2!==r.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,r.length);return r})).reduce((function(e,[r,i],n){if(-1===t.indexOf(r))throw new s.default("E_UNAUTHORIZED_KEY",n,r);return e[r]=i.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(r,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,r=[]){return o(r,e),t.reduce((function(t,r){return e[r]?t+(t?", ":"")+r+'="'+e[r]+'"':t}),"")};var i,s=(i=r(257))&&i.__esModule?i:{default:i};const n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},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,C=t.ExpGolomb()+1,D=t.readBit();let A=0;D||(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:C,frame_mbs_only_flag:D,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.co2ppm="co2ppm",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"]},CO2Sensor:{name:"CO2Sensor",methods:[],properties:["co2ppm"]},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.CO2Sensor="CO2Sensor",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:()=>Ke,default:()=>$e});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"};u()||("darwin"===c().platform()?e.VideoToolbox="h264_videotoolbox":"win32"===c().platform()?(e["Intel QuickSync"]="h264_qsv",e.AMD="h264_amf",e.Nvidia="h264_nvenc"):"linux"===c().platform()&&(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("child_process");var f=r.n(h);const g=require("net");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(y().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 C=["decode_slice_header error","no frame!","non-existing PPS"];function D(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)}}(y)}const l=e.inputArguments.slice();let p=!1;const m=e=>{if(!i)throw e(),new Error("parser session was killed killed before ffmpeg connected");s.on("killed",e)},h=["pipe","pipe","pipe"];let g=3;for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.parseDatagram){p=!0;const r=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:"+g++),h.push("pipe")}p&&(l.push("-sdp_file","pipe:"+g++),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 y=f().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 C)if(-1!==o.indexOf(e))return;if(!(s||r||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(o)};return i}t.stdout?.on("data",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}(r,y,void 0,t?.storage),y.on("exit",(()=>u(new Error("ffmpeg exited")))),v=p?new Promise((e=>{const t=[];y.stdio[g-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=y.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")}(y).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)}))}(y).then((e=>a=e)),await async function(e){return R(e,"Video")}(y).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=D(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){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 V="\n".charCodeAt(0);async function H(e){return async function(e,t){const r=[];let i=0;for(;;){const s=await B(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,V)}var U=r(113),L=r.n(U),j=r(747);const F=require("tls");var N=r.n(F);class q extends Error{constructor(e){super("Operation Timed Out"),this.promise=e}}function K(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));let r=0;for(let e=0;e<t.length;e++){t[e].startsWith("m=")&&(t.splice(e+1,0,"a=control:trackID="+r),r++)}return t.join("\r\n")}function $(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function G(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 W="a=control:";function z(e){const t=e.find((e=>e.startsWith(W)))?.substring(W.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase(),i=function(e){return{type:e.split(" ")[0].substring(2),payloadTypes:$(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:G(e),lines:e,contents:e.join("\r\n"),control:t,codec:s,direction:n}}function Q(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(z),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}async function J(e){await new Promise((t=>setTimeout(t,e)))}function Y(e,t){if("h264"!==e.type)return;const r=e.chunks[e.chunks.length-1].subarray(12),i=31&r[0];if(24===i){let e=1;for(;e<r.length;){const i=r.readUInt16BE(e);e+=2;if((31&r[e])===t)return r.subarray(e,e+i);e+=i}}else if(28===i){const e=31&r[1],i=!!(128&r[1]);if(e===t&&i)return r.subarray(1)}else if(i===t)return r}function X(e){const t={};for(const r of e.slice(1)){const e=r.indexOf(":");let i="";-1!==e&&(i=r.substring(e+1).trim());t[r.substring(0,e).toLowerCase()]=i}return t}function Z(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class ee 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=N().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=y().connect(i,r.hostname)}async safeTeardown(){if(!this.issuedTeardown){this.issuedTeardown=!0;try{this.writeTeardown(),await J(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 B(this.client,r);this.setupOptions.get(t)?.onRtp?.(e,i)}async readDataPayload(){const e=await B(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 B(this.client,4);if("RTSP"===e.toString()){this.client.unshift(e);return await super.readMessage()}await this.handleDataPayload(e)}}createAuthorizationHeader(e,t){if(!this.wwwAuthenticate)throw new Error("no WWW-Authenticate found");if(this.wwwAuthenticate.includes("Basic")){return`Basic ${j.rh.computeHash(t)}`}const r=j.Nu.parseWWWAuthenticateRest(this.wwwAuthenticate),i=new URL(this.url),s=decodeURIComponent(i.username),n=decodeURIComponent(i.password),o=new URL(t);o.username="",o.password="";const a=L().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=L().createHash("md5").update(`${e}:${o}`).digest("hex"),d=L().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 q(a))),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=X(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=function(e){for(const t of e.slice(1)){const e=t.indexOf(":");let r="";if(-1!==e&&(r=t.substring(e+1).trim()),"www-authenticate"===t.substring(0,e).toLowerCase())return r}}(n)||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 B(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=Z(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 te{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,U.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 B(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 B(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=Q(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 B(this.client,r);this.sdp=i.toString();const s={};s.Session=this.session,this.respond(200,"OK",t,s)}async record(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async teardown(e,t){const r={};r.Session=this.session,this.respond(200,"OK",t,r)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=X(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,r,i,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",i,{}),this.client.destroy(),new Error("check request failed")}if(this[t])return await this[t](r,i),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",i,{})}respond(e,t,r,i,s){let 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:re}=t();class ie{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 re.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:se}=t();class ne extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>se.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=se.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),se.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await se.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const{mediaManager:oe}=t();async function*ae(e){for(;;){const t=await B(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),s=await B(e,r);yield{header:t,length:r,type:i,data:s}}}const ce=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function de(e){return e}function ue(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...ce],async*parse(e){const t=ae(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:de}}var le=r(415);const pe=require("stream");function me(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 he(e,t,r,i,s){const n=Q(e),o=D(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 fe(e,t,r,i,s){let n=!0;const o=new pe.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=Q(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,le.Qc)(t);v=me(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return(async()=>{await J(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=Y(a,7);if(t)try{const r=(0,le.Qc)(t);v=me(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=>he(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:ge}=t(),ye="transcode",Se="mixin:@scrypted/prebuffer-mixin";function ve(){if(!ge.getNativeIds().includes(ye))return;return ge.getDeviceState(ye)?.id}class be extends e.ScryptedDeviceBase{constructor(e){super(ye),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(Se))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 _e(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function we(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):_e(t)}function Pe(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 ie(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=we(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(ve())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:_e(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:xe,log:Te,systemManager:Me,deviceManager:Ie}=t(),Oe="Default",Ce="AAC or No Audio",De=`${Ce} (Copy)`,Ae="Compatible Audio",Re="Other Audio",Ee=["aac","mp3","mp2","opus"],ke="-fflags +genpts",Be="Scrypted (TCP)",Ve="Scrypted (UDP)",He="FFmpeg (TCP)",Ue="FFmpeg (UDP)",Le="Default",je=[Ce,Ae,Re],Fe=["mpegts","mp4","rtsp"];class Ne{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=L().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)Y(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)||"";je.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),r=-1!==e.indexOf(Ae),i=-1!==e.indexOf(Re);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===Ue&&(r=Ue),e&&!r&&(i&&i!==Le||(r=Be),i===Be&&(r=Be),i===Ve&&(r=Ve)),r||(r=He)):r=Le,{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:[Le,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Le});const u=()=>{t.push({title:"Audio Codec Transcoding",group:d,description:"Configuring your camera to output Opus, PCM, or AAC is recommended.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Oe,choices:[Oe,De,"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:ke,choices:[ke,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Oddities()?Be:He,i=e?[Be,Ve]:[],s=this.storage.getItem(this.rtspParserKey)||Le;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:[Le,...i,He,Ue]}),(s===Le?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=!Ee.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Te.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"],f=[];let g;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)g=["-an"],this.audioDisabled=!0;else if(a||b)g=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(n||v)g=["-acodec","copy"],g.push(...h);else if(o)g=["-acodec","copy"],g.push(...f);else{g=["-acodec","copy"];const e="aac"===p?h:f;g.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,U.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?Y(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?Y(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 te(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?g:["-acodec","copy"]});this.sdp=e.sdp,P.parsers.rtsp=e}else P.parsers.mpegts={container:"mpegts",outputArguments:[...(T={vcodec:w,acodec:g})?.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=ue({vcodec:w,acodec:g}));const O=await this.mixinDevice.getVideoStream(t),C="x-scrypted/x-rfc4571"===O.mimeType;let D,A;this.storage.removeItem(this.lastDetectedAudioCodecKey),this.usingScryptedParser=!1;const R=this.getLastH264Oddities();if(c&&C){this.usingScryptedParser=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await xe.convertMediaObjectToJSON(O,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;D=fe(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return y().connect(parseInt(t.port),t.hostname)}(t),r,i,P),this.sdp=D.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await xe.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===Be||s===Ve,n&&this.usingScryptedParser&&R&&!this.stopInactive&&"scrypted"!==A.tool&&(this.console.warn("H264 oddities were detected in prebuffered 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."),this.usingScryptedParser=!1,s=He),this.usingScryptedParser)D=await async function(e,t,r,i){let s=!0;const n=new pe.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new ee(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=Q(d);let h=0;const f={};let g;const{useUdp:y}=i,S=e=>{if(y&&e.session&&!g){const t=Z(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"),process.nextTick((async()=>{try{await a.play(),await 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,le.Qc)(t);c=me(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=>he(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===Ve,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=D.sdp.then((e=>Buffer.concat(e).toString()));else{s===Ue?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)||ke;i.inputArguments.unshift(...e.split(" ")),D=await k(i,P)}}if(this.usingScryptedParser){const e={};let t=!1;const r=r=>{if("h264"!==r.type)return;const s=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||=s.has(29),e.stapb||=s.has(25),e.mtap16||=s.has(26),e.mtap32||=s.has(27),e.sei||=s.has(6);if((e.fuab||e.stapb||e.mtap16||e.mtap32||e.sei)&&!t){t=!0;let{isDefault:r}=this.getParser(c,A);if(this.console.warn("H264 oddity detected."),!r)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.');if(!this.stopInactive)return this.console.warn("Oddity in prebuffered stream. Restarting rebroadcast to use FFmpeg instead."),D.kill(new Error("restarting due to H264 oddity detection")),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),i(),void this.startPrebufferSession()}},i=()=>D.removeListener("rtsp",r);D.killed.finally((()=>clearTimeout(s))),D.on("rtsp",r);const s=setTimeout((()=>{i(),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e))}),R?6e4:1e4)}!r&&i&&void 0!==D.inputAudioCodec&&D.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const B=t?.video?.codec;if(B&&void 0!==D.inputVideoCodec&&D.inputVideoCodec!==B&&this.console.warn("Video codec plugin reported vs detected mismatch",B,D.inputVideoCodec),D.inputAudioCodec?Ee.includes(D.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",D.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",D.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,D.inputAudioCodec||"null"),"h264"!==D.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."),D.kill(new Error("audio probe completed, restarting")),this.startPrebufferSession();if(this.parserSession=D,D.killed.finally((()=>{this.parserSession===D&&(this.parserSession=void 0)})),D.killed.finally((()=>clearTimeout(this.inactivityTimeout))),Ie.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),A?.refreshAt){let t,r=A;const i=async()=>{if(!D.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await xe.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),D.killed.finally((()=>clearTimeout(t)))}for(const e of Fe){let t=0,r=this.prebuffers[e];D.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 D}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;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)}))})),async function(e,t){const r=await e;let i=!0;const s=()=>{const e=n;n=void 0,r.destroy(),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")))}(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||this.getLastH264Oddities())for(const e of d)e.time<o-n||a(e,!0);else{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)}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&&"remote"!==t?.destination&&(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=Q(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=K(p);const t=new te(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 qe extends ne{released=!1;sessions=new Map;streamSettings=Pe(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(ve()),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 xe.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(" ")),xe.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"),Te.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const 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&&Te.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 Ne(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?")}Ie.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 we(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 Ke extends o{storageSettings=new ie(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});transcodeStorageSettings=new ie(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:["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0","-force_key_frames","expr:gte(t,n_forced*4)"].join(" "),combobox:!0,mapPut:(e,t)=>m()[t]?.join(" ")||t||["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0","-force_key_frames","expr:gte(t,n_forced*4)"].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(Me.getSystemState())){const t=Me.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((()=>Ie.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{Ie.onDeviceDiscovered({nativeId:ye,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===ye)return new be(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){_(this.rtspServer),this.rtspServer=new(y().Server)((async e=>{let t;const r=new te(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=Q(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 te(e,n);await t.handlePlayback();const r=y().connect(parseInt(c.port),c.hostname);for(e.on("close",(()=>{r.destroy()})),r.on("close",(()=>{e.destroy()}));;){const i=(await B(r,2)).readInt16BE(0),s=await B(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,Se];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new qe(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 $e=new Ke})();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,C=t.ExpGolomb()+1,D=t.readBit();let R=0;D||(R=t.readBit());const A=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:C,frame_mbs_only_flag:D,mb_adaptive_frame_field_flag:R,direct_8x8_inference_flag:A,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.co2ppm="co2ppm",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"]},CO2Sensor:{name:"CO2Sensor",methods:[],properties:["co2ppm"]},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.CO2Sensor="CO2Sensor",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:()=>Fe,default:()=>Ne});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"};u()||("darwin"===c().platform()?e.VideoToolbox="h264_videotoolbox":"win32"===c().platform()?(e["Intel QuickSync"]="h264_qsv",e.AMD="h264_amf",e.Nvidia="h264_nvenc"):"linux"===c().platform()&&(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("child_process");var f=r.n(h);const g=require("net");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(){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 x(){const e=new(y().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 T=require("process");var M=r.n(T);const I=["decode_slice_header error","no frame!","non-existing PPS"];function O(e){if(e)return JSON.parse(JSON.stringify(e))}const{mediaManager:C}=t();async function D(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 R(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 A(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)}}(y)}const l=e.inputArguments.slice();const p=e=>{if(!i)throw e(),new Error("parser session was killed killed before ffmpeg connected");s.on("killed",e)},m=["pipe","pipe","pipe"];let h=3;const g=[];for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.tcpProtocol){const n=await x(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),l.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=R(e,u,s,t?.timeout);g.push((async()=>{const t=await n.clientPromise;try{p((()=>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:"+h++),m.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 y=f().spawn(await C.getFFmpegPath(),l,{stdio:m});let v;!function(e,t,r,i){const s=!!M().env.SCRYPTED_FFMPEG_NOISY||!!i?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const i=n=>{const o=n.toString();for(const e of I)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,y,void 0,t?.storage),y.on("exit",(()=>u(new Error("ffmpeg exited")))),v=Promise.resolve([]);return async function(e){return D(e,"Audio")}(y).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)}))}(y).then((e=>a=e)),await async function(e){return D(e,"Video")}(y).then((e=>o=e)),{start:()=>{for(const e of g)e();let e=0;Object.keys(t.parsers).forEach((async i=>{const n=t.parsers[i];if(!n.parse||n.tcpProtocol)return;const o=y.stdio[3+e];e++;try{const{resetActivityTimer:e}=R(i,u,s,t?.timeout);for await(const t of n.parse(o,parseInt(a?.[2]),parseInt(a?.[3])))s.emit(i,t),e()}catch(e){r.error("rebroadcast parse error",e),u(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=O(e.mediaStreamOptions)||{id:void 0,name:void 0};return t.video||(t.video={}),t.video.codec=o,t.audio=t?.audio?.codec===n?e?.mediaStreamOptions?.audio:{codec:n},t},emit(e,t){return s.emit(e,t),this},on(e,t){return s.on(e,t),this},once(e,t){return s.once(e,t),this},removeListener(e,t){return s.removeListener(e,t),this}}}async function E(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 k="\n".charCodeAt(0);async function B(e){return async function(e,t){const r=[];let i=0;for(;;){const s=await E(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,k)}var V=r(113),H=r.n(V),U=r(747);const L=require("tls");var j=r.n(L);class F extends Error{constructor(e){super("Operation Timed Out"),this.promise=e}}function N(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));let r=0;for(let e=0;e<t.length;e++){t[e].startsWith("m=")&&(t.splice(e+1,0,"a=control:trackID="+r),r++)}return t.join("\r\n")}function q(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function K(e){return e.filter((e=>e.startsWith("a=fmtp"))).map((e=>{const t=e.indexOf(" ");if(-1===t)return;const r=e.substring(0,t),i=e.substring(t+1),s=parseInt(r.split(":")[1]);if(!r||!i||NaN===s)return;const n={};return i.split(";").map((e=>e.trim())).forEach((e=>{const[t,...r]=e.split("=");n[t]=r.join("=")})),{payloadType:s,parameters:n}})).filter((e=>!!e))}const $="a=control:";function G(e){const t=e.find((e=>e.startsWith($)))?.substring($.length),r=e.find((e=>e.startsWith("a=rtpmap:")))?.toLowerCase(),i=function(e){return{type:e.split(" ")[0].substring(2),payloadTypes:q(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:K(e),lines:e,contents:e.join("\r\n"),control:t,codec:s,direction:n}}function W(e){const t=e.split("\n").map((e=>e.trim())),r=[],i=[];let s;for(const e of t)e.startsWith("m=")&&(s&&i.push(s),s=[]),s?s.push(e):r.push(e);s&&i.push(s);const n={header:{lines:r,contents:r.join("\r\n")},msections:i.map(G),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}function z(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 Q(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 J(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class Y 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 B(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=j().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=y().connect(i,r.hostname)}async safeTeardown(){if(!this.issuedTeardown){this.issuedTeardown=!0;try{this.writeTeardown(),await async function(e){await new Promise((t=>setTimeout(t,e)))}(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 E(this.client,r);this.setupOptions.get(t)?.onRtp?.(e,i)}async readDataPayload(){const e=await E(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 E(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 ${U.rh.computeHash(t)}`}const r=U.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=H().createHash("md5").update(`${s}:${r.realm}:${n}`).digest("hex"),c=H().createHash("md5").update(`${e}:${o}`).digest("hex"),d=H().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 F(a))),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=Q(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=function(e){for(const t of e.slice(1)){const e=t.indexOf(":");let r="";if(-1!==e&&(r=t.substring(e+1).trim()),"www-authenticate"===t.substring(0,e).toLowerCase())return r}}(n)||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 E(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="udp"===e.type?"UDP":"TCP",r="udp"===e.type?"client_port":"interleaved";let i;if("tcp"===e.type)i=e.port;else{if(!e.dgram){const t=await P();e.dgram=t.server,this.client.on("close",(()=>_(t.server)))}i=e.dgram.address().port,e.dgram.on("message",(t=>e.onRtp(void 0,t)))}const s={Transport:`RTP/AVP/${t};unicast;${r}=${i}-${i+1}`},n=await this.request("SETUP",s,e.path);let o;if(n.headers.session){const e=J(n.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=n.headers.session.split(";")[0]}if(n.headers.transport){const e=n.headers.transport.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const[t,r,i]=e;r&&i&&(o={begin:parseInt(r),end:parseInt(i)})}}return"tcp"===e.type&&this.setupOptions.set(o.begin,e),Object.assign({interleaved:o,options:e},n)}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 X{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,V.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await B(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 E(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 E(this.client,t),i=e.readUInt8(1),s=i-i%2,n=Object.values(this.setupTracks).find((e=>e.destination===s));if(!n)throw new Error("RSTP Server received unknown channel: "+i);yield{type:n.codec,rtcp:i%2==1,header:e,packet:r}}}send(e,t){const r=Buffer.alloc(4);r.writeUInt8(36,0),r.writeUInt8(t,1),r.writeUInt16BE(e.length,2),this.client.write(r),this.client.write(Buffer.from(e))}sendUdp(e,t,r){this.udp.send(t,r?e+1:e,"127.0.0.1")}sendTrack(e,t,r){const i=this.setupTracks[e];i?"udp"!==i.protocol?this.send(t,r?i.destination+1:i.destination):this.udp?this.sendUdp(i.destination,t,r):this.console?.warn("RTSP Server UDP socket not available."):this.console?.warn("RTSP Server track not found:",e)}options(e,t){const r={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,r)}describe(e,t){const r={};r["Content-Base"]=e,r["Content-Type"]="application/sdp",this.respond(200,"OK",t,r,Buffer.from(this.sdp))}setup(e,t){const r={},i=t.transport;r.Transport=i,r.Session=this.session;const s=W(this.sdp).msections.find((t=>e.endsWith(t.control)));if(s){if(i.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const e=i.match(/.*?client_port=([0-9]+)-([0-9]+)/),[r,n,o]=e;this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(n),codec:s.codec}}else if(i.includes("TCP")){const e=i.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const t=parseInt(e[1]);parseInt(e[2]);this.setupTracks[s.control]={control:s.control,protocol:"tcp",destination:t,codec:s.codec}}}this.respond(200,"OK",t,r)}else this.respond(404,"Not Found",t,r)}play(e,t){const r={},i=Object.values(this.setupTracks).map((t=>`url=${e}/${t.control}`)).join(",")+";seq=0;rtptime=0";r["RTP-Info"]=i,r.Range="npt=now-",r.Session=this.session,this.respond(200,"OK",t,r)}async announce(e,t){const r=parseInt(t["content-length"]),i=await E(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=Q(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:Z}=t();class ee{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 Z.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:te}=t();class re extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>te.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=te.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),te.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await te.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const{mediaManager:ie}=t();async function*se(e){for(;;){const t=await E(e,8),r=t.readInt32BE(0)-8,i=t.slice(4).toString(),s=await E(e,r);yield{header:t,length:r,type:i,data:s}}}const ne=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function oe(e){return e}function ae(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...ne],async*parse(e){const t=se(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:oe}}var ce=r(415);const de=require("stream");function ue(e){let t,r;0==e.chroma_format_idc&&0==e.color_plane_flag?t=r=0:1==e.chroma_format_idc&&0==e.color_plane_flag?t=r=2:2==e.chroma_format_idc&&0==e.color_plane_flag?(t=2,r=1):3==e.chroma_format_idc&&(0==e.color_plane_flag?t=r=1:1==e.color_plane_flag&&(t=r=0));let i=e.pic_width_in_mbs,s=e.pic_height_in_map_units,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 le(e,t,r,i,s){const n=W(e),o=O(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 pe(e,t,r,i,s){let n=!0;const o=new de.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=W(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}=R("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,ce.Qc)(t);v=ue(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return{start:()=>{(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=z(a,7);if(t)try{const r=(0,ce.Qc)(t);v=ue(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=>le(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:me}=t(),he="transcode",fe="mixin:@scrypted/prebuffer-mixin";function ge(){if(!me.getNativeIds().includes(he))return;return me.getDeviceState(he)?.id}class ye extends e.ScryptedDeviceBase{constructor(e){super(he),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(fe))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 Se(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function ve(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Se(t)}function be(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 ee(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=ve(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(ge())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Se(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream: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:_e,log:we,systemManager:Pe,deviceManager:xe}=t(),Te="Default",Me="AAC or No Audio",Ie=`${Me} (Copy)`,Oe="Compatible Audio",Ce="Other Audio",De=["aac","mp3","mp2","opus"],Re="-fflags +genpts",Ae="Scrypted (TCP)",Ee="Scrypted (UDP)",ke="FFmpeg (TCP)",Be="FFmpeg (UDP)",Ve="Default",He=[Me,Oe,Ce],Ue=["mpegts","mp4","rtsp"];class Le{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=H().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)z(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 Ue)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)||"";He.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Me),r=-1!==e.indexOf(Oe),i=-1!==e.indexOf(Ce);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===ke&&(r=ke),i===Be&&(r=Be),e&&!r&&(i&&i!==Ve||(r=Ae),i===Ae&&(r=Ae),i===Ee&&(r=Ee)),r||(r=ke)):r=Ve,{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:[Ve,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ve});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)||Te,choices:[Te,Ie,"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:Re,choices:[Re,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Oddities()?Ae:ke,i=e?[Ae,Ee]:[],s=this.storage.getItem(this.rtspParserKey)||Ve;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:[Ve,...i,ke,Be]}),(s===Ve?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=!De.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&we.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"],f=[];let g;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)g=["-an"],this.audioDisabled=!0;else if(a||b)g=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(n||v)g=["-acodec","copy"],g.push(...h);else if(o)g=["-acodec","copy"],g.push(...f);else{g=["-acodec","copy"];const e="aac"===p?h:f;g.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,V.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?z(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?z(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 X(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?g:["-acodec","copy"]});this.sdp=e.sdp,P.parsers.rtsp=e}else P.parsers.mpegts={container:"mpegts",outputArguments:[...(x={vcodec:w,acodec:g})?.vcodec||[],...x?.acodec||[],"-f","mpegts"],parse:(T=188,M=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],r=0;for(;;){const i=e.read();if(!i){await(0,S.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<T)continue;const s=Buffer.concat(t);M?.(s);const n=s.length%T,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],r=a.length,yield{chunks:[o]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const s=r.chunks[i];let n=0;for(;n+188<s.length;){const r=s.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}};var x,T,M;d&&(P.parsers.mp4=ae({vcodec:w,acodec:g}));const I=await this.mixinDevice.getVideoStream(t),O="x-scrypted/x-rfc4571"===I.mimeType;let C,D;this.storage.removeItem(this.lastDetectedAudioCodecKey),this.usingScryptedParser=!1;const E=this.getLastH264Oddities();if(c&&O){this.usingScryptedParser=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await _e.convertMediaObjectToJSON(I,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;C=pe(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return y().connect(parseInt(t.port),t.hostname)}(t),r,i,P),this.sdp=C.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await _e.convertMediaObjectToBuffer(I,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());D=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,D);if(this.usingScryptedParser=s===Ae||s===Ee,n&&this.usingScryptedParser&&E&&!this.stopInactive&&"scrypted"!==D.tool&&(this.console.warn("H264 oddities were detected in prebuffered 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."),this.usingScryptedParser=!1,s=ke),this.usingScryptedParser)C=await async function(e,t,r,i){let s=!0;const n=new de.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new Y(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}=R("rtsp",l,n,i?.rtspRequestTimeout);try{await a.options();const t=await a.describe(),o=t.headers["content-base"];if(o){const e=new URL(o,a.url),t=new URL(a.url);e.username=t.username,e.password=t.password,a.url=e.toString()}let c=t.body.toString().trim();e.log("sdp",c);const d=W(c);let m=0;const h={};let f;const{useUdp:g}=i,y=e=>{if(g&&e.session&&!f){const t=J(e.session);f=parseInt(t.timeout)}},S=async(e,t)=>{let r;if(g){const i=m,s={path:e,type:"udp",onRtp:(e,r)=>{const s=Buffer.alloc(4);s.writeUInt8(36,0),s.writeUInt8(i,1),s.writeUInt16BE(r.length,2);const o={chunks:[s,r],type:t};n.emit("rtsp",o),p?.()}},o=await a.setup(s);r=s.dgram,y(o.headers);const c=Buffer.alloc(1),d=o.headers.transport.match(/.*?server_port=([0-9]+)-([0-9]+)/),[u,l,f]=d,{hostname:g}=new URL(a.url);r.send(c,parseInt(l),g),h[m]=t}else{const r=await a.setup({path:e,type:"tcp",port:m,onRtp:(e,r)=>{const i={chunks:[e,r],type:t};n.emit("rtsp",i),p?.()}});r.interleaved?h[r.interleaved.begin]=t:h[m]=t}m+=2};let v=!1;d.msections=d.msections.filter((t=>{if("video"===t.type){if(v)return e.warn("additional video section found. skipping."),!1;v=!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 d.msections)await S(e.control,e.codec);c=[...d.header.lines,...d.msections.map((e=>e.lines)).flat()].join("\r\n");const b=async()=>{try{await a.play(),await a.readLoop()}catch(e){l(e)}finally{l(new Error("rtsp read loop exited"))}};return(()=>{const t=d.msections.find((e=>"audio"===e.type)),i=d.msections.find((e=>"video"===e.type)),o=t?.codec,a=i.codec;let m;const h=i?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],f=h?.split(",")?.[0];if(f)try{const t=Buffer.from(f,"base64"),r=(0,ce.Qc)(t);m=ue(r),e.log("parsed sdp sps",r)}catch(t){e.warn("sdp sps parsing failed")}return{start:b,sdp:Promise.resolve([Buffer.from(c)]),inputAudioCodec:o,inputVideoCodec:a,get inputVideoResolution(){return m},get isActive(){return s},kill(e){l(e)},killed:u,resetActivityTimer:p,negotiateMediaStream:e=>le(c,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===Ee,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=C.sdp.then((e=>Buffer.concat(e).toString()));else{s===Be?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===ke&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Re;i.inputArguments.unshift(...e.split(" ")),C=await A(i,P)}}if(this.usingScryptedParser){const e={};let t=!1;const r=r=>{if("h264"!==r.type)return;const s=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||=s.has(29),e.stapb||=s.has(25),e.mtap16||=s.has(26),e.mtap32||=s.has(27),e.sei||=s.has(6);if((e.fuab||e.stapb||e.mtap16||e.mtap32||e.sei)&&!t){t=!0;let{isDefault:r}=this.getParser(c,D);if(this.console.warn("H264 oddity detected."),!r)return void this.console.warn("If there are issues streaming, consider using the Default parser.");if("scrypted"===D.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.');if(!this.stopInactive)return this.console.warn("Oddity in prebuffered stream. Restarting rebroadcast to use FFmpeg instead."),C.kill(new Error("restarting due to H264 oddity detection")),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),i(),void this.startPrebufferSession()}},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))}),E?6e4:1e4)}!r&&i&&void 0!==C.inputAudioCodec&&C.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const k=t?.video?.codec;if(k&&void 0!==C.inputVideoCodec&&C.inputVideoCodec!==k&&this.console.warn("Video codec plugin reported vs detected mismatch",k,C.inputVideoCodec),C.inputAudioCodec?De.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))),xe.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),D?.refreshAt){let t,r=D;const i=async()=>{if(!C.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await _e.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 Ue){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.start(),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;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)}))})),async function(e,t){const r=await e;let i=!0;const s=()=>{const e=n;n=void 0,r.destroy(),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")))}(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||this.getLastH264Oddities())for(const e of d)e.time<o-n||a(e,!0);else{const e=this.parsers[r],t=d.filter((e=>e.time>=o-n));let i=e.findSyncFrame(t);i?this.console.log("Found sync frame in rtsp prebuffer."):(this.console.warn("Unable to find sync frame in rtsp prebuffer."),i=t);for(const e of i)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&&"remote"!==t?.destination&&(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,h=new Map;let f;if("rtsp"===a){const e=W(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){const t=h.get(e.type);return void(t&&f.sendTrack(t.control,e.chunks[1],e.type.startsWith("rtcp-")))}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 x();d=o.clientPromise.then((async e=>{p=N(p);const{server:t}=await P();e.on("close",(()=>_(t))),f=new X(e,p,t),await f.handlePlayback();for(const e of Object.values(f.setupTracks))"udp"!==e.protocol?(m.set(e.codec,e.destination),m.set(`rtcp-${e.codec}`,e.destination+1)):(h.set(e.codec,e),h.set(`rtcp-${e.codec}`,e));return e})),u=o.url.replace("tcp://","rtsp://")}else{const e=await x();d=e.clientPromise,u=`tcp://127.0.0.1:${e.port}`}c.sdp=p;const g=!1!==t?.refresh;this.handleRebroadcasterClient({findSyncFrame:e,isActiveClient:g,container:a,requestedPrebuffer:i,socketPromise:d,session:r,filter:l}),c.prebuffer=i;const{reencodeAudio:y}=this.getAudioConfig();this.audioDisabled?c.audio=null:y&&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 S=Date.now();let v=0;const b=this.prebuffers[a];for(const e of b)if(!(e.time<S-i))for(const t of e.chunks)v+=t.length;return{url:u,container:a,inputArguments:["-analyzeduration","0","-probesize",Math.max(5e5,v).toString(),...this.parsers[a].inputArguments||[],"-f",this.parsers[a].container,"-i",u],mediaStreamOptions:c}}}class je extends re{released=!1;sessions=new Map;streamSettings=be(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(ge()),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 _e.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(" ")),_e.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"),we.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&&we.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 Le(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?")}xe.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 ve(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 Fe extends o{storageSettings=new ee(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});transcodeStorageSettings=new ee(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:["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0","-force_key_frames","expr:gte(t,n_forced*4)"].join(" "),combobox:!0,mapPut:(e,t)=>m()[t]?.join(" ")||t||["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0","-force_key_frames","expr:gte(t,n_forced*4)"].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(Pe.getSystemState())){const t=Pe.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((()=>xe.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{xe.onDeviceDiscovered({nativeId:he,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===he)return new ye(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){_(this.rtspServer),this.rtspServer=new(y().Server)((async e=>{let t;const r=new X(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=W(n),a=new Map;for(const e of o.msections)for(const t of e.payloadTypes)a.set(t,e.control);const c=new URL(s);if(!c.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:d,url:u}=await x(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new X(e,n);await t.handlePlayback();const r=y().connect(parseInt(c.port),c.hostname);for(e.on("close",(()=>{r.destroy()})),r.on("close",(()=>{e.destroy()}));;){const i=(await E(r,2)).readInt16BE(0),s=await E(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,fe];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new je(this,{mixinDevice:e,mixinDeviceState:r,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Stream Management",groupKey:"prebuffer"});return this.currentMixins.set(r.id,i),i}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}const Ne=new Fe})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in i)s[n]=i[n];i.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
|
|
3
3
|
import { getDebugModeH264EncoderArgs, getH264EncoderArgs } from '@scrypted/common/src/ffmpeg-hardware-acceleration';
|
|
4
4
|
import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
|
|
5
|
-
import { closeQuiet, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
|
5
|
+
import { closeQuiet, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
|
6
6
|
import { readLength } from '@scrypted/common/src/read-stream';
|
|
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';
|
|
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, RtspTrack } 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";
|
|
@@ -780,7 +780,6 @@ class PrebufferSession {
|
|
|
780
780
|
removeOddityProbe();
|
|
781
781
|
this.storage.setItem(this.lastH264ProbeKey, JSON.stringify(h264Probe));
|
|
782
782
|
}, h264Oddities ? 60000 : 10000);
|
|
783
|
-
|
|
784
783
|
}
|
|
785
784
|
|
|
786
785
|
// complain to the user about the codec if necessary. upstream may send a audio
|
|
@@ -878,6 +877,7 @@ class PrebufferSession {
|
|
|
878
877
|
});
|
|
879
878
|
}
|
|
880
879
|
|
|
880
|
+
session.start();
|
|
881
881
|
return session;
|
|
882
882
|
}
|
|
883
883
|
|
|
@@ -995,6 +995,9 @@ class PrebufferSession {
|
|
|
995
995
|
this.console.warn('Unable to find sync frame in rtsp prebuffer.');
|
|
996
996
|
availablePrebuffers = filtered;
|
|
997
997
|
}
|
|
998
|
+
else {
|
|
999
|
+
this.console.log('Found sync frame in rtsp prebuffer.');
|
|
1000
|
+
}
|
|
998
1001
|
for (const prebuffer of availablePrebuffers) {
|
|
999
1002
|
safeWriteData(prebuffer, true);
|
|
1000
1003
|
}
|
|
@@ -1033,6 +1036,8 @@ class PrebufferSession {
|
|
|
1033
1036
|
let url: string;
|
|
1034
1037
|
let filter: (chunk: StreamChunk, prebuffer: boolean) => StreamChunk;
|
|
1035
1038
|
const codecMap = new Map<string, number>();
|
|
1039
|
+
const udpMap = new Map<string, RtspTrack>();
|
|
1040
|
+
let server: RtspServer;
|
|
1036
1041
|
|
|
1037
1042
|
if (container === 'rtsp') {
|
|
1038
1043
|
const parsedSdp = parseSdp(sdp);
|
|
@@ -1046,8 +1051,12 @@ class PrebufferSession {
|
|
|
1046
1051
|
sdp = parsedSdp.toSdp();
|
|
1047
1052
|
filter = (chunk, prebuffer) => {
|
|
1048
1053
|
const channel = codecMap.get(chunk.type);
|
|
1049
|
-
if (channel == undefined)
|
|
1054
|
+
if (channel == undefined) {
|
|
1055
|
+
const udp = udpMap.get(chunk.type);
|
|
1056
|
+
if (udp)
|
|
1057
|
+
server.sendTrack(udp.control, chunk.chunks[1], chunk.type.startsWith('rtcp-'));
|
|
1050
1058
|
return;
|
|
1059
|
+
}
|
|
1051
1060
|
// if no prebuffer is explicitly requested, don't send prebuffer audio
|
|
1052
1061
|
if (prebuffer && filterPrebufferAudio && chunk.type !== videoCodec)
|
|
1053
1062
|
return;
|
|
@@ -1064,10 +1073,17 @@ class PrebufferSession {
|
|
|
1064
1073
|
const client = await listenZeroSingleClient();
|
|
1065
1074
|
socketPromise = client.clientPromise.then(async (socket) => {
|
|
1066
1075
|
sdp = addTrackControls(sdp);
|
|
1067
|
-
const server =
|
|
1076
|
+
const {server: udp} = await createBindZero();
|
|
1077
|
+
socket.on('close', () => closeQuiet(udp));
|
|
1078
|
+
server = new RtspServer(socket, sdp, udp);
|
|
1068
1079
|
// server.console = this.console;
|
|
1069
1080
|
await server.handlePlayback();
|
|
1070
1081
|
for (const track of Object.values(server.setupTracks)) {
|
|
1082
|
+
if (track.protocol === 'udp') {
|
|
1083
|
+
udpMap.set(track.codec, track);
|
|
1084
|
+
udpMap.set(`rtcp-${track.codec}`, track);
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1071
1087
|
codecMap.set(track.codec, track.destination);
|
|
1072
1088
|
codecMap.set(`rtcp-${track.codec}`, track.destination + 1);
|
|
1073
1089
|
}
|
package/src/rfc4571.ts
CHANGED
|
@@ -125,11 +125,9 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
const start = () => {
|
|
129
129
|
// don't start parsing until next tick, to prevent missed packets.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
await read16BELengthLoop(socket, {
|
|
130
|
+
read16BELengthLoop(socket, {
|
|
133
131
|
headerLength: 2,
|
|
134
132
|
skipHeader: undefined,
|
|
135
133
|
callback: (header, data) => {
|
|
@@ -178,17 +176,19 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
|
|
|
178
176
|
events.emit('rtsp', chunk);
|
|
179
177
|
resetActivityTimer();
|
|
180
178
|
}
|
|
181
|
-
});
|
|
182
|
-
})()
|
|
183
|
-
.catch(e => {
|
|
184
|
-
throw e;
|
|
185
179
|
})
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
180
|
+
.catch(e => {
|
|
181
|
+
throw e;
|
|
182
|
+
})
|
|
183
|
+
.finally(() => {
|
|
184
|
+
kill(new Error('parser exited'));
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
189
188
|
|
|
190
189
|
|
|
191
190
|
return {
|
|
191
|
+
start,
|
|
192
192
|
sdp: Promise.resolve([Buffer.from(sdp)]),
|
|
193
193
|
inputAudioCodec,
|
|
194
194
|
inputVideoCodec,
|
package/src/rtsp-session.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
|
2
2
|
import { closeQuiet, createBindZero } from "@scrypted/common/src/listen-cluster";
|
|
3
|
-
import { parseSemicolonDelimited, RtspClient, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
|
|
3
|
+
import { parseSemicolonDelimited, RtspClient, RtspClientUdpSetupOptions, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
|
|
4
4
|
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
|
5
5
|
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
|
6
6
|
import { ResponseMediaStreamOptions } from "@scrypted/sdk";
|
|
@@ -63,7 +63,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
63
63
|
const sdpResponse = await rtspClient.describe();
|
|
64
64
|
const contentBase = sdpResponse.headers['content-base'];
|
|
65
65
|
if (contentBase) {
|
|
66
|
-
const url = new URL(contentBase);
|
|
66
|
+
const url = new URL(contentBase, rtspClient.url);
|
|
67
67
|
const existing = new URL(rtspClient.url);
|
|
68
68
|
url.username = existing.username;
|
|
69
69
|
url.password = existing.password;
|
|
@@ -85,32 +85,28 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const doSetup = async (control: string, codec: string) => {
|
|
88
|
-
let setupChannel = channel;
|
|
89
88
|
let udp: dgram.Socket;
|
|
90
89
|
if (useUdp) {
|
|
91
90
|
const rtspChannel = channel;
|
|
92
|
-
const { port, server } = await createBindZero();
|
|
93
|
-
udp = server;
|
|
94
|
-
servers.push(server);
|
|
95
|
-
setupChannel = port;
|
|
96
|
-
server.on('message', data => {
|
|
97
|
-
const prefix = Buffer.alloc(4);
|
|
98
|
-
prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
|
|
99
|
-
prefix.writeUInt8(rtspChannel, 1);
|
|
100
|
-
prefix.writeUInt16BE(data.length, 2);
|
|
101
|
-
const chunk: StreamChunk = {
|
|
102
|
-
chunks: [prefix, data],
|
|
103
|
-
type: codec,
|
|
104
|
-
};
|
|
105
|
-
events.emit('rtsp', chunk);
|
|
106
|
-
resetActivityTimer?.();
|
|
107
|
-
});
|
|
108
91
|
|
|
109
|
-
const
|
|
92
|
+
const setup: RtspClientUdpSetupOptions = {
|
|
110
93
|
path: control,
|
|
111
94
|
type: 'udp',
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
onRtp: (header, data) => {
|
|
96
|
+
const prefix = Buffer.alloc(4);
|
|
97
|
+
prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
|
|
98
|
+
prefix.writeUInt8(rtspChannel, 1);
|
|
99
|
+
prefix.writeUInt16BE(data.length, 2);
|
|
100
|
+
const chunk: StreamChunk = {
|
|
101
|
+
chunks: [prefix, data],
|
|
102
|
+
type: codec,
|
|
103
|
+
};
|
|
104
|
+
events.emit('rtsp', chunk);
|
|
105
|
+
resetActivityTimer?.();
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const setupResult = await rtspClient.setup(setup);
|
|
109
|
+
udp = setup.dgram;
|
|
114
110
|
checkUdpSessionTimeout(setupResult.headers);
|
|
115
111
|
|
|
116
112
|
const punch = Buffer.alloc(1);
|
|
@@ -126,7 +122,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
126
122
|
const setupResult = await rtspClient.setup({
|
|
127
123
|
path: control,
|
|
128
124
|
type: 'tcp',
|
|
129
|
-
port:
|
|
125
|
+
port: channel,
|
|
130
126
|
onRtp: (header, data) => {
|
|
131
127
|
const chunk: StreamChunk = {
|
|
132
128
|
chunks: [header, data],
|
|
@@ -175,7 +171,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
175
171
|
|
|
176
172
|
// don't start parsing until next tick when this function returns to allow
|
|
177
173
|
// event handlers to be set prior to parsing.
|
|
178
|
-
|
|
174
|
+
const start = async () => {
|
|
179
175
|
try {
|
|
180
176
|
await rtspClient.play();
|
|
181
177
|
await rtspClient.readLoop();
|
|
@@ -186,7 +182,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
186
182
|
finally {
|
|
187
183
|
kill(new Error('rtsp read loop exited'));
|
|
188
184
|
}
|
|
189
|
-
}
|
|
185
|
+
};
|
|
190
186
|
|
|
191
187
|
// this return block is intentional, to ensure that the remaining code happens sync.
|
|
192
188
|
return (() => {
|
|
@@ -219,6 +215,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
219
215
|
}
|
|
220
216
|
|
|
221
217
|
return {
|
|
218
|
+
start,
|
|
222
219
|
sdp: Promise.resolve([Buffer.from(sdp)]),
|
|
223
220
|
inputAudioCodec,
|
|
224
221
|
inputVideoCodec,
|