@scrypted/prebuffer-mixin 0.9.26 → 0.9.29
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/.vscode/launch.json +2 -1
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -2
- package/src/file-rtsp-server.ts +54 -0
- package/src/main.ts +192 -129
- package/src/rtsp-session.ts +17 -5
- package/src/stream-settings.ts +6 -0
- package/src/transcode-settings.ts +4 -1
package/.vscode/launch.json
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
"**/plugin-remote-worker.*",
|
|
14
14
|
"<node_internals>/**"
|
|
15
15
|
],
|
|
16
|
+
"autoAttachChildProcesses": true,
|
|
16
17
|
"preLaunchTask": "scrypted: deploy+debug",
|
|
17
18
|
"sourceMaps": true,
|
|
18
19
|
"localRoot": "${workspaceFolder}/out",
|
|
19
20
|
"remoteRoot": "/plugin/",
|
|
20
|
-
"type": "
|
|
21
|
+
"type": "node"
|
|
21
22
|
}
|
|
22
23
|
]
|
|
23
24
|
}
|
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 I=t.ExpGolomb(),M=t.readBit(),O=t.ExpGolomb()+1,R=t.ExpGolomb()+1,C=t.readBit();let D=0;C||(D=t.readBit());const E=t.readBit(),A=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}}(A,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:I,gaps_in_frame_num_value_allowed_flag:M,pic_width_in_mbs:O,pic_height_in_map_units:R,frame_mbs_only_flag:C,mb_adaptive_frame_field_flag:D,direct_8x8_inference_flag:E,frame_cropping_flag:A,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,s,n,o=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]}),a=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||o(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,a(r(268),t);const c=r(268);class d extends c.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=d;class u extends c.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=u,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(c.ScryptedInterfaceProperty))Object.defineProperty(d.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(u.prototype,r,{set:t(r),get:e(r)})}();let l={};try{let e;try{e=pluginRuntimeAPI}catch(e){}l=Object.assign(l,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI,...e});try{null===(n=null===(s=(i=systemManager).setScryptedInterfaceDescriptors)||void 0===s?void 0:s.call(i,c.TYPES_VERSION,c.ScryptedInterfaceDescriptors))||void 0===n||n.catch((()=>{}))}catch(e){}}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=l},733:e=>{const t=require("realfs");e.exports=t},268:(e,t,r)=>{"use strict";r.r(t),r.d(t,{TYPES_VERSION:()=>i,DeviceBase:()=>s,ScryptedInterfaceProperty:()=>n,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>a,HumidityMode:()=>c,FanMode:()=>d,TemperatureUnit:()=>u,ThermostatMode:()=>l,LockState:()=>p,AirQuality:()=>m,SecuritySystemMode:()=>h,SecuritySystemObstruction:()=>f,MediaPlayerState:()=>g,ScryptedInterface:()=>y,ScryptedMimeTypes:()=>S});const i="0.0.58";class s{}let n;!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"}(n||(n={}));const o={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","pluginId","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},Microphone:{name:"Microphone",methods:["getAudioStream"],properties:[]},Display:{name:"Display",methods:["startDisplay","stopDisplay"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamCurrentTime","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:[]},EventRecorder:{name:"EventRecorder",methods:["getRecordedEvents"],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 a,c,d,u,l,p,m,h,f,g,y,S;!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"}(a||(a={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(c||(c={})),function(e){e.Auto="Auto",e.Manual="Manual"}(d||(d={})),function(e){e.C="C",e.F="F"}(u||(u={})),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"}(l||(l={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Unknown="Unknown",e.Excellent="Excellent",e.Good="Good",e.Fair="Fair",e.Inferior="Inferior",e.Poor="Poor"}(m||(m={})),function(e){e.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(h||(h={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(f||(f={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(g||(g={})),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.EventRecorder="EventRecorder",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"}(y||(y={})),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.FFmpegTranscodeStream="x-scrypted/x-ffmpeg-transcode-stream",e.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object",e.RequestMediaStream="x-scrypted/x-scrypted-request-stream"}(S||(S={}))},113:e=>{"use strict";e.exports=require("crypto")},37:e=>{"use strict";e.exports=require("os")},257:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>h});var i=r(37);function s(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,i)}function i(){return a(e,arguments,u(this).constructor)}return i.prototype=Object.create(e.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),d(i,e)},o(e)}function a(e,t,r){return a=c()?Reflect.construct:function(e,t,r){var i=[null];i.push.apply(i,t);var s=new(Function.bind.apply(e,i));return r&&d(s,r.prototype),s},a.apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function d(e,t){return d=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},d(e,t)}function u(e){return u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},u(e)}let l=function(e){function t(e,r,...i){var s,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(i=(void 0===r?[]:[r]).concat(i),r=e,e=[]),o=this,(s=!(a=u(t).call(this,r))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=r||"E_UNEXPECTED",s.params=i,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var r,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),r=t,(o=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+i.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(r.prototype,o),a&&s(r,a),t}(o(Error));function p(e){return e instanceof l||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&m(e.code)&&e.params&&e.params instanceof Array}function m(e){return/^([A-Z0-9_]+)$/.test(e)}l.wrap=function(e,t,...r){let i=null;const s=m(e.message),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&r.push(e.message),i=new l(n,t,...r),i},l.cast=function(e,...t){return p(e)?e:l.wrap.apply(l,[e].concat(t))},l.bump=function(e,...t){return p(e)?l.wrap.apply(l,[e,e.code].concat(e.params)):l.wrap.apply(l,[e].concat(t))};const h=l}},t={};function r(i){var s=t[i];if(void 0!==s)return s.exports;var n=t[i]={exports:{}};return e[i].call(n.exports,n,n.exports,r),n.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};(()=>{"use strict";r.r(i),r.d(i,{RebroadcastPlugin:()=>Qe,default:()=>Je});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","nv12"],"Nvidia CUVID":["-vsync","0","-hwaccel","cuvid","-c:v","h264_cuvid","-hwaccel_output_format","nv12"]};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="h264_nvenc"));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"],t}const h=require("child_process");var f=r.n(h);const g=require("events");function y(e){if(e)return JSON.parse(JSON.stringify(e))}const S=require("dgram");var v=r.n(S);const b=require("net");var _=r.n(b);async function w(){const e=new(_().Server),t=await async function(e){return e.listen(0),await(0,g.once)(e,"listening"),e.address().port}(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{e.close(),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}`,host:"127.0.0.1",port:t,clientPromise:r}}async function P(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function x(e,t,r){e.bind(t,r),await(0,g.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function T(e){return I(0,e)}async function I(e,t){const r=v().createSocket(t||"udp4"),{port:i,url:s}=await x(r,e);return{server:r,port:i,url:s}}const M=require("process");var O=r.n(M);const R=["decode_slice_header error","no frame!","non-existing PPS"];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 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 A(e,t){const{console:r}=t;let i=!0;const s=new g.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)}}(v)}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 S=[];for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.tcpProtocol){const n=await w(),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);S.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 v=f().spawn(await C.getFFmpegPath(),l,{stdio:m});let b;!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 R)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,v,void 0,t?.storage),v.on("exit",(()=>u(new Error("ffmpeg exited")))),b=Promise.resolve([]);return async function(e){return D(e,"Audio")}(v).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)}))}(v).then((e=>a=e)),await async function(e){return D(e,"Video")}(v).then((e=>o=e)),{start:()=>{for(const e of S)e();let e=0;Object.keys(t.parsers).forEach((async i=>{const n=t.parsers[i];if(!n.parse||n.tcpProtocol)return;const o=v.stdio[3+e];e++;try{const{resetActivityTimer:e}=E(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:b,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=y(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}}}class k extends Error{constructor(){super()}}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 k)},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const V="\n".charCodeAt(0);async function U(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 L=r(113),H=r.n(L),j=r(747);const N=require("tls");var F=r.n(N);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))}function W(e,t){const r=t?.match(/a=rtpmap:([\d]+) (.*?)\/([\d]+)/);let i;return t=t?.toLowerCase(),t?.includes("mpeg4")?i="aac":t?.includes("opus")?i="opus":t?.includes("pcma")?i="pcm_alaw":t?.includes("pcmu")?i="pcm_ulaw":t?.includes("pcm")?i="pcm":t?.includes("h264")?i="h264":t?.includes("h265")?i="h265":t||"audio"!==e||(i="pcm_alaw"),{line:t,codec:i,rawCodec:r?.[2],clock:parseInt(r?.[3]),payloadType:parseInt(r?.[1])}}const z="a=control:",Q="a=rtpmap:";function J(e){const t=e.find((e=>e.startsWith(z)))?.substring(z.length),r=e.find((e=>e.startsWith(Q))),i=function(e){const t=e.split(" ");return{type:t[0].substring(2),port:parseInt(t[1]),protocol:t[2],payloadTypes:$(e)}}(e[0]);let s=W(i.type,r).codec;const n=e.filter((e=>e.startsWith(Q))).map((e=>W(i.type,e)));let o;for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){o=t;break}}const a={...i,fmtp:G(e),lines:e,rtpmaps:n,contents:e.join("\r\n"),control:t,codec:s,direction:o,toSdp:()=>a.lines.join("\r\n")};return a}function Y(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(J),toSdp:()=>[...n.header.lines,...n.msections.map((e=>e.lines)).flat()].join("\r\n")};return n}const X=24,Z=28;function ee(e,t){if("h264"===e.type)return function(e,t){const r=31&e[0];if(24===r){let r=1;for(;r<e.length;){const i=e.readUInt16BE(r);r+=2;if((31&e[r])===t)return e.subarray(r,r+i);r+=i}}else if(28===r){const r=31&e[1],i=!!(128&e[1]);if(r===t&&i)return e.subarray(1)}else if(r===t)return e;return}(e.chunks[e.chunks.length-1].subarray(12),t)}function te(e){return"h264"!==e.type?new Set:function(e,t=!1,r=!1){const i=new Set,s=31&e[0];if(s===X){i.add(X);let t=1;for(;t<e.length;){const r=e.readUInt16BE(t);t+=2;const s=31&e[t];i.add(s),t+=r}}else if(s===Z){i.add(Z);const s=31&e[1];if(t){!!(128&e[1])&&i.add(s)}else if(r){!!(64&e[1])&&i.add(s)}else i.add(s)}else i.add(s);return i}(e.chunks[e.chunks.length-1].subarray(12))}function re(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 ie(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class se extends Error{constructor(e){super(),this.status=e}}class ne extends class{constructor(e){this.console=e}write(e,t,r){let i=`${e}\r\n`;r&&(t["Content-Length"]=r.length.toString());for(const[e,r]of Object.entries(t))i+=`${e}: ${r}\r\n`;i+="\r\n",this.client.write(i),this.console?.log("rtsp outgoing message\n",i),this.console?.log(),r&&this.client.write(r)}async readMessage(){const e=await async function(e){let t=[];for(;;){let r=await U(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;hasGetParameter=!0;constructor(e,t){super(t),this.url=e;const r=new URL(e),i=parseInt(r.port)||554;e.startsWith("rtsps")?this.client=F().connect({rejectUnauthorized:!1,port:i,host:r.hostname}):this.client=_().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 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,this.hasGetParameter?await this.getParameter():await this.options()),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=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 q(a))),o);const r=setTimeout((()=>t(new q(a))),o);a.then((t=>{clearTimeout(r),e(t)})),a.catch((e=>{clearTimeout(r),t(e)}))}))):await this.readMessage();var o,a;const c=n[0],[d,u,l]=c.split(" ",3),p=parseInt(u),m=re(n),h={line:c,code:p,version:d,reason:l};if(200!==p&&!m["www-authenticate"])throw new se(h);const f=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)||m["www-authenticate"];if(f){if(s)throw new Error("auth failed");return this.wwwAuthenticate=f,this.request(e,t,r,i,!0)}const g=parseInt(m["content-length"]);return g?{headers:m,body:await B(this.client,g),status:h}:{headers:m,body:void 0,status:h}}async options(){const e=(await this.request("OPTIONS",{})).headers.public;e&&(this.hasGetParameter=e.toLowerCase().includes("get_parameter"))}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?"":"/TCP",r="udp"===e.type?"client_port":"interleaved";let i;if("tcp"===e.type)i=e.port;else{if(!e.dgram){const t=await T();e.dgram=t.server,this.client.on("close",(()=>P(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=ie(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 oe{setupTracks={};constructor(e,t,r,i){this.client=e,this.sdp=t,this.udp=r,this.checkRequest=i,this.session=(0,L.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(e=["play","record","teardown"]){let t=[];for(;;){let r=await U(this.client);if(r=r.trim(),r)t.push(r);else{const r=await this.headers(t);if(e.includes(r))return r;t=[]}}}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);return 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){e.send(r,t,"127.0.0.1")}sendTrack(e,t,r){const i=this.setupTracks[e];return i?"udp"===i.protocol?(this.udp?this.sendUdp(r?i.rtcp:i.rtp,i.destination,t):this.console?.warn("RTSP Server UDP socket not available."),!0):this.send(t,r?i.destination+1:i.destination):(this.console?.warn("RTSP Server track not found:",e),!0)}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))}setupInterleaved(e,t,r){this.setupTracks[e.control]={control:e.control,protocol:"tcp",destination:t,codec:e.codec}}async setup(e,t){const r={};let i=t.transport;r.Session=this.session;const s=Y(this.sdp).msections.find((t=>e.endsWith(t.control)));if(s){if(i.includes("TCP")){const e=i.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const t=parseInt(e[1]),r=parseInt(e[2]);this.setupInterleaved(s,t,r)}}else{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,a=await T(),c=await I(a.port+1);this.client.on("close",(()=>a.server.close())),this.client.on("close",(()=>c.server.close())),this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(n),codec:s.codec,rtp:a.server,rtcp:c.server},i=i.replace("RTP/AVP/UDP","RTP/AVP").replace("RTP/AVP","RTP/AVP/UDP"),i+=`;server_port=${a.port}-${c.port}`}r.Transport=i,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(",");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),this.client.destroy()}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=re(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),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()))}destroy(){this.client.destroy();for(const e of Object.values(this.setupTracks))P(e.rtp),P(e.rtcp)}}const{systemManager:ae}=t();class ce{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 ae.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:de}=t();class ue extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>de.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=de.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),de.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await de.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const{mediaManager:le}=t();async function*pe(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 me=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function he(e){return e}function fe(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...me],async*parse(e){const t=pe(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:he}}var ge=r(415);const ye=require("stream");function Se(e){let t,r;0==e.chroma_format_idc&&0==e.color_plane_flag?t=r=0:1==e.chroma_format_idc&&0==e.color_plane_flag?t=r=2:2==e.chroma_format_idc&&0==e.color_plane_flag?(t=2,r=1):3==e.chroma_format_idc&&(0==e.color_plane_flag?t=r=1:1==e.color_plane_flag&&(t=r=0));let i=e.pic_width_in_mbs,s=e.pic_height_in_map_units,n=(2-e.frame_mbs_only_flag)*s,o=0,a=0,c=0,d=0;return e.frame_cropping_flag&&(o=e.frame_cropping.left,a=e.frame_cropping.right,c=e.frame_cropping.top,d=e.frame_cropping.bottom),{width:16*i-t*(o+a),height:16*n-r*(2-e.frame_mbs_only_flag)*(c+d)}}function ve(e,t,r,i,s){const n=Y(e),o=y(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 be(e,t,r,i,s){let n=!0;const o=new ye.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=Y(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})),y=e=>{n&&(o.emit("killed"),o.emit("error",e||new Error("killed"))),n=!1,h(),t.destroy()};t.on("close",(()=>{y(new Error("rfc4751 socket closed"))})),t.on("error",(e=>{y(e)}));const{resetActivityTimer:S}=E("rtsp",y,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,ge.Qc)(t);v=Se(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,g.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=ee(a,7);if(t)try{const r=(0,ge.Qc)(t);v=Se(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),S()}}).catch((e=>{throw e})).finally((()=>{y(new Error("parser exited"))}))},sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:p,inputVideoCodec:m,get inputVideoResolution(){return v},get isActive(){return n},kill(e){y(e)},killed:f,resetActivityTimer:S,negotiateMediaStream:e=>ve(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:_e}=t(),we="transcode",Pe="mixin:@scrypted/prebuffer-mixin";function xe(){if(!_e.getNativeIds().includes(we))return;return _e.getDeviceState(we)?.id}class Te extends e.ScryptedDeviceBase{constructor(e){super(we),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(Pe))return[e.ScryptedInterface.Settings]}invalidateSettings(t){process.nextTick((()=>this.plugin.currentMixins.get(t)?.onDeviceEvent(e.ScryptedInterface.Settings,void 0)))}async getMixin(e,t,r){return this.invalidateSettings(r.id),e}async releaseMixin(e,t){this.invalidateSettings(e)}}function Ie(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function Me(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Ie(t)}function Oe(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: 1280x720.",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: 1280x720.",hide:!0,prefersPrebuffer:!0,preferredResolution:921600}},r=new ce(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},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},missingCodecParameters:{group:"Transcoding",title:"Out of Band Codec Parameters",description:"Some cameras do not include H264 codec parameters 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}});function i(e,t){const i=Me(r,t);return function(e,t){if(!e)return;let r,i;for(const s of e){const e=Math.abs(s.video?.width*s.video?.height-t);(!r||e<i)&&(r=s,i=e,Number.isNaN(i)&&(i=Number.MAX_SAFE_INTEGER))}return r}(e.prefersPrebuffer&&i?.length>0?i:t,e.preferredResolution)}function s(e,s){const n=r.settings[e],o=r.values[e];let a="Default"===o,c=s?.find((e=>e.name===o));return!a&&c||(a=!0,c=i(n,s)),{title:t[e].title,isDefault:a,stream:c}}function n(e,t){const r=["Default",...t.map((e=>e.name))],s=i(e,t).name;return{defaultValue:"Default",description:e.description+` The default for this stream is ${s}.`,choices:r,hide:!1}}return r.options={onGet:async()=>{let r;const i=e.mixins?.includes(xe())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Ie(i)?.map((e=>e.name)),choices:i.map((e=>e.name)),hide:!1},i?.length>1?{enabledStreams:r,defaultStream:n(t.defaultStream,i),remoteStream:n(t.remoteStream,i),lowResolutionStream:n(t.lowResolutionStream,i),recordingStream:n(t.recordingStream,i),remoteRecordingStream:n(t.remoteRecordingStream,i),...s}:{enabledStreams:r,...s}}catch(t){e.console.error("error retrieving getVideoStreamOptions",t)}return{...s}}},{getDefaultStream:e=>s(r.keys.defaultStream,e),getRemoteStream:e=>s(r.keys.remoteStream,e),getLowResolutionStream:e=>s(r.keys.lowResolutionStream,e),getRecordingStream:e=>s(r.keys.recordingStream,e),getRemoteRecordingStream:e=>s(r.keys.remoteRecordingStream,e),storageSettings:r}}const{mediaManager:Re,log:Ce,systemManager:De,deviceManager:Ee}=t(),Ae="Default",ke="AAC or No Audio",Be=`${ke} (Copy)`,Ve="Compatible Audio",Ue="Other Audio",Le=["aac","mp3","mp2","opus"],He="-fflags +genpts",je="Scrypted (TCP)",Ne="Scrypted (UDP)",Fe="FFmpeg (TCP)",qe="FFmpeg (UDP)",Ke="Default",$e=[ke,Ve,Ue],Ge=["mpegts","mp4","rtsp"];class We{prebuffers={mp4:[],mpegts:[],rtsp:[]};usingScryptedParser=!1;usingScryptedUdpParser=!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)ee(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 Ge)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)||"";$e.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(ke),r=-1!==e.indexOf(Ve),i=-1!==e.indexOf(Ue);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===Fe&&(r=Fe),i===qe&&(r=qe),e&&!r&&(i&&i!==Ke||(r=je),i===je&&(r=je),i===Ne&&(r=Ne)),r||(r=Fe)):r=Ke,{parser:r,isDefault:!i||"Default"===i}}getRebroadcastContainer(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default";"Default"===e&&(e="RTSP");const t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),muxingMp4:!t}}async getMixinSettings(){const t=[],r=this.parserSession;let i=0,s=0;const{muxingMp4:n,rtspMode:o}=this.getRebroadcastContainer();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunks)i+=t.byteLength}const a=Date.now()-s,c=Math.round(i/a*8),d=this.streamName?`Stream: ${this.streamName}`:"Stream";t.push({title:"Rebroadcast Container",group:d,description:"The container format to use when rebroadcasting. The default mode for this camera is RTSP.",placeholder:"RTSP",choices:[Ke,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ke});const u=()=>{t.push({title:"Audio Codec Transcoding",group:d,description:"Configuring your camera to output Opus, PCM, or AAC is recommended.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Ae,choices:[Ae,Be,"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:He,choices:[He,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Oddities()?je:Fe,i=e?[je,Ne]:[],s=this.storage.getItem(this.rtspParserKey)||Ke;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:[Ke,...i,Fe,qe]}),(s===Ke?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=!Le.includes(p);!d||l||!1===t?.userConfigurable||r||m&&(s&&Ce.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 y;this.audioDisabled=!1;const S=null===u;let v=!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."),v=!0),r||l)y=["-an"],this.audioDisabled=!0;else if(a||v)y=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(n||S)y=["-acodec","copy"],y.push(...h);else if(o)y=["-acodec","copy"],y.push(...f);else{y=["-acodec","copy"];const e="aac"===p?h:f;y.push(...e)}const b=["-vcodec","copy"],w={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=w.parsers,this.console.log("rebroadcast mode:",c?"rtsp":"mpegts"),c){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,L.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){let t,r={};const i=()=>e.slice(t);for(let i=0;i<e.length;i++){const s=e[i];"h264"===s.type?ee(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?ee(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 oe(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:b,acodec:r?y:["-acodec","copy"]});this.sdp=e.sdp,w.parsers.rtsp=e}else w.parsers.mpegts={container:"mpegts",outputArguments:[...(x={vcodec:b,acodec:y})?.vcodec||[],...x?.acodec||[],"-f","mpegts"],parse:(T=188,I=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],r=0;for(;;){const i=e.read();if(!i){await(0,g.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<T)continue;const s=Buffer.concat(t);I?.(s);const n=s.length%T,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],r=a.length,yield{chunks:[o]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const s=r.chunks[i];let n=0;for(;n+188<s.length;){const r=s.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}};var x,T,I;d&&(w.parsers.mp4=fe({vcodec:b,acodec:y}));const M=await this.mixinDevice.getVideoStream(t),O="x-scrypted/x-rfc4571"===M.mimeType;let R,C;this.storage.removeItem(this.lastDetectedAudioCodecKey),this.usingScryptedParser=!1;const D=this.getLastH264Oddities();if(c&&O){this.usingScryptedParser=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Re.convertMediaObjectToJSON(M,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;R=be(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return _().connect(parseInt(t.port),t.hostname)}(t),r,i,w),this.sdp=R.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Re.convertMediaObjectToBuffer(M,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());C=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,C);if(this.usingScryptedParser=s===je||s===Ne,this.usingScryptedUdpParser=s===Ne,n&&this.usingScryptedParser&&D&&!this.stopInactive&&"scrypted"!==C.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=Fe),this.usingScryptedParser)R=await async function(e,t,r,i){let s=!0;const n=new ye.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new ne(t,e);a.requestTimeout=i.rtspRequestTimeout;const c=()=>{for(const e of o)P(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(),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=Y(c);let m=0;const h={};let f;const{useUdp:g}=i,y=e=>{if(g&&e.session&&!f){const t=ie(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));if(!i)throw new Error("SDP does not contain a video section!");const o=t?.codec,a=i.codec;let m;const h=Date.now(),f=t=>{Date.now()-h>6e3&&n.removeListener("rtsp",f);const r=ee(t,7);if(r){try{const t=(0,ge.Qc)(r);m=Se(t),e.log("parsed bitstream sps",m)}catch(t){e.warn("sps parsing failed"),m={width:NaN,height:NaN}}n.removeListener("rtsp",f)}};m||n.on("rtsp",f);const g=i?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],y=g?.split(",")?.[0];if(y)try{const t=Buffer.from(y,"base64"),r=(0,ge.Qc)(t);m=Se(r),e.log("parsed sdp sps",m)}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=>ve(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===Ne,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=R.sdp.then((e=>Buffer.concat(e).toString()));else{s===qe?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===Fe&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||He;i.inputArguments.unshift(...e.split(" ")),R=await A(i,w)}}if(this.usingScryptedParser){const e={};let t=!1;const r=r=>{if("h264"!==r.type)return;const s=te(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,C);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"===C.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."),R.kill(new Error("restarting due to H264 oddity detection")),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),i(),void this.startPrebufferSession()}},i=()=>R.removeListener("rtsp",r);R.killed.finally((()=>clearTimeout(s))),R.on("rtsp",r);const s=setTimeout((()=>{i(),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e))}),D?6e4:1e4)}!r&&i&&void 0!==R.inputAudioCodec&&R.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,u);const k=t?.video?.codec;if(k&&void 0!==R.inputVideoCodec&&R.inputVideoCodec!==k&&this.console.warn("Video codec plugin reported vs detected mismatch",k,R.inputVideoCodec),R.inputAudioCodec?Le.includes(R.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,R.inputAudioCodec||"null"),"h264"!==R.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),l)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),R.kill(new Error("audio probe completed, restarting")),this.startPrebufferSession();if(this.parserSession=R,R.killed.finally((()=>{this.parserSession===R&&(this.parserSession=void 0)})),R.killed.finally((()=>clearTimeout(this.inactivityTimeout))),Ee.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),C?.refreshAt){let t,r=C;const i=async()=>{if(!R.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await Re.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());r=n.mediaStreamOptions,s(r)},s=e=>{const r=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),t=setTimeout(i,r)};s(r),R.killed.finally((()=>clearTimeout(t)))}for(const e of Ge){let t=0,r=this.prebuffers[e];R.on(e,(i=>{const s=Date.now();for(i.time=s,r.push(i);r.length&&r[0].time<s-1e4;)r.shift(),t++;t>1e5&&(r=r.slice(),this.prebuffers[e]=r,t=0)}))}return R.start(),R}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t,r){this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(this.inactivityTimeout&&!r||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.inactivityTimeout=void 0,this.activeClients?this.console.log("inactivity timeout found active clients."):(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),t.kill(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=[]);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;if(null==i){const e="remote"===t?.destination?2e3:4e3;i=Math.min(e,this.getDetectedIdrInterval()||e)}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=Y(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 w();d=o.clientPromise.then((async e=>{p=K(p),f=new oe(e,p),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 w();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;c.prebufferBytes=v;const _=["-analyzeduration","0","-probesize",Math.max(5e5,v).toString()];this.usingScryptedUdpParser||_.push("-reorder_queue_size","0");return{url:u,container:a,inputArguments:[..._,...this.parsers[a].inputArguments||[],"-f",this.parsers[a].container,"-i",u],mediaStreamOptions:c}}}class ze extends ue{released=!1;sessions=new Map;streamSettings=Oe(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,n=t?.id;this.sessions.has(n)||(n=void 0);const o=this.mixins?.includes(xe()),a=await this.mixinDevice.getVideoStreamOptions();let c;const d=2e6;if(!n){switch(t?.destination){case"medium-resolution":case"remote":c=this.streamSettings.getRemoteStream(a),s=this.plugin.transcodeStorageSettings.values.remoteStreamingBitrate;break;case"low-resolution":c=this.streamSettings.getLowResolutionStream(a),s=512e3;break;case"local-recorder":c=this.streamSettings.getRecordingStream(a),s=d;break;case"remote-recorder":c=this.streamSettings.getRemoteRecordingStream(a),s=d;break;default:c=this.streamSettings.getDefaultStream(a),s=d}n=c.stream.id,this.console.log("Selected stream",c.stream.name),o&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(c.title)&&(r=this.plugin.transcodeStorageSettings.values.h264EncoderArguments?.split(" "),this.streamSettings.storageSettings.values.videoFilterArguments&&(i=this.streamSettings.storageSettings.values.videoFilterArguments))}let u,l=this.sessions.get(n);if(l.canPrebuffer||(this.console.log("Source container can not be prebuffered. Using a direct media stream."),l=void 0),l){const e=!(o||t?.container&&"rtsp"!==t?.container||"ffmpeg"===t?.tool);u=await l.getVideoStream(e,t)}else{const r=await this.mixinDevice.getVideoStream(t);if(!o)return r;u=await Re.convertMediaObjectToJSON(r,e.ScryptedMimeTypes.FFmpegInput)}return u.h264EncoderArguments=r,u.destinationVideoBitrate=s,o&&this.streamSettings.storageSettings.values.missingCodecParameters&&(u.mediaStreamOptions||(u.mediaStreamOptions={id:n}),u.mediaStreamOptions.oobCodecParameters=!0),u.h264FilterArguments&&i?function(e,t,r="unfilteredRecording"){const i=e.findIndex((e=>"-filter_complex"===e));void 0!==i&&-1!==i?e[i+1]=e[i+1]+`[${r}] ; [${r}] ${t}`:e.push("-filter_complex",t)}(u.h264FilterArguments,i):i&&(u.h264FilterArguments=["-filter_complex",i]),o&&(u.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),Re.createFFmpegMediaObject(u,{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"),Ce.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&&Ce.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 We(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?")}}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 Me(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(){super.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 Qe extends o{storageSettings=new ce(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});transcodeStorageSettings=new ce(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"].join(" "),combobox:!0,mapPut:(e,t)=>m()[t]?.join(" ")||t||["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0"].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(De.getSystemState())){const t=De.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout((()=>Ee.requestRestart()),r),this.startRtspServer(),process.nextTick((()=>{Ee.onDeviceDiscovered({nativeId:we,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===we)return new Te(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}startRtspServer(){P(this.rtspServer),this.rtspServer=new(_().Server)((async e=>{let t;const r=new oe(e,void 0,!1,(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=new Map;for(const[e,t]of Object.entries(r.setupTracks))i.set(t.codec,e);const s=await t.parserSessionPromise,n=Math.max(4e3,t.getDetectedIdrInterval()||4e3);t.handleRebroadcasterClient({findSyncFrame:!0,isActiveClient:!0,container:"rtsp",session:s,socketPromise:Promise.resolve(e),requestedPrebuffer:n,filter:(e,t)=>{const s=i.get(e.type);s&&r.sendTrack(s,e.chunks[1],!1)}}),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=Y(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 w(),l={url:u,inputArguments:["-rtsp_transport","tcp","-i",u.replace("tcp","rtsp")]};return d.then((async e=>{const t=new oe(e,n);await t.handlePlayback();const r=_().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,Pe];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);const i=new ze(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 Je=new Qe})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in i)s[n]=i[n];i.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
1
|
+
(()=>{var e={747:(e,t,r)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}});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 l(e,t){const r=n.default.createHash(e);return r.update(t),r.digest("hex")}var u={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||l(e.algorithm,[e.username,e.realm,e.password].join(":")),r=l(e.algorithm,[e.method,e.uri].join(":"));return l(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,r].join(":"))}};t.default=u},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,l=0,u=0,p=0,m=0,h=0;const f=[],g=[],y=[],v=[];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()),l=t.ExpGolomb()+8,u=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,v,r-6))}}const S=t.ExpGolomb()+4,b=t.ExpGolomb();let w=0,_=0,P=0;const x=[];let T=0;if(0===b)T=t.ExpGolomb()+4;else if(1===b){w=t.readBit(),_=t.SignedExpGolomb(),P=t.SignedExpGolomb();const e=t.ExpGolomb();for(let r=0;r<e;r++)x.push(t.SignedExpGolomb())}const I=t.ExpGolomb(),M=t.readBit(),O=t.ExpGolomb()+1,R=t.ExpGolomb()+1,C=t.readBit();let D=0;C||(D=t.readBit());const E=t.readBit(),A=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}}(A,t),B=t.readBit();return{sps_id:c,profile_compatibility:o,profile_idc:r,level_idc:a,chroma_format_idc:d,bit_depth_luma:l,bit_depth_chroma:u,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:S,pic_order_cnt_type:b,delta_pic_order_always_zero_flag:w,offset_for_non_ref_pic:_,offset_for_top_to_bottom_field:P,offset_for_ref_frame:x,log2_max_pic_order_cnt_lsb:T,max_num_ref_frames:I,gaps_in_frame_num_value_allowed_flag:M,pic_width_in_mbs:O,pic_height_in_map_units:R,frame_mbs_only_flag:C,mb_adaptive_frame_field_flag:D,direct_8x8_inference_flag:E,frame_cropping_flag:A,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,s,n,o=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]}),a=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||o(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,a(r(268),t);const c=r(268);class d extends c.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=d;class l extends c.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._deviceState.__rpcproxy_traps_all_properties&&deviceManager.createDeviceState&&"string"==typeof this._deviceState.id&&(this._deviceState=deviceManager.createDeviceState(this._deviceState.id,this._deviceState.setState)),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=l,function(){function e(e){return function(){var t;return this._lazyLoadDeviceState(),null===(t=this._deviceState)||void 0===t?void 0:t[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState?this._deviceState[e]=t:console.warn("device state is unavailable. the device must be discovered with deviceManager.onDeviceDiscovered or deviceManager.onDevicesChanged before the state can be set.")}}for(var r of Object.values(c.ScryptedInterfaceProperty))Object.defineProperty(d.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(l.prototype,r,{set:t(r),get:e(r)})}();let u={};try{let e;try{e=pluginRuntimeAPI}catch(e){}u=Object.assign(u,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI,...e});try{null===(n=null===(s=(i=systemManager).setScryptedInterfaceDescriptors)||void 0===s?void 0:s.call(i,c.TYPES_VERSION,c.ScryptedInterfaceDescriptors))||void 0===n||n.catch((()=>{}))}catch(e){}}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/client instead",e)}t.default=u},733:e=>{const t=require("realfs");e.exports=t},268:(e,t,r)=>{"use strict";r.r(t),r.d(t,{TYPES_VERSION:()=>i,DeviceBase:()=>s,ScryptedInterfaceProperty:()=>n,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>a,HumidityMode:()=>c,FanMode:()=>d,TemperatureUnit:()=>l,ThermostatMode:()=>u,LockState:()=>p,AirQuality:()=>m,SecuritySystemMode:()=>h,SecuritySystemObstruction:()=>f,MediaPlayerState:()=>g,ScryptedInterface:()=>y,ScryptedMimeTypes:()=>v});const i="0.0.88";class s{}let n;!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.ptzCapabilities="ptzCapabilities",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",e.applicationInfo="applicationInfo"}(n||(n={}));const o={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","pluginId","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"],properties:["thermostatActiveMode","thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},Microphone:{name:"Microphone",methods:["getAudioStream"],properties:[]},Display:{name:"Display",methods:["startDisplay","stopDisplay"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamCurrentTime","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:[]},PanTiltZoom:{name:"PanTiltZoom",methods:["ptzCommand"],properties:["ptzCapabilities"]},EventRecorder:{name:"EventRecorder",methods:["getRecordedEvents"],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:[]},LauncherApplication:{name:"LauncherApplication",methods:[],properties:["applicationInfo"]}};let a,c,d,l,u,p,m,h,f,g,y,v;!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"}(a||(a={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(c||(c={})),function(e){e.Auto="Auto",e.Manual="Manual"}(d||(d={})),function(e){e.C="C",e.F="F"}(l||(l={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Unknown="Unknown",e.Excellent="Excellent",e.Good="Good",e.Fair="Fair",e.Inferior="Inferior",e.Poor="Poor"}(m||(m={})),function(e){e.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(h||(h={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(f||(f={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(g||(g={})),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.PanTiltZoom="PanTiltZoom",e.EventRecorder="EventRecorder",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",e.LauncherApplication="LauncherApplication"}(y||(y={})),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.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaStreamUrl="text/x-media-url",e.MediaObject="x-scrypted/x-scrypted-media-object",e.RequestMediaStream="x-scrypted/x-scrypted-request-stream",e.ScryptedDevice="x-scrypted/x-scrypted-device",e.ScryptedDeviceId="x-scrypted/x-scrypted-device-id",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.FFmpegTranscodeStream="x-scrypted/x-ffmpeg-transcode-stream",e.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.RTCSignalingSession="x-scrypted/x-scrypted-rtc-signaling-session",e.RTCConnectionManagement="x-scrypted/x-scrypted-rtc-connection-management"}(v||(v={}))},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,l(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 l(e){return l=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},l(e)}let u=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=l(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 u||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)}u.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 u(n,t,...r),i},u.cast=function(e,...t){return p(e)?e:u.wrap.apply(u,[e].concat(t))},u.bump=function(e,...t){return p(e)?u.wrap.apply(u,[e,e.code].concat(e.params)):u.wrap.apply(u,[e].concat(t))};const h=u}},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:()=>rt,default:()=>ot,fork:()=>nt});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 l(){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 u="Video4Linux (Docker compatible)";function p(){if(l()){return{}}if("darwin"===c().platform())return{VideoToolbox:["-hwaccel","auto"]};const e={"Nvidia CUDA":["-vsync","0","-hwaccel","cuda","-hwaccel_output_format","nv12"],"Nvidia CUVID":["-vsync","0","-hwaccel","cuvid","-c:v","h264_cuvid","-hwaccel_output_format","nv12"]};if(l())e["Raspberry Pi"]=["-c:v","h264_mmal"],e[u]=["-c:v","h264_v4l2m2m"];else if("linux"===c().platform())e[u]=["-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"};l()||("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="h264_nvenc"));const t={};for(const[r,i]of Object.entries(e))t[r]=["-c:v",i];return l()&&(t["Raspberry Pi"]=["-pix_fmt","yuv420p","-c:v","h264_v4l2m2m"]),t["libx264 (Software)"]=["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0"],t}const h=require("child_process");var f=r.n(h);const g=require("events");function y(e){if(e)return JSON.parse(JSON.stringify(e))}const v=require("dgram");var S=r.n(v);const b=require("net");var w=r.n(b);async function _(){const e=new(w().Server),t=await async function(e){return e.listen(0),await(0,g.once)(e,"listening"),e.address().port}(e),r=new Promise(((t,r)=>{const i=setTimeout((()=>{e.close(),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}`,host:"127.0.0.1",port:t,clientPromise:r}}async function P(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}async function x(e,t,r){e.bind(t,r),await(0,g.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function T(e){return I(0,e)}async function I(e,t){const r=S().createSocket(t||"udp4"),{port:i,url:s}=await x(r,e);return{server:r,port:i,url:s}}const M=require("process");var O=r.n(M);const R=["decode_slice_header error","no frame!","non-existing PPS"];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 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 A(e,t){const{console:r}=t;let i=!0;const s=new g.EventEmitter;let n,o,a,c;s.on("error",(e=>r.error("rebroadcast error",e)));const d=new Promise((e=>{c=e}));function l(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)}}(S)}const u=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 v=[];for(const e of Object.keys(t.parsers)){const i=t.parsers[e];if(i.tcpProtocol){const n=await _(),o=new URL(i.tcpProtocol);o.port=n.port.toString(),u.push(...i.outputArguments,o.toString());const{resetActivityTimer:c}=E(e,l,s,t?.timeout);v.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),l(e)}}))}else u.push(...i.outputArguments,"pipe:"+h++),m.push("pipe")}u.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,u);const S=f().spawn(await C.getFFmpegPath(),u,{stdio:m});let b;!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 R)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,S,void 0,t?.storage),S.on("exit",(()=>l(new Error("ffmpeg exited")))),b=Promise.resolve([]);return async function(e){return D(e,"Audio")}(S).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)}))}(S).then((e=>a=e)),await async function(e){return D(e,"Video")}(S).then((e=>o=e)),{start:()=>{for(const e of v)e();let e=0;Object.keys(t.parsers).forEach((async i=>{const n=t.parsers[i];if(!n.parse||n.tcpProtocol)return;const o=S.stdio[3+e];e++;try{const{resetActivityTimer:e}=E(i,l,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),l(e)}}))},sdp:b,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){l(e)},killed:d,negotiateMediaStream:()=>{const t=y(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}}}class k extends Error{constructor(){super()}}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 k)},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const V="\n".charCodeAt(0);async function L(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),H=r.n(U),j=r(747),N=r(761);const F=require("tls");var q=r.n(F);class K{finished=!1;promise=new Promise(((e,t)=>{this.resolve=t=>{this.finished=!0,e(t)},this.reject=e=>{this.finished=!0,t(e)}}))}class $ extends Error{constructor(e){super("Operation Timed Out"),this.promise=e}}function G(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 W(e){const t=[];for(const r of e.split(" ").slice(3)||[])t.push(parseInt(r));return t}function z(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))}function Q(e,t){const r=t?.match(/a=rtpmap:([\d]+) (.*?)\/([\d]+)/);let i;return t=t?.toLowerCase(),t?.includes("mpeg4")?i="aac":t?.includes("opus")?i="opus":t?.includes("pcma")?i="pcm_alaw":t?.includes("pcmu")?i="pcm_ulaw":t?.includes("pcm")?i="pcm":t?.includes("h264")?i="h264":t?.includes("h265")?i="h265":t||"audio"!==e||(i="pcm_alaw"),{line:t,codec:i,rawCodec:r?.[2],clock:parseInt(r?.[3]),payloadType:parseInt(r?.[1])}}const J="a=control:",Y="a=rtpmap:";function Z(e){const t=e.find((e=>e.startsWith(J)))?.substring(J.length),r=e.find((e=>e.startsWith(Y))),i=function(e){const t=e.split(" ");return{type:t[0].substring(2),port:parseInt(t[1]),protocol:t[2],payloadTypes:W(e)}}(e[0]);let s=Q(i.type,r).codec;const n=e.filter((e=>e.startsWith(Y))).map((e=>Q(i.type,e)));let o;for(const t of["sendonly","sendrecv","recvonly","inactive"]){if(e.find((e=>e==="a="+t))){o=t;break}}const a={...i,fmtp:z(e),lines:e,rtpmaps:n,contents:e.join("\r\n"),control:t,codec:s,direction:o,toSdp:()=>a.lines.join("\r\n")};return a}function X(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}const ee=["realm","nonce"],te=36;const re=24,ie=28;function se(e,t){if("h264"===e.type)return function(e,t){const r=31&e[0];if(24===r){let r=1;for(;r<e.length;){const i=e.readUInt16BE(r);r+=2;if((31&e[r])===t)return e.subarray(r,r+i);r+=i}}else if(28===r){const r=31&e[1],i=!!(128&e[1]);if(r===t&&i)return e.subarray(1)}else if(r===t)return e;return}(e.chunks[e.chunks.length-1].subarray(12),t)}function ne(e){return"h264"!==e.type?new Set:function(e,t=!1,r=!1){const i=new Set,s=31&e[0];if(s===re){i.add(re);let t=1;for(;t<e.length;){const r=e.readUInt16BE(t);t+=2;const s=31&e[t];i.add(s),t+=r}}else if(s===ie){i.add(ie);const s=31&e[1];if(t){!!(128&e[1])&&i.add(s)}else if(r){!!(64&e[1])&&i.add(s)}else i.add(s)}else i.add(s);return i}(e.chunks[e.chunks.length-1].subarray(12))}function oe(e){const t={};for(const r of e.slice(1)){const e=r.indexOf(":");let i="";-1!==e&&(i=r.substring(e+1).trim());t[r.substring(0,e).toLowerCase()]=i}return t}function ae(e){const t={};for(const r of e.split(";")){const[e,i]=r.split("=",2);t[e]=i}return t}class ce extends Error{constructor(e){super(),this.status=e}}class de extends class{constructor(){}write(e,t,r){let i=`${e}\r\n`;r&&(t["Content-Length"]=r.length.toString());for(const[e,r]of Object.entries(t))i+=`${e}: ${r}\r\n`;i+="\r\n",this.client.write(i),this.console?.log("rtsp outgoing message\n",i),this.console?.log(),r&&this.client.write(r)}async readMessage(){const e=await async function(e){let t=[];for(;;){let r=await L(e);if(r=r.trim(),!r)return t;t.push(r)}}(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;needKeepAlive=!1;setupOptions=new Map;issuedTeardown=!1;hasGetParameter=!0;constructor(e){super(),this.url=e;const t=new URL(e),r=parseInt(t.port)||554;e.startsWith("rtsps")?this.client=q().connect({rejectUnauthorized:!1,port:r,host:t.hostname}):this.client=w().connect(r,t.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(e[0]!==te)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)}createBadHeader(e){return 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())}async readLoopLegacy(){try{for(;;)this.needKeepAlive&&(this.needKeepAlive=!1,this.hasGetParameter?await this.getParameter():await this.options()),await this.readDataPayload()}catch(e){throw this.client.destroy(e),e}}async readLoop(){const e=new K;let t,r,i;const s=async()=>{this.needKeepAlive&&(this.needKeepAlive=!1,this.hasGetParameter?this.writeGetParameter():this.writeOptions());try{for(;;){if(!t){if(t=this.client.read(4),!t)return;if(t[0]!==te){if("RTSP"!==t.toString())throw this.createBadHeader(t);this.client.unshift(t),t=void 0,this.client.removeListener("readable",s);await super.readMessage();this.client.on("readable",s);continue}r=t.readUInt8(1),i=t.readUInt16BE(2)}const e=this.client.read(i);if(!e)return;const n=t;t=void 0;this.setupOptions.get(r)?.onRtp?.(n,e)}}catch(t){e.reject(t),this.client.destroy()}};s(),this.client.on("readable",s),await Promise.all([(0,g.once)(this.client,"end")])}async readMessage(){for(;;){const e=await B(this.client,4);if(e[0]!==te){if("RTSP"===e.toString()){this.client.unshift(e);return await super.readMessage()}throw this.createBadHeader(e)}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=(0,N.parseHTTPHeadersQuotedKeyValueSet)(this.wwwAuthenticate,{indexOf:()=>0},ee),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"),l={username:s,realm:r.realm,nonce:r.nonce,uri:o.toString(),algorithm:"MD5",response:d};return`Digest ${Object.entries(l).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 $(a))),o);const r=setTimeout((()=>t(new $(a))),o);a.then((t=>{clearTimeout(r),e(t)})),a.catch((e=>{clearTimeout(r),t(e)}))}))):await this.readMessage();var o,a;const c=n[0],[d,l,u]=c.split(" ",3),p=parseInt(l),m=oe(n),h={line:c,code:p,version:d,reason:u};if(200!==p&&!m["www-authenticate"])throw new ce(h);const f=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)||m["www-authenticate"];if(f){if(s)throw new Error("auth failed");return this.wwwAuthenticate=f,this.request(e,t,r,i,!0)}const g=parseInt(m["content-length"]);return g?{headers:m,body:await B(this.client,g),status:h}:{headers:m,body:void 0,status:h}}async options(){const e=await this.request("OPTIONS",{}),t=e.headers.public;return t&&(this.hasGetParameter=t.toLowerCase().includes("get_parameter")),e}writeOptions(){return this.writeRequest("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?"":"/TCP",r="udp"===e.type?"client_port":"interleaved";let i;if("tcp"===e.type)i=e.port;else{if(!e.dgram){const t=await T();e.dgram=t.server,this.client.on("close",(()=>P(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=ae(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?o.begin:i,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 le{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(e=["play","record","teardown"]){let t=[];for(;;){let r=await L(this.client);if(r=r.trim(),r)t.push(r);else{const r=await this.headers(t);if(e.includes(r))return r;t=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await B(this.client,4);if(e[0]!==te)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}}}writeRtpPayload(e,t){return this.client.write(e),this.client.write(Buffer.from(t))}send(e,t){const r=Buffer.alloc(4);return r.writeUInt8(36,0),r.writeUInt8(t,1),r.writeUInt16BE(e.length,2),this.writeRtpPayload(r,e)}sendUdp(e,t,r){e.send(r,t,"127.0.0.1")}sendTrack(e,t,r){const i=this.setupTracks[e];return i?"udp"===i.protocol?(this.udp?this.sendUdp(r?i.rtcp:i.rtp,i.destination,t):this.console?.warn("RTSP Server UDP socket not available."),!0):this.send(t,r?i.destination+1:i.destination):(this.console?.warn("RTSP Server track not found:",e),!0)}availableOptions=["DESCRIBE","OPTIONS","PAUSE","PLAY","SETUP","TEARDOWN","ANNOUNCE","RECORD"];options(e,t){const r={};r.Public=this.availableOptions.join(", "),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))}setupInterleaved(e,t,r){this.setupTracks[e.control]={control:e.control,protocol:"tcp",destination:t,codec:e.codec}}async setup(e,t){const r={};let i=t.transport;r.Session=this.session;const s=X(this.sdp).msections.find((t=>e.endsWith(t.control)));if(s){if(i.includes("TCP"))if(this.resolveInterleaved){const[e,t]=this.resolveInterleaved(s);this.setupInterleaved(s,e,t),i=`RTP/AVP/TCP;unicast;interleaved=${e}-${t}`}else{const e=i.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(e){const t=parseInt(e[1]),r=parseInt(e[2]);this.setupInterleaved(s,t,r)}}else{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,a=await T(),c=await I(a.port+1);this.client.on("close",(()=>a.server.close())),this.client.on("close",(()=>c.server.close())),this.setupTracks[s.control]={control:s.control,protocol:"udp",destination:parseInt(n),codec:s.codec,rtp:a.server,rtcp:c.server},i=i.replace("RTP/AVP/UDP","RTP/AVP").replace("RTP/AVP","RTP/AVP/UDP"),i+=`;server_port=${a.port}-${c.port}`}r.Transport=i,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(",");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),this.client.destroy()}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,r]=e[0].split(" ",2);t=t.toLowerCase();const i=oe(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,r,i,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",i,{}),this.client.destroy(),new Error("check request failed")}if(this[t]&&this.availableOptions.includes(t.toUpperCase()))return await this[t](r,i),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()))}destroy(){this.client.destroy();for(const e of Object.values(this.setupTracks))P(e.rtp),P(e.rtcp)}}const{systemManager:ue}=t();class pe{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 ue.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:me}=t();class he extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>me.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=me.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),me.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await me.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const{mediaManager:fe}=t();async function*ge(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 ye=["-movflags","frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer","-f","mp4"];function ve(e){return e}function Se(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],...ye],async*parse(e){const t=ge(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:ve}}const be=require("fs");var we=r.n(be);class _e extends le{segmentBytesWritten=0;constructor(e,t){super(e,t),this.client.on("close",(()=>{this.cleanup()})),this.availableOptions.push("WRITE","WRITESIZE")}cleanup(){const e=this.writeStream;this.writeStream=void 0,e?.end((()=>e?.destroy()))}write(e,t){this.cleanup(),this.segmentBytesWritten=0;const r=t["x-scrypted-rtsp-file"];if(!r)return this.respond(400,"Bad Request",t,{});this.writeStream=we().createWriteStream(r),this.respond(200,"OK",t,{})}writesize(e,t){this.respond(200,"OK",t,{"x-scrypted-rtsp-file-size":this.segmentBytesWritten.toString()})}writeRtpPayload(e,t){return this.writeStream?(this.segmentBytesWritten+=e.length+t.length,this.writeStream.write(e),this.writeStream.write(t)):super.writeRtpPayload(e,t)}}var Pe=r(415);const xe=require("stream");function Te(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 Ie(e,t,r,i,s){const n=X(e),o=y(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 Me(e,t,r,i,s){let n=!0;const o=new xe.EventEmitter;o.on("error",(t=>e.error("rebroadcast error",t)));const a=X(r),c=a.msections.find((e=>"audio"===e.type)),d=a.msections.find((e=>"video"===e.type)),l=c?.payloadTypes?.[0],u=d?.payloadTypes?.[0],p=c?.codec,m=d.codec;let h;const f=new Promise((e=>{h=e})),y=e=>{n&&(o.emit("killed"),o.emit("error",e||new Error("killed"))),n=!1,h(),t.destroy()};t.on("close",(()=>{y(new Error("rfc4751 socket closed"))})),t.on("error",(e=>{y(e)}));const{resetActivityTimer:v}=E("rtsp",y,o,s?.timeout);let S;const b=d?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],w=b?.split(",")?.[0];if(w)try{const t=Buffer.from(w,"base64"),r=(0,Pe.Qc)(t);S=Te(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,l=0;const u=()=>{l++,p()},p=()=>{for(;;){if(d!==l)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,u)){d++,a=void 0;continue}c=a.readUInt16BE(n)}}};throw p(),e.on("readable",p),await(0,g.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]=te,s===l?n[1]=0:s===u&&(n[1]=2),t=Buffer.concat([n,t]),s===l?i=p:s===u&&(i=m);const a={chunks:[t,r],type:i};if(!S){const t=se(a,7);if(t)try{const r=(0,Pe.Qc)(t);S=Te(r),e.log(S),e.log("parsed bitstream sps",r)}catch(t){e.warn("sps parsing failed"),S={width:NaN,height:NaN}}}o.emit("rtsp",a),v()}}).catch((e=>{throw e})).finally((()=>{y(new Error("parser exited"))}))},sdp:Promise.resolve([Buffer.from(r)]),inputAudioCodec:p,inputVideoCodec:m,get inputVideoResolution(){return S},get isActive(){return n},kill(e){y(e)},killed:f,resetActivityTimer:v,negotiateMediaStream:e=>Ie(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:Oe}=t(),Re="transcode",Ce="mixin:@scrypted/prebuffer-mixin";function De(){if(!Oe.getNativeIds().includes(Re))return;return Oe.getDeviceState(Re)?.id}class Ee extends e.ScryptedDeviceBase{constructor(e){super(Re),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(Ce))return[e.ScryptedInterface.Settings]}invalidateSettings(t){process.nextTick((async()=>{(await(this.plugin.currentMixins.get(t)?.mixin))?.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 Ae(e){if(!e)return;const t=e.find((e=>"cloud"!==e.source&&"rawvideo"!==e.container));return t?[t]:[]}function ke(e,t){if(t)return e.hasValue.enabledStreams?t.filter((t=>e.values.enabledStreams.includes(t.name))):Ae(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: 1280x720.",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: 1280x720.",hide:!0,prefersPrebuffer:!0,preferredResolution:921600}},r=new pe(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,rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number",hide:!1},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},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},missingCodecParameters:{group:"Transcoding",title:"Out of Band Codec Parameters",description:"Some cameras do not include H264 codec parameters 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}});function i(e,t){const i=ke(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(De())?{hide:!1}:{},s={transcodeStreams:i,missingCodecParameters:i,videoDecoderArguments:i,videoFilterArguments:i};try{const i=await e.mixinDevice.getVideoStreamOptions();return r={defaultValue:Ae(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:Ve,log:Le,systemManager:Ue,deviceManager:He}=t(),je="Default",Ne="AAC or No Audio",Fe=`${Ne} (Copy)`,qe="Compatible Audio",Ke="Other Audio",$e=["aac","mp3","mp2","opus"],Ge="-fflags +genpts",We="Scrypted (TCP)",ze="Scrypted (UDP)",Qe="FFmpeg (TCP)",Je="FFmpeg (UDP)",Ye="Default",Ze=[Ne,qe,Ke],Xe=["mpegts","mp4","rtsp"];class et{prebuffers={mp4:[],mpegts:[],rtsp:[]};usingScryptedParser=!1;usingScryptedUdpParser=!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)se(r,5)&&(t&&e.push(r.time-t),t=r.time)}if(!e.length)return;return e.reduce(((e,t)=>e+t),0)/e.length}get maxBitrate(){let e=parseInt(this.storage.getItem(this.maxBitrateKey));return e||(e=this.advertisedMediaStreamOptions?.video?.maxBitrate,this.storage.setItem(this.maxBitrateKey,e?.toString())),e||void 0}async resetBitrate(){this.console.log("Resetting bitrate after adaptive streaming session",this.maxBitrate),this.needBitrateReset=!1,this.mixinDevice.setVideoStreamOptions({id:this.streamId,video:{bitrate:this.maxBitrate}})}get streamId(){return this.advertisedMediaStreamOptions.id}get streamName(){return this.advertisedMediaStreamOptions.name}clearPrebuffers(){for(const e of Xe)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)||"";Ze.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ne),r=-1!==e.indexOf(qe),i=-1!==e.indexOf(Ke);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===Qe&&(r=Qe),i===Je&&(r=Je),e&&!r&&(i&&i!==Ye||(r=We),i===We&&(r=We),i===ze&&(r=ze)),r||(r=Qe)):r=Ye,{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:[Ye,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Ye});const l=()=>{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)||je,choices:[je,Fe,"Compatible Audio (Copy)","Other Audio (Transcode)"]})},u=()=>{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:Ge,choices:[Ge,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};let p=n;if(this.canUseRtspParser(this.advertisedMediaStreamOptions)){const e=o,r=e&&!this.getLastH264Oddities()?We:Qe,i=e?[We,ze]:[],s=this.storage.getItem(this.rtspParserKey)||Ye;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:[Ye,...i,Qe,Je]}),(s===Ye?r:s).includes("Scrypted")||(p=!0)}n&&l(),p&&u();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.streamSettings.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 l=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===l&&(l=null);let u=!1;d&&!r&&!i&&s&&void 0===l&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),u=!0);const p=void 0===l?i?.toLowerCase():l?.toLowerCase(),m=!$e.includes(p);!d||u||!1===t?.userConfigurable||r||m&&(s&&Le.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 y;this.audioDisabled=!1;const v=null===l;let S=!1;if(d&&!u&&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."),S=!0),r||u)y=["-an"],this.audioDisabled=!0;else if(a||S)y=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(n||v)y=["-acodec","copy"],y.push(...h);else if(o)y=["-acodec","copy"],y.push(...f);else{y=["-acodec","copy"];const e="aac"===p?h:f;y.push(...e)}const b=["-vcodec","copy"],_={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=_.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?se(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?se(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 le(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:b,acodec:r?y:["-acodec","copy"]});this.sdp=e.sdp,_.parsers.rtsp=e}else _.parsers.mpegts={container:"mpegts",outputArguments:[...(x={vcodec:b,acodec:y})?.vcodec||[],...x?.acodec||[],"-f","mpegts"],parse:(T=188,I=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],r=0;for(;;){const i=e.read();if(!i){await(0,g.once)(e,"readable");continue}if(t.push(i),r+=i.length,r<T)continue;const s=Buffer.concat(t);I?.(s);const n=s.length%T,o=s.slice(0,s.length-n),a=s.slice(s.length-n);t=[a],r=a.length,yield{chunks:[o]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let i=0;i<r.chunks.length;i++){const s=r.chunks[i];let n=0;for(;n+188<s.length;){const r=s.subarray(n,n+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);n+=188}}}return e}};var x,T,I;d&&(_.parsers.mp4=Se({vcodec:b,acodec:y}));const M=await this.mixinDevice.getVideoStream(t),O="x-scrypted/x-rfc4571"===M.mimeType;let R,C;this.storage.removeItem(this.lastDetectedAudioCodecKey),this.usingScryptedParser=!1;const D=this.getLastH264Oddities();if(c&&O){this.usingScryptedParser=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Ve.convertMediaObjectToJSON(M,"x-scrypted/x-rfc4571"),{url:t,sdp:r,mediaStreamOptions:i}=e;R=Me(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return w().connect(parseInt(t.port),t.hostname)}(t),r,i,_),this.sdp=R.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Ve.convertMediaObjectToBuffer(M,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());C=i.mediaStreamOptions||this.advertisedMediaStreamOptions;let{parser:s,isDefault:n}=this.getParser(c,C);if(this.usingScryptedParser=s===We||s===ze,this.usingScryptedUdpParser=s===ze,n&&this.usingScryptedParser&&D&&!this.stopInactive&&"scrypted"!==C.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=Qe),this.usingScryptedParser)R=await async function(e,t,r,i){let s=!0;const n=new xe.EventEmitter;n.on("error",(t=>e.error("rebroadcast error",t)));let o=[];const a=new de(t);a.console=e,a.requestTimeout=i.rtspRequestTimeout;const c=()=>{for(const e of o)P(e);a.safeTeardown()};let d;const l=new Promise((e=>{d=e})),u=e=>{s&&(n.emit("killed"),n.emit("error",e||new Error("killed"))),s=!1,d(),c()};a.client.on("close",(()=>{u(new Error("rtsp socket closed"))})),a.client.on("error",(e=>{u(e)}));const{resetActivityTimer:p}=E("rtsp",u,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=X(c);let m=0;const h={};let f;const{useUdp:g}=i,y=e=>{if(g&&e.session&&!f){const t=ae(e.session);f=parseInt(t.timeout)}};let v;g||(v={interleaved:new Map});const 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(te,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]+)/),[l,u,f]=d,{hostname:g}=new URL(a.url);r.send(c,parseInt(u),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?.()}}),i=r.interleaved?r.interleaved.begin:m;h[i]=t,v.interleaved.set(t,i)}m+=2};let b=!1;d.msections=d.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 d.msections)await S(e.control,e.codec);c=[...d.header.lines,...d.msections.map((e=>e.lines)).flat()].join("\r\n");const w=async()=>{try{await a.play(),a.console=void 0,await a.readLoop()}catch(e){u(e)}finally{u(new Error("rtsp read loop exited"))}};return(()=>{const t=d.msections.find((e=>"audio"===e.type)),i=d.msections.find((e=>"video"===e.type));if(!i)throw new Error("SDP does not contain a video section!");const o=t?.codec,a=i.codec;let m;const h=Date.now(),f=t=>{Date.now()-h>6e3&&n.removeListener("rtsp",f);const r=se(t,7);if(r){try{const t=(0,Pe.Qc)(r);m=Te(t),e.log("parsed bitstream sps",m)}catch(t){e.warn("sps parsing failed"),m={width:NaN,height:NaN}}n.removeListener("rtsp",f)}};m||n.on("rtsp",f);const g=i?.fmtp?.[0]?.parameters?.["sprop-parameter-sets"],y=g?.split(",")?.[0];if(y)try{const t=Buffer.from(y,"base64"),r=(0,Pe.Qc)(t);m=Te(r),e.log("parsed sdp sps",m)}catch(t){e.warn("sdp sps parsing failed")}return{parserSpecific:v,start:w,sdp:Promise.resolve([Buffer.from(c)]),inputAudioCodec:o,inputVideoCodec:a,get inputVideoResolution(){return m},get isActive(){return s},kill(e){u(e)},killed:l,resetActivityTimer:p,negotiateMediaStream:e=>Ie(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===ze,audioSoftMuted:r,rtspRequestTimeout:1e4}),this.sdp=R.sdp.then((e=>Buffer.concat(e).toString()));else{s===Je?i.inputArguments=["-rtsp_transport","udp","-i",i.url]:s===Qe&&(i.inputArguments=["-rtsp_transport","tcp","-i",i.url]);const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ge;i.inputArguments.unshift(...e.split(" ")),R=await A(i,_)}}if(this.usingScryptedParser){const e={};let t=!1;const r=r=>{if("h264"!==r.type)return;const s=ne(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,C);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"===C.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."),R.kill(new Error("restarting due to H264 oddity detection")),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e)),i(),void this.startPrebufferSession()}},i=()=>R.removeListener("rtsp",r);R.killed.finally((()=>clearTimeout(s))),R.on("rtsp",r);const s=setTimeout((()=>{i(),this.storage.setItem(this.lastH264ProbeKey,JSON.stringify(e))}),D?6e4:1e4)}!r&&i&&void 0!==R.inputAudioCodec&&R.inputAudioCodec!==i&&this.console.warn("Audio codec plugin reported vs detected mismatch",i,l);const k=t?.video?.codec;if(k&&void 0!==R.inputVideoCodec&&R.inputVideoCodec!==k&&this.console.warn("Video codec plugin reported vs detected mismatch",k,R.inputVideoCodec),R.inputAudioCodec?$e.includes(R.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",R.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,R.inputAudioCodec||"null"),"h264"!==R.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),u)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),R.kill(new Error("audio probe completed, restarting")),this.startPrebufferSession();if(this.parserSession=R,R.killed.finally((()=>{this.parserSession===R&&(this.parserSession=void 0)})),R.killed.finally((()=>clearTimeout(this.inactivityTimeout))),He.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),C?.refreshAt){let t,r=C;const i=async()=>{if(!R.isActive)return;const t=await this.mixinDevice.getVideoStream(r),i=await Ve.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(i.toString());r=n.mediaStreamOptions,s(r)},s=e=>{const r=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",r),t=setTimeout(i,r)};s(r),R.killed.finally((()=>clearTimeout(t)))}for(const e of Xe){let t=0,r=this.prebuffers[e];R.on(e,(i=>{const s=Date.now();for(i.time=s,r.push(i);r.length&&r[0].time<s-1e4;)r.shift(),t++;t>1e5&&(r=r.slice(),this.prebuffers[e]=r,t=0)}))}return R.start(),R}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t,r){this.activeClients||(this.needBitrateReset&&this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),this.stopInactive&&(this.inactivityTimeout&&!r||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.inactivityTimeout=void 0,this.activeClients?this.console.log("inactivity timeout found active clients."):(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),t.kill(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,i=e=>{for(const t of e.chunks)r.write(t);return r.writableLength},s=()=>{const e=o;o=void 0,r.destroy(),e?.()},n={writeData:e=>(e.startStream&&r.write(e.startStream),n.writeData=i,i(e)),destroy:s};let o=t?.connect(n);r.once("close",(()=>{s()})),r.on("error",(e=>t?.console?.log("client stream ended")))}(s,{connect:t=>{const s=Date.now(),o=(r,i)=>{if(e.filter&&!(r=e.filter(r,i)))return;t.writeData(r)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),a())},a=()=>{i.removeListener(r,o),i.removeListener("killed",a),t.destroy()};i.on(r,o),i.once("killed",a);const c=this.prebuffers[r];if("rtsp"!==r||!e.findSyncFrame||this.getLastH264Oddities())for(const e of c)e.time<s-n||o(e,!0);else{const e=this.parsers[r],t=c.filter((e=>e.time>=s-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=[]);for(const e of i)o(e,!0)}return a}})}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;if(null==i){const e="remote"===t?.destination?2e3:4e3;i=Math.min(e,this.getDetectedIdrInterval()||e)}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,l,u,p=await this.sdp;!c.video?.h264Info&&this.usingScryptedParser&&(c.video.h264Info=this.getLastH264Probe());let m=!1;const h=new Map,f=new Map;let g;if("rtsp"===a){const e=X(p),i=e.msections.find((e=>e.codec&&e.codec===c.video?.codec))||e.msections.find((e=>"video"===e.type));let s=e.msections.find((e=>e.codec&&e.codec===c.audio?.codec))||e.msections.find((e=>"audio"===e.type));null===c.audio&&(s=void 0),e.msections=e.msections.filter((e=>e===i||e===s));const n=void 0===t?.prebuffer,o=e.msections.find((e=>"video"===e.type))?.codec;p=e.toSdp(),u=(e,t)=>{if(t&&n&&e.type!==o)return;const r=h.get(e.type);if(m){if(void 0===r)return}else{if(null==r){const t=f.get(e.type);return void(t&&g.sendTrack(t.control,e.chunks[1],e.type.startsWith("rtcp-")))}const t=e.chunks.slice(),i=Buffer.from(t[0]);i.writeUInt8(r,1),t[0]=i,e={startStream:e.startStream,chunks:t}}if(!g.writeStream)return e;g.writeRtpPayload(e.chunks[0],e.chunks[1])};const a=await _();d=a.clientPromise.then((async e=>{if(p=G(p),g=new _e(e,p),r.parserSpecific){const e=r.parserSpecific;g.resolveInterleaved=t=>{const r=e.interleaved.get(t.codec);return[r,r+1]}}await g.handlePlayback(),g.handleTeardown().finally((()=>e.destroy()));for(const e of Object.values(g.setupTracks))"udp"!==e.protocol?(h.set(e.codec,e.destination),h.set(`rtcp-${e.codec}`,e.destination+1)):(f.set(e.codec,e),f.set(`rtcp-${e.codec}`,e));return m=r.parserSpecific&&0===f.size,e})),l=a.url.replace("tcp://","rtsp://")}else{const e=await _();d=e.clientPromise,l=e.url}c.sdp=p;const y=!1!==t?.refresh;this.handleRebroadcasterClient({findSyncFrame:e,isActiveClient:y,container:a,requestedPrebuffer:i,socketPromise:d,session:r,filter:u}),c.prebuffer=i;const{reencodeAudio:v}=this.getAudioConfig();this.audioDisabled?c.audio=null:v&&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 b=0;const w=this.prebuffers[a];for(const e of w)if(!(e.time<S-i))for(const t of e.chunks)b+=t.length;c.prebufferBytes=b;const P=["-analyzeduration","0","-probesize",Math.max(5e5,b).toString()];this.usingScryptedUdpParser||P.push("-reorder_queue_size","0");return{url:l,container:a,inputArguments:[...P,...this.parsers[a].inputArguments||[],"-f",this.parsers[a].container,"-i",l],mediaStreamOptions:c}}}class tt extends he{released=!1;sessions=new Map;streamSettings=Be(this);constructor(e,t){super(t),this.getTranscodeStorageSettings=e,this.delayStart(),this.startRtspServer()}startRtspServer(){P(this.rtspServer),this.rtspServer=new(w().Server)((async e=>{let t;const r=new le(e,void 0,!1,(async(e,i,s,n)=>{r.checkRequest=void 0;const o=new URL(i);for(const e of this.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=new Map;for(const[e,t]of Object.entries(r.setupTracks))i.set(t.codec,e);const s=await t.parserSessionPromise,n=Math.max(4e3,t.getDetectedIdrInterval()||4e3);t.handleRebroadcasterClient({findSyncFrame:!0,isActiveClient:!0,container:"rtsp",session:s,socketPromise:Promise.resolve(e),requestedPrebuffer:n,filter:(e,t)=>{const s=i.get(e.type);s&&r.sendTrack(s,e.chunks[1],!1)}}),await r.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.rtspServer.listen(this.streamSettings.storageSettings.values.rebroadcastPort||0),(0,g.once)(this.rtspServer,"listening").then((()=>{const e=this.rtspServer.address().port;this.streamSettings.storageSettings.values.rebroadcastPort=e}))}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,n=t?.id;this.sessions.has(n)||(n=void 0);const o=this.mixins?.includes(De()),a=await this.mixinDevice.getVideoStreamOptions();let c;const d=await this.getTranscodeStorageSettings(),l=2e6;if(!n){switch(t?.destination){case"medium-resolution":case"remote":c=this.streamSettings.getRemoteStream(a),s=d.remoteStreamingBitrate;break;case"low-resolution":c=this.streamSettings.getLowResolutionStream(a),s=512e3;break;case"local-recorder":c=this.streamSettings.getRecordingStream(a),s=l;break;case"remote-recorder":c=this.streamSettings.getRemoteRecordingStream(a),s=l;break;default:c=this.streamSettings.getDefaultStream(a),s=l}n=c.stream.id,this.console.log("Selected stream",c.stream.name),o&&this.streamSettings.storageSettings.values.transcodeStreams?.includes(c.title)&&(r=d.h264EncoderArguments?.split(" "),this.streamSettings.storageSettings.values.videoFilterArguments&&(i=this.streamSettings.storageSettings.values.videoFilterArguments))}let u,p=this.sessions.get(n);if(p.canPrebuffer||(this.console.log("Source container can not be prebuffered. Using a direct media stream."),p=void 0),p){const e=!(o||t?.container&&"rtsp"!==t?.container||"ffmpeg"===t?.tool);u=await p.getVideoStream(e,t)}else{const r=await this.mixinDevice.getVideoStream(t);if(!o)return r;u=await Ve.convertMediaObjectToJSON(r,e.ScryptedMimeTypes.FFmpegInput)}return u.h264EncoderArguments=r,u.destinationVideoBitrate=s,o&&this.streamSettings.storageSettings.values.missingCodecParameters&&(u.mediaStreamOptions||(u.mediaStreamOptions={id:n}),u.mediaStreamOptions.oobCodecParameters=!0),u.h264FilterArguments&&i?function(e,t,r="unfilteredRecording"){const i=e.findIndex((e=>"-filter_complex"===e));void 0!==i&&-1!==i?e[i+1]=e[i+1]+`[${r}] ; [${r}] ${t}`:e.push("-filter_complex",t)}(u.h264FilterArguments,i):i&&(u.h264FilterArguments=["-filter_complex",i]),o&&(u.videoDecoderArguments=this.streamSettings.storageSettings.values.videoDecoderArguments?.split(" ")),Ve.createFFmpegMediaObject(u,{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"),Le.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&&Le.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 et(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?")}}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 ke(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.online=!0,super.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 rt extends o{storageSettings=new pe(this,{});transcodeStorageSettings=new pe(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"].join(" "),combobox:!0,mapPut:(e,t)=>m()[t]?.join(" ")||t||["-c:v","libx264","-pix_fmt","yuvj420p","-preset","ultrafast","-bf","0"].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(Ue.getSystemState())){const t=Ue.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((()=>He.requestRestart()),r),process.nextTick((()=>{He.onDeviceDiscovered({nativeId:Re,name:"Transcoding",interfaces:[e.ScryptedInterface.Settings,e.ScryptedInterface.MixinProvider],type:e.ScryptedDeviceType.API})}))}getDevice(e){if(e===Re)return new Ee(this)}getSettings(){return this.storageSettings.getSettings()}putSetting(e,t){return this.storageSettings.putSetting(e,t)}async convert(e,t,r){const i=JSON.parse(e.toString()),{url:s,sdp:n}=i,o=X(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:l}=await _(),u={url:l,inputArguments:["-rtsp_transport","tcp","-i",l.replace("tcp","rtsp")]};return d.then((async e=>{const t=new le(e,n);t.console=this.console,await t.handlePlayback();const r=w().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(u))}async canMixin(t,r){if(!r.includes(e.ScryptedInterface.VideoCamera))return null;const i=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online,Ce];return r.includes(e.ScryptedInterface.VideoCameraConfiguration)&&i.push(e.ScryptedInterface.VideoCameraConfiguration),i}async getMixin(e,t,r){this.setHasEnabledMixin(r.id);{const i=it((async()=>this.transcodeStorageSettings.values),e,t,r);return this.currentMixins.set(r.id,{mixin:Promise.resolve(i),terminate:void 0}),i}}async releaseMixin(e,t){const r=this.currentMixins.get(e);this.currentMixins.delete(e);try{await t.release()}finally{r?.terminate?.()}}}async function it(e,t,r,i){return new tt(e,{mixinDevice:t,mixinDeviceState:i,mixinProviderNativeId:void 0,mixinDeviceInterfaces:r,group:"Stream Management",groupKey:"prebuffer"})}class st{newPrebufferMixin=it}async function nt(){return new st}const ot=rt})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in i)s[n]=i[n];i.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrypted/prebuffer-mixin",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.29",
|
|
4
4
|
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
|
5
5
|
"author": "Scrypted",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"type": "API",
|
|
28
28
|
"interfaces": [
|
|
29
29
|
"DeviceProvider",
|
|
30
|
-
"Settings",
|
|
31
30
|
"MixinProvider",
|
|
32
31
|
"BufferConverter"
|
|
33
32
|
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RtspServer, Headers } from "@scrypted/common/src/rtsp-server";
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
// non standard extension that dumps the rtp payload to a file.
|
|
6
|
+
export class FileRtspServer extends RtspServer {
|
|
7
|
+
writeStream: fs.WriteStream;
|
|
8
|
+
segmentBytesWritten = 0;
|
|
9
|
+
|
|
10
|
+
constructor(client: net.Socket, sdp?: string) {
|
|
11
|
+
super(client, sdp);
|
|
12
|
+
|
|
13
|
+
this.client.on('close', () => {
|
|
14
|
+
this.cleanup();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
this.availableOptions.push('WRITE', "WRITESIZE");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
cleanup() {
|
|
21
|
+
const ws = this.writeStream;
|
|
22
|
+
this.writeStream = undefined;
|
|
23
|
+
ws?.end(() => ws?.destroy());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
write(url: string, requestHeaders: Headers) {
|
|
27
|
+
this.cleanup();
|
|
28
|
+
this.segmentBytesWritten = 0;
|
|
29
|
+
|
|
30
|
+
const file = requestHeaders['x-scrypted-rtsp-file'];
|
|
31
|
+
|
|
32
|
+
if (!file)
|
|
33
|
+
return this.respond(400, 'Bad Request', requestHeaders, {});
|
|
34
|
+
|
|
35
|
+
this.writeStream = fs.createWriteStream(file);
|
|
36
|
+
|
|
37
|
+
this.respond(200, 'OK', requestHeaders, {});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
writesize(url: string, requestHeaders: Headers) {
|
|
41
|
+
this.respond(200, 'OK', requestHeaders, {
|
|
42
|
+
'x-scrypted-rtsp-file-size': this.segmentBytesWritten.toString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
writeRtpPayload(header: Buffer, rtp: Buffer): boolean {
|
|
47
|
+
if (!this.writeStream)
|
|
48
|
+
return super.writeRtpPayload(header, rtp);
|
|
49
|
+
|
|
50
|
+
this.segmentBytesWritten += header.length + rtp.length;
|
|
51
|
+
this.writeStream.write(header);
|
|
52
|
+
return this.writeStream.write(rtp);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -10,12 +10,14 @@ import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
|
|
10
10
|
import { StorageSettings } from '@scrypted/common/src/settings';
|
|
11
11
|
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
|
12
12
|
import { createFragmentedMp4Parser, createMpegTsParser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
|
13
|
-
import sdk, { BufferConverter, DeviceProvider, FFmpegInput, H264Info, MediaObject, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
|
|
13
|
+
import sdk, { BufferConverter, DeviceProvider, DeviceState, FFmpegInput, H264Info, MediaObject, MediaStreamOptions, MixinProvider, PluginFork, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
|
|
14
14
|
import crypto from 'crypto';
|
|
15
|
-
import
|
|
15
|
+
import { once } from 'events';
|
|
16
|
+
import net, { AddressInfo } from 'net';
|
|
16
17
|
import { Duplex } from 'stream';
|
|
18
|
+
import { FileRtspServer } from './file-rtsp-server';
|
|
17
19
|
import { connectRFC4571Parser, startRFC4571Parser } from './rfc4571';
|
|
18
|
-
import { startRtspSession } from './rtsp-session';
|
|
20
|
+
import { RtspSessionParserSpecific, startRtspSession } from './rtsp-session';
|
|
19
21
|
import { createStreamSettings, getPrebufferedStreams } from './stream-settings';
|
|
20
22
|
import { getTranscodeMixinProviderId, REBROADCAST_MIXIN_INTERFACE_TOKEN, TranscodeMixinProvider, TRANSCODE_MIXIN_PROVIDER_NATIVE_ID } from './transcode-settings';
|
|
21
23
|
|
|
@@ -465,7 +467,7 @@ class PrebufferSession {
|
|
|
465
467
|
title: 'RTSP Rebroadcast Url',
|
|
466
468
|
description: 'The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.',
|
|
467
469
|
readonly: true,
|
|
468
|
-
value: `rtsp://localhost:${this.mixin.
|
|
470
|
+
value: `rtsp://localhost:${this.mixin.streamSettings.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`,
|
|
469
471
|
});
|
|
470
472
|
}
|
|
471
473
|
|
|
@@ -947,7 +949,7 @@ class PrebufferSession {
|
|
|
947
949
|
|
|
948
950
|
handleRebroadcasterClient(socketPromise, {
|
|
949
951
|
// console: this.console,
|
|
950
|
-
connect: (
|
|
952
|
+
connect: (connection) => {
|
|
951
953
|
const now = Date.now();
|
|
952
954
|
|
|
953
955
|
const safeWriteData = (chunk: StreamChunk, prebuffer?: boolean) => {
|
|
@@ -956,7 +958,7 @@ class PrebufferSession {
|
|
|
956
958
|
if (!chunk)
|
|
957
959
|
return;
|
|
958
960
|
}
|
|
959
|
-
const buffered = writeData(chunk);
|
|
961
|
+
const buffered = connection.writeData(chunk);
|
|
960
962
|
if (buffered > 100000000) {
|
|
961
963
|
this.console.log('more than 100MB has been buffered, did downstream die? killing connection.', this.streamName);
|
|
962
964
|
cleanup();
|
|
@@ -966,7 +968,7 @@ class PrebufferSession {
|
|
|
966
968
|
const cleanup = () => {
|
|
967
969
|
session.removeListener(container, safeWriteData);
|
|
968
970
|
session.removeListener('killed', cleanup);
|
|
969
|
-
destroy();
|
|
971
|
+
connection.destroy();
|
|
970
972
|
}
|
|
971
973
|
|
|
972
974
|
session.on(container, safeWriteData);
|
|
@@ -1040,9 +1042,10 @@ class PrebufferSession {
|
|
|
1040
1042
|
let socketPromise: Promise<Duplex>;
|
|
1041
1043
|
let url: string;
|
|
1042
1044
|
let filter: (chunk: StreamChunk, prebuffer: boolean) => StreamChunk;
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1045
|
+
let interleavePassthrough = false;
|
|
1046
|
+
const interleavedMap = new Map<string, number>();
|
|
1047
|
+
const serverPortMap = new Map<string, RtspTrack>();
|
|
1048
|
+
let server: FileRtspServer;
|
|
1046
1049
|
|
|
1047
1050
|
if (container === 'rtsp') {
|
|
1048
1051
|
const parsedSdp = parseSdp(sdp);
|
|
@@ -1055,41 +1058,65 @@ class PrebufferSession {
|
|
|
1055
1058
|
const videoCodec = parsedSdp.msections.find(msection => msection.type === 'video')?.codec;
|
|
1056
1059
|
sdp = parsedSdp.toSdp();
|
|
1057
1060
|
filter = (chunk, prebuffer) => {
|
|
1058
|
-
const channel = codecMap.get(chunk.type);
|
|
1059
|
-
if (channel == undefined) {
|
|
1060
|
-
const udp = udpMap.get(chunk.type);
|
|
1061
|
-
if (udp)
|
|
1062
|
-
server.sendTrack(udp.control, chunk.chunks[1], chunk.type.startsWith('rtcp-'));
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
1061
|
// if no prebuffer is explicitly requested, don't send prebuffer audio
|
|
1066
1062
|
if (prebuffer && filterPrebufferAudio && chunk.type !== videoCodec)
|
|
1067
1063
|
return;
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1064
|
+
|
|
1065
|
+
const channel = interleavedMap.get(chunk.type);
|
|
1066
|
+
if (!interleavePassthrough) {
|
|
1067
|
+
if (channel == undefined) {
|
|
1068
|
+
const udp = serverPortMap.get(chunk.type);
|
|
1069
|
+
if (udp)
|
|
1070
|
+
server.sendTrack(udp.control, chunk.chunks[1], chunk.type.startsWith('rtcp-'));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const chunks = chunk.chunks.slice();
|
|
1075
|
+
const header = Buffer.from(chunks[0]);
|
|
1076
|
+
header.writeUInt8(channel, 1);
|
|
1077
|
+
chunks[0] = header;
|
|
1078
|
+
chunk = {
|
|
1079
|
+
startStream: chunk.startStream,
|
|
1080
|
+
chunks,
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
else if (channel === undefined) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (server.writeStream) {
|
|
1088
|
+
server.writeRtpPayload(chunk.chunks[0], chunk.chunks[1]);
|
|
1089
|
+
return;
|
|
1075
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
return chunk;
|
|
1076
1093
|
}
|
|
1077
1094
|
|
|
1078
1095
|
const client = await listenZeroSingleClient();
|
|
1079
1096
|
socketPromise = client.clientPromise.then(async (socket) => {
|
|
1080
1097
|
sdp = addTrackControls(sdp);
|
|
1081
|
-
server = new
|
|
1098
|
+
server = new FileRtspServer(socket, sdp);
|
|
1099
|
+
if (session.parserSpecific) {
|
|
1100
|
+
const parserSpecific = session.parserSpecific as RtspSessionParserSpecific;
|
|
1101
|
+
server.resolveInterleaved = msection => {
|
|
1102
|
+
const channel = parserSpecific.interleaved.get(msection.codec);
|
|
1103
|
+
return [channel, channel + 1];
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1082
1106
|
// server.console = this.console;
|
|
1083
1107
|
await server.handlePlayback();
|
|
1108
|
+
server.handleTeardown().finally(() => socket.destroy());
|
|
1084
1109
|
for (const track of Object.values(server.setupTracks)) {
|
|
1085
1110
|
if (track.protocol === 'udp') {
|
|
1086
|
-
|
|
1087
|
-
|
|
1111
|
+
serverPortMap.set(track.codec, track);
|
|
1112
|
+
serverPortMap.set(`rtcp-${track.codec}`, track);
|
|
1088
1113
|
continue;
|
|
1089
1114
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1115
|
+
interleavedMap.set(track.codec, track.destination);
|
|
1116
|
+
interleavedMap.set(`rtcp-${track.codec}`, track.destination + 1);
|
|
1092
1117
|
}
|
|
1118
|
+
|
|
1119
|
+
interleavePassthrough = session.parserSpecific && serverPortMap.size === 0;
|
|
1093
1120
|
return socket;
|
|
1094
1121
|
})
|
|
1095
1122
|
url = client.url.replace('tcp://', 'rtsp://');
|
|
@@ -1097,7 +1124,7 @@ class PrebufferSession {
|
|
|
1097
1124
|
else {
|
|
1098
1125
|
const client = await listenZeroSingleClient();
|
|
1099
1126
|
socketPromise = client.clientPromise;
|
|
1100
|
-
url =
|
|
1127
|
+
url = client.url;
|
|
1101
1128
|
}
|
|
1102
1129
|
|
|
1103
1130
|
mediaStreamOptions.sdp = sdp;
|
|
@@ -1176,11 +1203,83 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1176
1203
|
sessions = new Map<string, PrebufferSession>();
|
|
1177
1204
|
|
|
1178
1205
|
streamSettings = createStreamSettings(this);
|
|
1206
|
+
rtspServer: net.Server;
|
|
1179
1207
|
|
|
1180
|
-
constructor(public
|
|
1208
|
+
constructor(public getTranscodeStorageSettings: () => Promise<any>, options: SettingsMixinDeviceOptions<VideoCamera & VideoCameraConfiguration>) {
|
|
1181
1209
|
super(options);
|
|
1182
1210
|
|
|
1183
1211
|
this.delayStart();
|
|
1212
|
+
|
|
1213
|
+
this.startRtspServer();
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
startRtspServer() {
|
|
1217
|
+
closeQuiet(this.rtspServer);
|
|
1218
|
+
|
|
1219
|
+
this.rtspServer = new net.Server(async (client) => {
|
|
1220
|
+
let prebufferSession: PrebufferSession;
|
|
1221
|
+
|
|
1222
|
+
const server = new RtspServer(client, undefined, false, async (method, url, headers, rawMessage) => {
|
|
1223
|
+
server.checkRequest = undefined;
|
|
1224
|
+
|
|
1225
|
+
const u = new URL(url);
|
|
1226
|
+
|
|
1227
|
+
for (const session of this.sessions.values()) {
|
|
1228
|
+
if (u.pathname.endsWith(session.rtspServerPath)) {
|
|
1229
|
+
server.console = session.console;
|
|
1230
|
+
prebufferSession = session;
|
|
1231
|
+
prebufferSession.ensurePrebufferSession();
|
|
1232
|
+
await prebufferSession.parserSessionPromise;
|
|
1233
|
+
server.sdp = await prebufferSession.sdp;
|
|
1234
|
+
return true;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return false;
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
this.console.log('RTSP Rebroadcast connection started.')
|
|
1242
|
+
server.console = this.console;
|
|
1243
|
+
|
|
1244
|
+
try {
|
|
1245
|
+
await server.handlePlayback();
|
|
1246
|
+
const map = new Map<string, string>();
|
|
1247
|
+
for (const [id, track] of Object.entries(server.setupTracks)) {
|
|
1248
|
+
map.set(track.codec, id);
|
|
1249
|
+
}
|
|
1250
|
+
const session = await prebufferSession.parserSessionPromise;
|
|
1251
|
+
|
|
1252
|
+
const requestedPrebuffer = Math.max(4000, prebufferSession.getDetectedIdrInterval() || 4000);;
|
|
1253
|
+
|
|
1254
|
+
prebufferSession.handleRebroadcasterClient({
|
|
1255
|
+
findSyncFrame: true,
|
|
1256
|
+
isActiveClient: true,
|
|
1257
|
+
container: 'rtsp',
|
|
1258
|
+
session,
|
|
1259
|
+
socketPromise: Promise.resolve(client),
|
|
1260
|
+
requestedPrebuffer,
|
|
1261
|
+
filter: (chunk, prebuffer) => {
|
|
1262
|
+
const track = map.get(chunk.type);
|
|
1263
|
+
if (track)
|
|
1264
|
+
server.sendTrack(track, chunk.chunks[1], false);
|
|
1265
|
+
return undefined;
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
await server.handleTeardown();
|
|
1270
|
+
}
|
|
1271
|
+
catch (e) {
|
|
1272
|
+
client.destroy();
|
|
1273
|
+
}
|
|
1274
|
+
this.console.log('RTSP Rebroadcast connection finished.')
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
this.rtspServer.listen(this.streamSettings.storageSettings.values.rebroadcastPort || 0);
|
|
1278
|
+
|
|
1279
|
+
once(this.rtspServer, 'listening').then(() => {
|
|
1280
|
+
const port = (this.rtspServer.address() as AddressInfo).port;
|
|
1281
|
+
this.streamSettings.storageSettings.values.rebroadcastPort = port;
|
|
1282
|
+
})
|
|
1184
1283
|
}
|
|
1185
1284
|
|
|
1186
1285
|
delayStart() {
|
|
@@ -1211,6 +1310,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1211
1310
|
title: string;
|
|
1212
1311
|
};
|
|
1213
1312
|
|
|
1313
|
+
const transcodeStorageSettings = await this.getTranscodeStorageSettings();
|
|
1214
1314
|
const defaultLocalBitrate = 2000000;
|
|
1215
1315
|
const defaultLowResolutionBitrate = 512000;
|
|
1216
1316
|
if (!id) {
|
|
@@ -1218,7 +1318,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1218
1318
|
case 'medium-resolution':
|
|
1219
1319
|
case 'remote':
|
|
1220
1320
|
result = this.streamSettings.getRemoteStream(msos);
|
|
1221
|
-
destinationVideoBitrate =
|
|
1321
|
+
destinationVideoBitrate = transcodeStorageSettings.remoteStreamingBitrate;
|
|
1222
1322
|
break;
|
|
1223
1323
|
case 'low-resolution':
|
|
1224
1324
|
result = this.streamSettings.getLowResolutionStream(msos);
|
|
@@ -1245,7 +1345,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1245
1345
|
// for this reason, do not automatically supply h264 encoder arguments
|
|
1246
1346
|
// even if h264 is requested, to force a visible failure.
|
|
1247
1347
|
if (transcodingEnabled && this.streamSettings.storageSettings.values.transcodeStreams?.includes(result.title)) {
|
|
1248
|
-
h264EncoderArguments =
|
|
1348
|
+
h264EncoderArguments = transcodeStorageSettings.h264EncoderArguments?.split(' ');
|
|
1249
1349
|
if (this.streamSettings.storageSettings.values.videoFilterArguments)
|
|
1250
1350
|
videoFilterArguments = this.streamSettings.storageSettings.values.videoFilterArguments;
|
|
1251
1351
|
}
|
|
@@ -1462,6 +1562,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraCo
|
|
|
1462
1562
|
}
|
|
1463
1563
|
|
|
1464
1564
|
async release() {
|
|
1565
|
+
this.online = true;
|
|
1465
1566
|
super.release();
|
|
1466
1567
|
this.console.log('prebuffer session releasing if started');
|
|
1467
1568
|
this.released = true;
|
|
@@ -1488,13 +1589,8 @@ function millisUntilMidnight() {
|
|
|
1488
1589
|
}
|
|
1489
1590
|
|
|
1490
1591
|
export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinProvider, BufferConverter, Settings, DeviceProvider {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
title: 'Rebroadcast Port',
|
|
1494
|
-
description: 'The port of the RTSP server that will rebroadcast your streams.',
|
|
1495
|
-
type: 'number',
|
|
1496
|
-
},
|
|
1497
|
-
});
|
|
1592
|
+
// no longer in use, but kept for future use.
|
|
1593
|
+
storageSettings = new StorageSettings(this, {});
|
|
1498
1594
|
transcodeStorageSettings = new StorageSettings(this, {
|
|
1499
1595
|
remoteStreamingBitrate: {
|
|
1500
1596
|
title: 'Remote Streaming Bitrate',
|
|
@@ -1511,8 +1607,10 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
|
|
1511
1607
|
mapPut: (oldValue, newValue) => getH264EncoderArgs()[newValue]?.join(' ') || newValue || getDebugModeH264EncoderArgs().join(' '),
|
|
1512
1608
|
}
|
|
1513
1609
|
});
|
|
1514
|
-
|
|
1515
|
-
|
|
1610
|
+
currentMixins = new Map<string, {
|
|
1611
|
+
terminate(): Promise<number>,
|
|
1612
|
+
mixin: Promise<PrebufferMixin>,
|
|
1613
|
+
}>();
|
|
1516
1614
|
|
|
1517
1615
|
constructor(nativeId?: string) {
|
|
1518
1616
|
super(nativeId);
|
|
@@ -1542,8 +1640,6 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
|
|
1542
1640
|
this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(twoAM / 1000 / 60)} minutes`)
|
|
1543
1641
|
setTimeout(() => deviceManager.requestRestart(), twoAM);
|
|
1544
1642
|
|
|
1545
|
-
this.startRtspServer();
|
|
1546
|
-
|
|
1547
1643
|
process.nextTick(() => {
|
|
1548
1644
|
deviceManager.onDeviceDiscovered({
|
|
1549
1645
|
nativeId: TRANSCODE_MIXIN_PROVIDER_NATIVE_ID,
|
|
@@ -1570,75 +1666,6 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
|
|
1570
1666
|
return this.storageSettings.putSetting(key, value);
|
|
1571
1667
|
}
|
|
1572
1668
|
|
|
1573
|
-
startRtspServer() {
|
|
1574
|
-
closeQuiet(this.rtspServer);
|
|
1575
|
-
|
|
1576
|
-
this.rtspServer = new net.Server(async (client) => {
|
|
1577
|
-
let prebufferSession: PrebufferSession;
|
|
1578
|
-
|
|
1579
|
-
const server = new RtspServer(client, undefined, false, async (method, url, headers, rawMessage) => {
|
|
1580
|
-
server.checkRequest = undefined;
|
|
1581
|
-
|
|
1582
|
-
const u = new URL(url);
|
|
1583
|
-
|
|
1584
|
-
for (const id of this.currentMixins.keys()) {
|
|
1585
|
-
const mixin = this.currentMixins.get(id);
|
|
1586
|
-
for (const session of mixin.sessions.values()) {
|
|
1587
|
-
if (u.pathname.endsWith(session.rtspServerPath)) {
|
|
1588
|
-
server.console = session.console;
|
|
1589
|
-
prebufferSession = session;
|
|
1590
|
-
prebufferSession.ensurePrebufferSession();
|
|
1591
|
-
await prebufferSession.parserSessionPromise;
|
|
1592
|
-
server.sdp = await prebufferSession.sdp;
|
|
1593
|
-
return true;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
return false;
|
|
1599
|
-
});
|
|
1600
|
-
|
|
1601
|
-
this.console.log('RTSP Rebroadcast connection started.')
|
|
1602
|
-
server.console = this.console;
|
|
1603
|
-
|
|
1604
|
-
try {
|
|
1605
|
-
await server.handlePlayback();
|
|
1606
|
-
const map = new Map<string, string>();
|
|
1607
|
-
for (const [id, track] of Object.entries(server.setupTracks)) {
|
|
1608
|
-
map.set(track.codec, id);
|
|
1609
|
-
}
|
|
1610
|
-
const session = await prebufferSession.parserSessionPromise;
|
|
1611
|
-
|
|
1612
|
-
const requestedPrebuffer = Math.max(4000, prebufferSession.getDetectedIdrInterval() || 4000);;
|
|
1613
|
-
|
|
1614
|
-
prebufferSession.handleRebroadcasterClient({
|
|
1615
|
-
findSyncFrame: true,
|
|
1616
|
-
isActiveClient: true,
|
|
1617
|
-
container: 'rtsp',
|
|
1618
|
-
session,
|
|
1619
|
-
socketPromise: Promise.resolve(client),
|
|
1620
|
-
requestedPrebuffer,
|
|
1621
|
-
filter: (chunk, prebuffer) => {
|
|
1622
|
-
const track = map.get(chunk.type);
|
|
1623
|
-
if (track)
|
|
1624
|
-
server.sendTrack(track, chunk.chunks[1], false);
|
|
1625
|
-
return undefined;
|
|
1626
|
-
}
|
|
1627
|
-
});
|
|
1628
|
-
|
|
1629
|
-
await server.handleTeardown();
|
|
1630
|
-
}
|
|
1631
|
-
catch (e) {
|
|
1632
|
-
client.destroy();
|
|
1633
|
-
}
|
|
1634
|
-
this.console.log('RTSP Rebroadcast connection finished.')
|
|
1635
|
-
});
|
|
1636
|
-
|
|
1637
|
-
if (!this.storageSettings.values.rebroadcastPort)
|
|
1638
|
-
this.storageSettings.values.rebroadcastPort = Math.round(Math.random() * 10000 + 30000);
|
|
1639
|
-
|
|
1640
|
-
this.rtspServer.listen(this.storageSettings.values.rebroadcastPort);
|
|
1641
|
-
}
|
|
1642
1669
|
|
|
1643
1670
|
async convert(data: Buffer, fromMimeType: string, toMimeType: string): Promise<Buffer> {
|
|
1644
1671
|
const json = JSON.parse(data.toString());
|
|
@@ -1666,7 +1693,7 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
|
|
1666
1693
|
|
|
1667
1694
|
clientPromise.then(async (client) => {
|
|
1668
1695
|
const rtsp = new RtspServer(client, sdp);
|
|
1669
|
-
|
|
1696
|
+
rtsp.console = this.console;
|
|
1670
1697
|
await rtsp.handlePlayback();
|
|
1671
1698
|
const socket = net.connect(parseInt(u.port), u.hostname);
|
|
1672
1699
|
|
|
@@ -1704,25 +1731,61 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
|
|
1704
1731
|
return ret;
|
|
1705
1732
|
}
|
|
1706
1733
|
|
|
1707
|
-
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState:
|
|
1734
|
+
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState) {
|
|
1708
1735
|
this.setHasEnabledMixin(mixinDeviceState.id);
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1736
|
+
|
|
1737
|
+
// 8-11-2022
|
|
1738
|
+
// old scrypted had a bug where mixin device state was not exposing properties like id correctly
|
|
1739
|
+
// across rpc boundaries.
|
|
1740
|
+
if (false && sdk.fork && typeof mixinDeviceState.id === 'string') {
|
|
1741
|
+
const forked = sdk.fork<RebroadcastPluginFork>();
|
|
1742
|
+
const result = await forked.result as RebroadcastPluginFork;
|
|
1743
|
+
const ret = result.newPrebufferMixin(async () => this.transcodeStorageSettings.values, mixinDevice, mixinDeviceInterfaces, mixinDeviceState);
|
|
1744
|
+
this.currentMixins.set(mixinDeviceState.id, {
|
|
1745
|
+
terminate: () => forked.worker.terminate(),
|
|
1746
|
+
mixin: ret,
|
|
1747
|
+
});
|
|
1748
|
+
return ret;
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
const ret = newPrebufferMixin(async () => this.transcodeStorageSettings.values, mixinDevice, mixinDeviceInterfaces, mixinDeviceState);
|
|
1752
|
+
this.currentMixins.set(mixinDeviceState.id, {
|
|
1753
|
+
mixin: Promise.resolve(ret),
|
|
1754
|
+
terminate: undefined,
|
|
1755
|
+
});
|
|
1756
|
+
return ret;
|
|
1757
|
+
}
|
|
1719
1758
|
}
|
|
1720
1759
|
|
|
1721
|
-
async releaseMixin(id: string, mixinDevice:
|
|
1722
|
-
|
|
1723
|
-
await mixinDevice.release();
|
|
1760
|
+
async releaseMixin(id: string, mixinDevice: PrebufferMixin) {
|
|
1761
|
+
const current = this.currentMixins.get(id);
|
|
1724
1762
|
this.currentMixins.delete(id);
|
|
1763
|
+
try {
|
|
1764
|
+
await mixinDevice.release();
|
|
1765
|
+
}
|
|
1766
|
+
finally {
|
|
1767
|
+
current?.terminate?.();
|
|
1768
|
+
}
|
|
1725
1769
|
}
|
|
1726
1770
|
}
|
|
1727
1771
|
|
|
1728
|
-
|
|
1772
|
+
async function newPrebufferMixin(getTranscodeStorageSettings: () => Promise<any>, mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState) {
|
|
1773
|
+
return new PrebufferMixin(getTranscodeStorageSettings, {
|
|
1774
|
+
mixinDevice,
|
|
1775
|
+
mixinDeviceState,
|
|
1776
|
+
mixinProviderNativeId: undefined,
|
|
1777
|
+
mixinDeviceInterfaces,
|
|
1778
|
+
group: "Stream Management",
|
|
1779
|
+
groupKey: "prebuffer",
|
|
1780
|
+
})
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
class RebroadcastPluginFork {
|
|
1784
|
+
newPrebufferMixin = newPrebufferMixin;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
export async function fork() {
|
|
1788
|
+
return new RebroadcastPluginFork();
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
export default RebroadcastPlugin;
|
package/src/rtsp-session.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { getSpsResolution } from "./sps-resolution";
|
|
|
12
12
|
|
|
13
13
|
export type RtspChannelCodecMapping = { [key: number]: string };
|
|
14
14
|
|
|
15
|
+
export interface RtspSessionParserSpecific {
|
|
16
|
+
interleaved: Map<string, number>;
|
|
17
|
+
}
|
|
15
18
|
|
|
16
19
|
export async function startRtspSession(console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: {
|
|
17
20
|
useUdp: boolean,
|
|
@@ -24,7 +27,8 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
24
27
|
events.on('error', e => console.error('rebroadcast error', e));
|
|
25
28
|
|
|
26
29
|
let servers: dgram.Socket[] = [];
|
|
27
|
-
const rtspClient = new RtspClient(url
|
|
30
|
+
const rtspClient = new RtspClient(url);
|
|
31
|
+
rtspClient.console = console;
|
|
28
32
|
rtspClient.requestTimeout = options.rtspRequestTimeout;
|
|
29
33
|
|
|
30
34
|
const cleanupSockets = () => {
|
|
@@ -84,6 +88,13 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
let parserSpecific: RtspSessionParserSpecific;
|
|
92
|
+
if (!useUdp) {
|
|
93
|
+
parserSpecific = {
|
|
94
|
+
interleaved: new Map(),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
const doSetup = async (control: string, codec: string) => {
|
|
88
99
|
let udp: dgram.Socket;
|
|
89
100
|
if (useUdp) {
|
|
@@ -133,10 +144,9 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
133
144
|
},
|
|
134
145
|
});
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
mapping[channel] = codec;
|
|
147
|
+
const resultChannel = setupResult.interleaved ? setupResult.interleaved.begin : channel;
|
|
148
|
+
mapping[resultChannel] = codec;
|
|
149
|
+
parserSpecific.interleaved.set(codec, resultChannel);
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
channel += 2;
|
|
@@ -174,6 +184,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
174
184
|
const start = async () => {
|
|
175
185
|
try {
|
|
176
186
|
await rtspClient.play();
|
|
187
|
+
rtspClient.console = undefined;
|
|
177
188
|
await rtspClient.readLoop();
|
|
178
189
|
}
|
|
179
190
|
catch (e) {
|
|
@@ -245,6 +256,7 @@ export async function startRtspSession(console: Console, url: string, mediaStrea
|
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
return {
|
|
259
|
+
parserSpecific,
|
|
248
260
|
start,
|
|
249
261
|
sdp: Promise.resolve([Buffer.from(sdp)]),
|
|
250
262
|
inputAudioCodec,
|
package/src/stream-settings.ts
CHANGED
|
@@ -100,6 +100,12 @@ export function createStreamSettings(device: MixinDeviceBase<VideoCamera>) {
|
|
|
100
100
|
hide: false,
|
|
101
101
|
},
|
|
102
102
|
...streamTypes,
|
|
103
|
+
rebroadcastPort: {
|
|
104
|
+
title: 'Rebroadcast Port',
|
|
105
|
+
description: 'The port of the RTSP server that will rebroadcast your streams.',
|
|
106
|
+
type: 'number',
|
|
107
|
+
hide: false,
|
|
108
|
+
},
|
|
103
109
|
transcodeStreams: {
|
|
104
110
|
group: 'Transcoding',
|
|
105
111
|
title: 'Transcode Streams',
|
|
@@ -34,7 +34,10 @@ export class TranscodeMixinProvider extends ScryptedDeviceBase implements MixinP
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
invalidateSettings(id: string) {
|
|
37
|
-
process.nextTick(() =>
|
|
37
|
+
process.nextTick(async () =>{
|
|
38
|
+
const mixin = await this.plugin.currentMixins.get(id)?.mixin;
|
|
39
|
+
mixin?.onDeviceEvent(ScryptedInterface.Settings, undefined)
|
|
40
|
+
});
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
|