@scrypted/prebuffer-mixin 0.1.189 → 0.1.192

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- (()=>{var e={510:function(e,t,i){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,i,r){void 0===r&&(r=i),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,r){void 0===r&&(r=i),e[r]=t[i]}),n=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(i(268),t);const s=i(268);class o extends s.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}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=o;class a extends s.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}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=a,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var i of Object.values(s.ScryptedInterfaceProperty))Object.defineProperty(o.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(a.prototype,i,{set:t(i),get:e(i)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=c},268:(e,t,i)=>{"use strict";i.r(t),i.d(t,{DeviceBase:()=>r,ScryptedInterfaceProperty:()=>n,ScryptedInterfaceDescriptors:()=>s,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let n;!function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(n||(n={}));const s={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","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:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,p,l,h,m;!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.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(l||(l={})),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.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){e.AcceptUrlParameter="accept-url",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.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))}},t={};function i(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r].call(s.exports,s,s.exports,i),s.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";i.r(r),i.d(r,{default:()=>Ue});var e=i(510),t=i.n(e);const n=require("events"),{deviceManager:s}=t();class o extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,i=this.getMixinSettings(),r=[];try{const e=await t||[];r.push(...e)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}try{const e=await i||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=s.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return r}async putSetting(t,i){const r=this.settingsGroupKey+":";if(!t?.startsWith(r))return this.mixinDevice.putSetting(t,i);await this.putMixinSetting(t.substring(r.length),i),s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const a=require("net");var c=i.n(a);const d=require("child_process");var u=i.n(d);const p=require("dgram");var l=i.n(p);async function h(e,t){e.bind(t),await(0,n.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function m(e){return h(e,0)}async function f(e,t){return e.bind(t),await(0,n.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function g(){const e=new(c().Server),t=await async function(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}(e);return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:new Promise(((t,i)=>{const r=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(r),t(i)}))}))}}const y=require("process");var v=i.n(y);const S=["decode_slice_header error","no frame!","non-existing PPS"];function b(e,t,i,r){const n=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function s(e){const r=s=>{const o=s.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(n||i||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(o)};return r}t.stdout?.on("data",s(e.log)),t.stderr?.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];for(const e of t)try{const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}catch(t){i.push(e)}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=n=>{const s=n.toString(),o=s.indexOf(`${t}: `);if(-1!==o){const n=s.substring(o+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(n.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let n;function s(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(n),n=setTimeout(s,r))}return i.once("killed",(()=>clearTimeout(n))),o(),{resetActivityTimer:o}}async function C(e,t){const{console:i}=t;let r,s=!0;const o=new n.EventEmitter;let a,c,d,p,h;o.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{p=e,h=t}));function v(){s&&(o.emit("killed"),o.emit("error",new Error("killed"))),s=!1,D?.kill(),setTimeout((()=>D?.kill("SIGKILL")),1e3),h?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r)}const S=e.inputArguments.slice();r=setTimeout(v,3e4);let C=!1;const x=["pipe","pipe","pipe"];let O=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){C=!0;const i=l().createSocket("udp4"),n=await m(i),s=l().createSocket("udp4");await f(s,n.port+1),o.once("killed",(()=>{i.close(),s.close()})),S.push(...r.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{for await(const t of r.parseDatagram(i,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),a()})(),(async()=>{for await(const t of r.parseDatagram(s,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),o.emit(e,t),a()})()}else if(r.tcpProtocol){const n=await g(),s=new URL(r.tcpProtocol);s.port=n.port.toString(),S.push(...r.outputArguments,s.toString());const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{const t=await n.clientPromise;try{for await(const i of r.parse(t,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,i),a()}catch(e){i.error("rebroadcast parse error",e),v()}})()}else S.push(...r.outputArguments,"pipe:"+O++),x.push("pipe")}C&&(S.push("-sdp_file","pipe:"+O++),x.push("pipe")),S.unshift("-hide_banner"),w(i,S);const D=u().spawn(await P.getFFmpegPath(),S,{stdio:x});let k;b(i,D,void 0,t?.storage),D.on("exit",v),k=C?new Promise((e=>{const t=[];D.stdio[O-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let A=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const n=D.stdio[3+A];A++;try{const{resetActivityTimer:i}=M(e,v,o,t?.timeout);for await(const t of r.parse(n,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),i()}catch(e){i.error("rebroadcast parse error",e),v()}})),async function(e){return I(e,"Audio")}(D).then((e=>a=e)),async function(e){return I(e,"Video")}(D).then((e=>c=e)),async function(e){return new Promise((t=>{const i=r=>{const n=r.toString(),s=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);s&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(s))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(D).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:k,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>s,kill:v,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},emit(e,t){return o.emit(e,t),this},on(e,t){return o.on(e,t),this},once(e,t){return o.once(e,t),this},removeListener(e,t){return o.removeListener(e,t),this}}}async function x(e,t){const i=await e;let r=!0;const n=()=>{i.removeAllListeners(),i.destroy();const e=s;s=void 0,e?.()};let s=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),n);i.once("close",(()=>{n()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function O(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const n=()=>{const r=e.read(t);r&&(o(),i(r))},s=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",n),e.removeListener("end",s)};e.on("readable",n),e.on("end",s)}))}const D="\n".charCodeAt(0);async function k(e){return async function(e,t){const i=[];let r=0;for(;;){const n=await O(e,1);if(!n)throw new Error("end of stream");if(n[0]===t)break;i[r++]=n[0]}return Buffer.from(i).toString()}(e,D)}function A(e){return e}async function*T(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await O(e,i);yield{header:t,length:i,type:r,data:n}}}async function*E(e){let t,i,r;for await(const n of e)t?i||(i=n):t=n,yield{startStream:r,chunks:[n.header,n.data],type:n.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:R}=t(),_="v4";class L extends e.ScryptedDeviceBase{hasEnabledMixin={};constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=R.getComponent("plugins"),R.listen((async(t,i,r)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(R.getSystemState())){const t=R.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]===_)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();t.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==_&&(this.hasEnabledMixin[e]=_,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const{mediaManager:B}=t();function U(e,t,i=["recvonly","sendrecv"]){const r=e.split("m=").filter((e=>e.startsWith(t)));for(const e of r){const t=()=>{const t=e.split("\n").map((e=>e.trim())).find((e=>e.startsWith("a=control:")));return{section:"m="+e,trackId:t?.split("a=control:")?.[1]}};for(const r of i)if(e.includes(`a=${r}`))return t();if(i.includes("recvonly")&&!e.includes("sendonly")&&!e.includes("inactive"))return t()}}const j=require("crypto");var H=i.n(j);const F=require("tls");var V=i.n(F);const $=require("os");function N(e,t){for(var i=0;i<t.length;i++){var r=t[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function K(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function q(e){var t="function"==typeof Map?new Map:void 0;return q=function(e){if(null===e||(i=e,-1===Function.toString.call(i).indexOf("[native code]")))return e;var i;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,r)}function r(){return W(e,arguments,J(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),z(r,e)},q(e)}function W(e,t,i){return W=G()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var n=new(Function.bind.apply(e,r));return i&&z(n,i.prototype),n},W.apply(null,arguments)}function G(){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 z(e,t){return z=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},z(e,t)}function J(e){return J=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},J(e)}let Y=function(e){function t(e,i,...r){var n,s,o;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(r=(void 0===i?[]:[i]).concat(r),i=e,e=[]),s=this,(n=!(o=J(t).call(this,i))||"object"!=typeof o&&"function"!=typeof o?K(s):o).code=i||"E_UNEXPECTED",n.params=r,n.wrappedErrors=e,n.name=n.toString(),Error.captureStackTrace&&Error.captureStackTrace(K(n),n.constructor),n}var i,r,n;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&&z(e,t)}(t,e),i=t,(r=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+$.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&N(i.prototype,r),n&&N(i,n),t}(q(Error));function Q(e){return e instanceof Y||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&X(e.code)&&e.params&&e.params instanceof Array}function X(e){return/^([A-Z0-9_]+)$/.test(e)}Y.wrap=function(e,t,...i){let r=null;const n=X(e.message),s=(e.wrappedErrors||[]).concat(e);return t||(t=n?e.message:"E_UNEXPECTED"),e.message&&!n&&i.push(e.message),r=new Y(s,t,...i),r},Y.cast=function(e,...t){return Q(e)?e:Y.wrap.apply(Y,[e].concat(t))},Y.bump=function(e,...t){return Q(e)?Y.wrap.apply(Y,[e,e.code].concat(e.params)):Y.wrap.apply(Y,[e].concat(t))};const Z=Y,ee=/\w+=(".*?"|[^",]+)(?=,|$)/g;function te(e,t,i=[]){const r=e.trim().match(ee);if(!r)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",e);const n=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new Z("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return re(i,n),n}function ie(e,t,i=[]){return re(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")}function re(e,t){e.forEach((e=>{if(void 0===t[e])throw new Z("E_REQUIRED_KEY",e)}))}const ne=["realm","nonce"],se=[...ne,"domain","opaque","stale","algorithm","qop"],oe=["username","realm","nonce","uri","response"],ae=[...oe,"algorithm","cnonce","opaque","qop","nc"];function ce(e,t){const i=H().createHash(e);return i.update(t),i.digest("hex")}const de={type:"Digest",parseWWWAuthenticateRest:function(e){return te(e,se,ne)},buildWWWAuthenticateRest:function(e){return ie(e,se,ne)},parseAuthorizationRest:function(e){return te(e,ae,oe)},buildAuthorizationRest:function(e){return ie(e,ae,oe)},computeHash:function(e){const t=e.ha1||ce(e.algorithm,[e.username,e.realm,e.password].join(":")),i=ce(e.algorithm,[e.method,e.uri].join(":"));return ce(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};async function ue(e){let t=[];for(;;){let i=await k(e);if(i=i.trim(),!i)return t;t.push(i)}}function pe(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=r}return t}class le extends class{write(e,t,i){let r=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))r+=`${e}: ${i}\r\n`;r+="\r\n",this.client.write(r),i&&this.client.write(i)}async readMessage(){return ue(this.client)}}{cseq=0;constructor(e){super(),this.url=e;const t=new URL(e),i=parseInt(t.port)||554;e.startsWith("rtsps")?this.client=V().connect({rejectUnauthorized:!1,port:i,host:t.hostname}):this.client=c().connect(i,t.hostname)}writeRequest(e,t,i,r){t=t||{};let n=this.url;i&&(n+="/"+i);const s=new URL(n);s.username="",s.password="",n=s.toString();const o=`${e} ${n} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),this.authorization&&(t.Authorization=this.authorization),this.session&&(t.Session=this.session),this.write(o,t,r)}async request(e,t,i,r,n){this.writeRequest(e,t,i,r);const s=await this.readMessage(),o=s[0],a=pe(s);if(!o.includes("200")&&!a["www-authenticate"])throw new Error(o);if(a["www-authenticate"]){if(n)throw new Error("auth failed");const s=new URL(this.url),o=de.parseWWWAuthenticateRest(a["www-authenticate"]),c=H().createHash("md5").update(`${s.username}:${o.realm}:${s.password}`).digest("hex"),d=H().createHash("md5").update(`${e}:${s.pathname}`).digest("hex"),u=H().createHash("md5").update(`${c}:${o.nonce}:${d}`).digest("hex"),p={username:s.username,realm:o.realm,nonce:o.nonce,uri:s.pathname,algorithm:"MD5",response:u},l=Object.entries(p).map((([e,t])=>{return`${e}=${t&&(i=t,`"${i.replace(/"/g,'\\"')}"`)}`;var i})).join(", ");return this.authorization=`Digest ${l}`,this.request(e,t,i,r,!0)}const c=parseInt(a["content-length"]);return c?{headers:a,body:await O(this.client,c)}:{headers:a,body:void 0}}async options(){return this.request("OPTIONS",{})}async writeGetParameter(){return this.writeRequest("GET_PARAMETER",{})}async describe(e){return this.request("DESCRIBE",{...e||{},Accept:"application/sdp"})}async setup(e,t){const i={Transport:`RTP/AVP/TCP;unicast;interleaved=${e}-${e+1}`},r=await this.request("SETUP",i,t);if(r.headers.session){const e=function(e){const t={};for(const i of e.split(";")){const[e,r]=i.split("=",2);t[e]=r}return t}(r.headers.session);let t=parseInt(e.timeout);if(t){let e=setInterval((()=>this.writeGetParameter()),1e3*(t-5));this.client.once("close",(()=>clearInterval(e)))}this.session=r.headers.session.split(";")[0]}return r}async play(){return await this.request("PLAY",{Range:"npt=0.000-"}),this.client}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class he{udpPorts={video:0,audio:0};constructor(e,t,i){this.client=e,this.sdp=t,this.udp=i,this.session=(0,j.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await k(this.client);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await O(this.client,4),t=e.readUInt16BE(2),i=await O(this.client,t),r=e.readUInt8(1);yield{type:r-r%2===this.videoChannel?"video":"audio",rtcp:r%2==1,header:e,packet:i}}}send(e,t){const i=Buffer.alloc(4);i.writeUInt8(36,0),i.writeUInt8(t,1),i.writeUInt16BE(e.length,2),this.client.write(i),this.client.write(Buffer.from(e))}sendUdp(e,t,i){this.udp.send(t,i?e+1:e,"127.0.0.1")}sendVideo(e,t){if(this.udp&&this.udpPorts.video)this.sendUdp(this.udpPorts.video,e,t);else{if(null==this.videoChannel)throw new Error("rtsp videoChannel not set up");this.send(e,t?this.videoChannel+1:this.videoChannel)}}sendAudio(e,t){if(this.udp&&this.udpPorts.audio)this.sendUdp(this.udpPorts.audio,e,t);else{if(null==this.audioChannel)throw new Error("rtsp audioChannel not set up");this.send(e,t?this.audioChannel+1:this.audioChannel)}}options(e,t){const i={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,i)}describe(e,t){const i={};i["Content-Base"]=e,i["Content-Type"]="application/sdp",this.respond(200,"OK",t,i,Buffer.from(this.sdp))}setup(e,t){const i={},r=t.transport;i.Transport=t.transport,i.Session=this.session;let n=U(this.sdp,"audio"),s=U(this.sdp,"video");if(r.includes("UDP")){const t=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[i,o,a]=t;n&&e.includes(n.trackId)?this.udpPorts.audio=parseInt(o):s&&e.includes(s.trackId)?this.udpPorts.video=parseInt(o):this.console?.warn("unknown track id",e)}else if(r.includes("TCP")){const t=r.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);n&&e.includes(n.trackId)?this.audioChannel=i:s&&e.includes(s.trackId)?this.videoChannel=i:this.console?.warn("unknown track id",e)}}this.respond(200,"OK",t,i)}play(e,t){const i={};let r=U(this.sdp,"audio"),n=U(this.sdp,"video"),s="";r&&(s=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&n&&(s+=","),n&&(s+=`url=${e}/trackID=${n.trackId};seq=0;rtptime=0`),i["RTP-Info"]=s,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),r=await O(this.client,i);this.sdp=r.toString();const n={};n.Session=this.session,this.respond(200,"OK",t,n)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async teardown(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=pe(e);if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,n){let s=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),n&&(r["Content-Length"]=n.length.toString());for(const[e,t]of Object.entries(r))s+=`${e}: ${t}\r\n`;this.console?.log("response headers",s),s+="\r\n",this.client.write(s),n&&this.client.write(n)}}const me=require("stream"),{mediaManager:fe}=t();async function ge(e,t,i,r,n,s){let o=!0;const a=new me.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=parseInt(i.match(/m=audio.* ([0-9]+)/)?.[1]),d=parseInt(i.match(/m=video.* ([0-9]+)/)?.[1]),u=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!1,t.destroy()};let p,l;t.on("close",u),t.on("error",u),(async()=>{const{resetActivityTimer:e}=M("rtsp",u,a,s?.timeout);for(;;){let i,r;if(n){if(i=await O(t,4),"RTSP"===i.toString()){const e=pe(await ue(t)),i=parseInt(e["content-length"]);i&&await O(t,i);continue}r=i.readUInt16BE(2)}else i=await O(t,2),r=i.readUInt16BE(0);const s=await O(t,r),o=127&s[1];if(!n){const e=Buffer.alloc(2);e[0]=36,o===c?e[1]=0:o===d&&(e[1]=2),i=Buffer.concat([e,i])}let u;o===c?u="rtp-audio":o===d&&(u="rtp-video");const p={chunks:[i,s],type:u};a.emit("rtsp",p),e()}})().finally(u);const h=U(i,"audio"),m=U(i,"video");if(h){const e=h.section.toLowerCase();e.includes("mpeg4")?p="aac":e.includes("pcm")&&(p="pcm")}return m&&m.section.toLowerCase().includes("h264")&&(l="h264"),{sdp:Promise.resolve([Buffer.from(i)]),inputAudioCodec:p,inputVideoCodec:l,inputVideoResolution:void 0,isActive:()=>o,kill:u,mediaStreamOptions:r,emit(e,t){return a.emit(e,t),this},on(e,t){return a.on(e,t),this},once(e,t){return a.once(e,t),this},removeListener(e,t){return a.removeListener(e,t),this}}}const{mediaManager:ye,log:ve,systemManager:Se,deviceManager:be}=t(),we=1e4,Pe="prebufferDuration",Ie="sendKeyframe",Me="Default",Ce="AAC or No Audio",xe=`${Ce} (Copy)`,Oe="Compatible Audio",De="Other Audio",ke=["aac","mp3","mp2","opus"],Ae="-fflags +genpts",Te="Scrypted",Ee="FFmpeg",Re=[Ce,Oe,De],_e=["mpegts","mp4","rtsp"];class Le{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;constructor(e,t,i,r){this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";Re.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),i=-1!==e.indexOf(Oe),r=-1!==e.indexOf(De);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;const{mp4Mode:n,rtspMode:s}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const o=Date.now()-r,a=Math.round(i/o*8),c=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";if(e.push({title:"Audio Codec Transcoding",group:c,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Me,choices:[Me,xe,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:c,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:Ae,choices:[Ae,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0},{title:"Rebroadcast Container",group:c,description:"Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP may have lower latency and better responsiveness.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||"MPEG-TS"}),t){if(s&&"rtsp"===t.mediaStreamOptions?.container){const i=this.getParser(s,t.mediaStreamOptions);e.push({key:this.rtspParserKey,group:c,title:"RTSP Parser",description:"Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser may have lower latency and better responsiveness.",value:i,choices:[Ee,Te]})}e.push({key:"detectedResolution",group:c,title:"Detected Resolution and Bitrate",readonly:!0,value:`${t?.inputVideoResolution?.[0]||"unknown"} @ ${a||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:c,title:"Detected Video/Audio Codecs",readonly:!0,value:(t?.inputVideoCodec?.toString()||"unknown")+"/"+(t?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:c,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:(this.detectedIdrInterval||0)/1e3||"unknown"})}else e.push({title:"Status",group:c,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}getParser(e,t){const i=e&&"scrypted"===t?.tool?Te:Ee,r=this.storage.getItem(this.rtspParserKey);return r?r===Te?Te:Ee:i}getRebroadcastMode(){const e=this.storage.getItem(this.rebroadcastModeKey),t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),mp4Mode:!t||e?.includes("MP4")}}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(Pe))||we;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,s=i?.audio?.codec,{isUsingDefaultAudioConfig:o,aacAudio:a,compatibleAudio:d,reencodeAudio:p}=this.getAudioConfig();let l=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===l&&(l=null);let h=!1;r||s||!o||void 0!==l||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),h=!0),!r&&s&&void 0!==l&&l!==s&&this.console.warn("Audio codec plugin reported vs detected mismatch",s,l);const m=void 0===l?s?.toLowerCase():l?.toLowerCase(),{rtspMode:f,mp4Mode:g}=this.getRebroadcastMode(),y=!f||g,v=!ke.includes(m);!y||h||!1===i?.userConfigurable||r||v&&(o&&ve.a(`${this.mixin.name} is using the ${m} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",m));const S=["-bsf:a","aac_adtstoasc"],P=[];let I;this.audioDisabled=!1;const x=null===l;let O=!1;if(!h&&o&&v&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",m):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),O=!0),r||h)I=["-an"],this.audioDisabled=!0;else if(p||O)I=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(a||x)I=["-acodec","copy"],I.push(...S);else if(d)I=["-acodec","copy"],I.push(...P);else{I=["-acodec","copy"];const e="aac"===m?S:P;I.push(...e)}const D=["-vcodec","copy"],k={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=k.parsers,this.console.log("rebroadcast mode:",f?"rtsp":"mpegts"),f){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,j.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];if("rtp-video"===i.type){const r=31&i.chunks[1].readUInt8(12),n=i.chunks[1].readUInt8(13),s=31&n,o=128&n;if((28===r||29===r)&&5===s&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const n=new he(e);await n.handleSetup(),t(n.sdp);for await(const{type:e,rtcp:t,header:s,packet:o}of n.handleRecord())yield{chunks:[s,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:D,acodec:I});this.sdp=e.sdp,k.parsers.rtsp=e}else k.parsers.mpegts={container:"mpegts",outputArguments:[...(R={vcodec:D,acodec:I})?.vcodec||[],...R?.acodec||[],"-f","mpegts"],parse:(_=188,L=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],i=0;for(;;){const r=e.read();if(!r){await(0,n.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<_)continue;const s=Buffer.concat(t);L?.(s);const o=s.length%_,a=s.slice(0,s.length-o),c=s.slice(s.length-o);t=[c],i=c.length,yield{chunks:[a]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const n=i.chunks[r];let s=0;for(;s+188<n.length;){const i=n.subarray(s,s+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);s+=188}}}return e}};var R,_,L;g&&(k.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=T(e);yield*E(t)},findSyncFrame:A}}({vcodec:D,acodec:I}));const H=await this.mixinDevice.getVideoStream(i),F="x-scrypted/x-rfc4571"===H.mimeType;let V,$;this.storage.removeItem(this.lastDetectedAudioCodecKey);let N=!1;if(f&&F){N=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await ye.convertMediaObjectToJSON(H,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;V=await ge(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return c().connect(parseInt(t.port),t.hostname)}(t),i,r,!1,k),this.sdp=V.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await ye.convertMediaObjectToBuffer(H,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());$=i.mediaStreamOptions;if(this.getParser(f,i.mediaStreamOptions)===Te){N=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new le(i.url);await e.options();const t=(await e.describe()).body.toString().trim();this.sdp=Promise.resolve(t);const{audio:n,video:s}=function(e,t=["recvonly","sendrecv"]){return{audio:U(e,"audio",t)?.trackId,video:U(e,"video",t)?.trackId}}(t);let o=0;r||(await e.setup(o,n),o+=2),await e.setup(o,s);const a=await e.play();V=await ge(this.console,a,t,i.mediaStreamOptions,!0,k)}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ae;i.inputArguments.unshift(...e.split(" ")),V=await C(i,k)}}if(N&&g&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ae,r=await ye.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const n=await async function(e,t,i,r){const n=e.slice();n.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),n.unshift("-hide_banner"),w(r,n);const s=u().spawn(await B.getFFmpegPath(),n,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,s),{cp:s,generator:T(s.stdio[3])}}(r.inputArguments,I,D,this.console),s=()=>{n.cp.kill("SIGKILL"),V.kill(),n.generator.throw(new Error("killed"))};if(!V.isActive)return void s();V.once("killed",s);const{resetActivityTimer:o}=M("mp4",s,V,k.timeout);for await(const e of E(n.generator))o(),V.emit("mp4",e)})).catch((()=>{})),V.inputAudioCodec?ke.includes(V.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",V.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",V.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,V.inputAudioCodec||"null"),"h264"!==V.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),h)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),V.kill(),this.startPrebufferSession();if(this.parserSession=V,be.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),$?.refreshAt){let t,i=$;const r=async()=>{if(!V.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await ye.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),s=JSON.parse(r.toString());i=s.mediaStreamOptions,n(i)},n=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};n(i),V.once("killed",(()=>clearTimeout(t)))}V.once("killed",(()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===V&&(this.parserSession=void 0)}));for(const e of _e){let i=0;V.on(e,(r=>{const n=this.prebuffers[e],s=Date.now();if("mdat"===r.type&&(this.prevIdr&&(this.detectedIdrInterval=s-this.prevIdr),this.prevIdr=s),"rtp-video"===r.type){const e=31&r.chunks[1].readUInt8(12),t=r.chunks[1].readUInt8(13),i=31&t,n=128&t;(28!==e&&29!==e||5!==i||128!=n)&&5!=e||(this.prevIdr&&(this.detectedIdrInterval=s-this.prevIdr),this.prevIdr=s)}for(n.push({time:s,chunk:r});n.length&&n[0].time<s-t;)n.shift(),i++;i>1e3&&(this.prebuffers[e]=n.slice(),i=0)}))}return V}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e){this.printActiveClients(),this.activeClients||(this.stopInactive?(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"),e.kill())}),3e4)):0===this.activeClients&&this.console.log("stopInactive false"))}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(Ie);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:n}=this.getRebroadcastMode(),s=n?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:s;e?.prebuffer&&"mp4"!==o&&"mp4"===e?.container&&(r+=1.5*(this.detectedIdrInterval||4e3));const a=Object.assign({},t.mediaStreamOptions);a.prebuffer=r;const{reencodeAudio:c}=this.getAudioConfig();let d=!1;this.audioDisabled?a.audio=null:c?a.audio={codec:"aac",encoder:"aac",profile:"aac_low"}:d=!0,d&&(a.audio=t?.mediaStreamOptions?.audio?.codec===t?.inputAudioCodec?t?.mediaStreamOptions?.audio:{codec:t?.inputAudioCodec}),a.video&&t.inputVideoResolution?.[2]&&t.inputVideoResolution?.[3]&&Object.assign(a.video,{width:parseInt(t.inputVideoResolution[2]),height:parseInt(t.inputVideoResolution[3])});const u=Date.now();let p=0;const l=this.prebuffers[o];for(const e of l)if(!(e.time<u-r))for(const t of e.chunk.chunks)p+=t.length;const h=Math.max(5e5,p).toString(),m=await(async i=>{const n=this.prebuffers[i];let s,o;if("rtsp"===i){const e=await g();s=e.clientPromise.then((async e=>{let t=await this.sdp;t=function(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));const i=t.findIndex((e=>e.startsWith("m=video")));-1!==i&&t.splice(i+1,0,"a=control:trackID=video");const r=t.findIndex((e=>e.startsWith("m=audio")));return-1!==r&&t.splice(r+1,0,"a=control:trackID=audio"),t.join("\r\n")}(t);const i=new he(e,t);return await i.handlePlayback(),e})),o=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,o=`tcp://127.0.0.1:${e.port}`}const a=!1!==e?.refresh;return x(s,{connect:(e,s)=>{a&&(this.activeClients++,this.printActiveClients());const o=Date.now(),c=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{s(),t.removeListener(i,c),t.removeListener("killed",d)};t.on(i,c),t.once("killed",d);for(const e of n)e.time<o-r||c(e.chunk);return()=>{a&&(this.activeClients--,this.inactivityCheck(t)),d()}}}),o})(o),f={url:m,container:o,inputArguments:["-analyzeduration","0","-probesize",h,...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",m],mediaStreamOptions:a};return ye.createFFmpegMediaObject(f)}}class Be extends o{released=!1;sessions=new Map;constructor(e){super(e),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=e?.id;let i=this.sessions.get(t);return!i||e?.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t),r=i?i.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"),ve.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const o=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let a=0;const c=r.length;for(const e of s){let i=this.sessions.get(e);if(!i){const s=t?.find((t=>t.id===e));s?.prebuffer&&ve.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const d=s?.name,u=!r.includes(e);if(i=new Le(this,d,e,o||u),this.sessions.set(e,i),e===t?.[0]?.id&&this.sessions.set(void 0,i),o){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(u){this.console.log("stream",d,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;a++,e=!0,this.online=a==c,await(0,n.once)(t,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&a--,e=!1,this.online=a==c}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)")})()}}be.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);t?.length>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:i.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:Pe,value:this.storage.getItem(Pe)||we.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:Ie,value:("false"!==this.storage.getItem(Ie)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const i=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of i.values())e?.parserSessionPromise?.then((e=>e.kill()));this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const i=parseInt(this.storage.getItem(Pe))||we;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const Ue=new class extends L{constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(Se.getSystemState())){const t=Se.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const i=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(i/1e3/60)} minutes`),setTimeout((()=>be.requestRestart()),i)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:n,sdp:s}=r,{audioPayloadTypes:o,videoPayloadTypes:a}=function(e){const t=new Set,i=new Set,r=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},n=e.match(/m=audio.*/)?.[0];r(t,n?.split(" ").slice(3));const s=e.match(/m=video.*/)?.[0];return r(i,s?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(s),d=new URL(n);if(!d.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:u,url:p}=await g(),l={url:p,inputArguments:["-rtsp_transport","tcp","-i",p.replace("tcp","rtsp")]};return u.then((async e=>{const t=new he(e,s);await t.handlePlayback();const i=c().connect(parseInt(d.port),d.hostname);for(e.on("close",(()=>{i.destroy()})),i.on("close",(()=>{e.destroy()}));;){const r=(await O(i,2)).readInt16BE(0),n=await O(i,r),s=127&n[1];if(o.has(s))t.sendAudio(n,!1);else{if(!a.has(s))throw e.destroy(),i.destroy(),new Error("unknown payload type "+s);t.sendVideo(n,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){return i.includes(e.ScryptedInterface.VideoCamera)?[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new Be({mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"})}async releaseMixin(e,t){t.online=!0,t.release()}}})();var n=exports="undefined"==typeof exports?{}:exports;for(var s in r)n[s]=r[s];r.__esModule&&Object.defineProperty(n,"__esModule",{value:!0})})();
1
+ (()=>{var e={510:function(e,t,i){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,i,r){void 0===r&&(r=i),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,r){void 0===r&&(r=i),e[r]=t[i]}),n=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(i(268),t);const s=i(268);class o extends s.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}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=o;class a extends s.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}onDeviceEvent(e,t){return deviceManager.onMixinEvent(this.id,this,e,t)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=a,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState[e]=t}}for(var i of Object.values(s.ScryptedInterfaceProperty))Object.defineProperty(o.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(a.prototype,i,{set:t(i),get:e(i)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=c},268:(e,t,i)=>{"use strict";i.r(t),i.d(t,{DeviceBase:()=>r,ScryptedInterfaceProperty:()=>n,ScryptedInterfaceDescriptors:()=>s,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let n;!function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(n||(n={}));const s={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","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:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,p,l,h,m;!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.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(o||(o={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(l||(l={})),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.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){e.AcceptUrlParameter="accept-url",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.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))}},t={};function i(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r].call(s.exports,s,s.exports,i),s.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";i.r(r),i.d(r,{default:()=>je});var e=i(510),t=i.n(e);const n=require("events"),{deviceManager:s}=t();class o extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)))}async getSettings(){const t=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Settings)?this.mixinDevice.getSettings():void 0,i=this.getMixinSettings(),r=[];try{const e=await t||[];r.push(...e)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}try{const e=await i||[];for(const t of e)t.group=t.group||this.settingsGroup,t.key=this.settingsGroupKey+":"+t.key;r.push(...e)}catch(e){const t=s.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),r.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return r}async putSetting(t,i){const r=this.settingsGroupKey+":";if(!t?.startsWith(r))return this.mixinDevice.putSetting(t,i);await this.putMixinSetting(t.substring(r.length),i),s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await s.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const a=require("net");var c=i.n(a);const d=require("child_process");var u=i.n(d);const p=require("dgram");var l=i.n(p);async function h(e,t){e.bind(t),await(0,n.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function m(e){return h(e,0)}async function f(e,t){return e.bind(t),await(0,n.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function g(){const e=new(c().Server),t=await async function(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}(e);return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:new Promise(((t,i)=>{const r=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(r),t(i)}))}))}}const y=require("process");var v=i.n(y);const S=["decode_slice_header error","no frame!","non-existing PPS"];function b(e,t,i,r){const n=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function s(e){const r=s=>{const o=s.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(n||i||-1===o.indexOf("frame=")&&-1===o.indexOf("size=")))return e(o),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(o)};return r}t.stdout?.on("data",s(e.log)),t.stderr?.on("data",s(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];for(const e of t)try{const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}catch(t){i.push(e)}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=n=>{const s=n.toString(),o=s.indexOf(`${t}: `);if(-1!==o){const n=s.substring(o+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(n.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let n;function s(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(n),n=setTimeout(s,r))}return i.once("killed",(()=>clearTimeout(n))),o(),{resetActivityTimer:o}}async function C(e,t){const{console:i}=t;let r,s=!0;const o=new n.EventEmitter;let a,c,d,p,h;o.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{p=e,h=t}));function v(){s&&(o.emit("killed"),o.emit("error",new Error("killed"))),s=!1,D?.kill(),setTimeout((()=>D?.kill("SIGKILL")),1e3),h?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r)}const S=e.inputArguments.slice();r=setTimeout(v,3e4);let C=!1;const x=["pipe","pipe","pipe"];let O=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){C=!0;const i=l().createSocket("udp4"),n=await m(i),s=l().createSocket("udp4");await f(s,n.port+1),o.once("killed",(()=>{i.close(),s.close()})),S.push(...r.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{for await(const t of r.parseDatagram(i,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),a()})(),(async()=>{for await(const t of r.parseDatagram(s,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),o.emit(e,t),a()})()}else if(r.tcpProtocol){const n=await g(),s=new URL(r.tcpProtocol);s.port=n.port.toString(),S.push(...r.outputArguments,s.toString());const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{const t=await n.clientPromise;try{for await(const i of r.parse(t,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,i),a()}catch(e){i.error("rebroadcast parse error",e),v()}})()}else S.push(...r.outputArguments,"pipe:"+O++),x.push("pipe")}C&&(S.push("-sdp_file","pipe:"+O++),x.push("pipe")),S.unshift("-hide_banner"),w(i,S);const D=u().spawn(await P.getFFmpegPath(),S,{stdio:x});let A;b(i,D,void 0,t?.storage),D.on("exit",v),A=C?new Promise((e=>{const t=[];D.stdio[O-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let T=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const n=D.stdio[3+T];T++;try{const{resetActivityTimer:i}=M(e,v,o,t?.timeout);for await(const t of r.parse(n,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),o.emit(e,t),i()}catch(e){i.error("rebroadcast parse error",e),v()}})),async function(e){return I(e,"Audio")}(D).then((e=>a=e)),async function(e){return I(e,"Video")}(D).then((e=>c=e)),async function(e){return new Promise((t=>{const i=r=>{const n=r.toString(),s=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);s&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(s))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(D).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:A,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>s,kill:v,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},emit(e,t){return o.emit(e,t),this},on(e,t){return o.on(e,t),this},once(e,t){return o.once(e,t),this},removeListener(e,t){return o.removeListener(e,t),this}}}async function x(e,t){const i=await e;let r=!0;const n=()=>{i.removeAllListeners(),i.destroy();const e=s;s=void 0,e?.()};let s=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),n);i.once("close",(()=>{n()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function O(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const n=()=>{const r=e.read(t);r&&(o(),i(r))},s=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",n),e.removeListener("end",s)};e.on("readable",n),e.on("end",s)}))}const D="\n".charCodeAt(0);async function A(e){return async function(e,t){const i=[];let r=0;for(;;){const n=await O(e,1);if(!n)throw new Error("end of stream");if(n[0]===t)break;i[r++]=n[0]}return Buffer.from(i).toString()}(e,D)}function T(e){return e}async function*k(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await O(e,i);yield{header:t,length:i,type:r,data:n}}}async function*E(e){let t,i,r;for await(const n of e)t?i||(i=n):t=n,yield{startStream:r,chunks:[n.header,n.data],type:n.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:R}=t(),_="v4";class L extends e.ScryptedDeviceBase{hasEnabledMixin={};constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=R.getComponent("plugins"),R.listen((async(t,i,r)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(R.getSystemState())){const t=R.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]===_)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();t.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==_&&(this.hasEnabledMixin[e]=_,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const{mediaManager:U}=t();function B(e,t,i=["recvonly","sendrecv"]){const r=e.split("m=").filter((e=>e.startsWith(t)));for(const e of r){const t=()=>{const t=e.split("\n").map((e=>e.trim())).find((e=>e.startsWith("a=control:")));return{section:"m="+e,trackId:t?.split("a=control:")?.[1]}};for(const r of i)if(e.includes(`a=${r}`))return t();if(i.includes("recvonly")&&!e.includes("sendonly")&&!e.includes("inactive"))return t()}}const j=require("crypto");var H=i.n(j);const $=require("tls");var F=i.n($);const N=require("os");function V(e,t){for(var i=0;i<t.length;i++){var r=t[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function K(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function q(e){var t="function"==typeof Map?new Map:void 0;return q=function(e){if(null===e||(i=e,-1===Function.toString.call(i).indexOf("[native code]")))return e;var i;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,r)}function r(){return W(e,arguments,J(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),z(r,e)},q(e)}function W(e,t,i){return W=G()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var n=new(Function.bind.apply(e,r));return i&&z(n,i.prototype),n},W.apply(null,arguments)}function G(){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 z(e,t){return z=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},z(e,t)}function J(e){return J=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},J(e)}let Y=function(e){function t(e,i,...r){var n,s,o;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(r=(void 0===i?[]:[i]).concat(r),i=e,e=[]),s=this,(n=!(o=J(t).call(this,i))||"object"!=typeof o&&"function"!=typeof o?K(s):o).code=i||"E_UNEXPECTED",n.params=r,n.wrappedErrors=e,n.name=n.toString(),Error.captureStackTrace&&Error.captureStackTrace(K(n),n.constructor),n}var i,r,n;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&&z(e,t)}(t,e),i=t,(r=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+N.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&V(i.prototype,r),n&&V(i,n),t}(q(Error));function Q(e){return e instanceof Y||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&X(e.code)&&e.params&&e.params instanceof Array}function X(e){return/^([A-Z0-9_]+)$/.test(e)}Y.wrap=function(e,t,...i){let r=null;const n=X(e.message),s=(e.wrappedErrors||[]).concat(e);return t||(t=n?e.message:"E_UNEXPECTED"),e.message&&!n&&i.push(e.message),r=new Y(s,t,...i),r},Y.cast=function(e,...t){return Q(e)?e:Y.wrap.apply(Y,[e].concat(t))},Y.bump=function(e,...t){return Q(e)?Y.wrap.apply(Y,[e,e.code].concat(e.params)):Y.wrap.apply(Y,[e].concat(t))};const Z=Y,ee=/\w+=(".*?"|[^",]+)(?=,|$)/g;function te(e,t,i=[]){const r=e.trim().match(ee);if(!r)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",e);const n=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new Z("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return re(i,n),n}function ie(e,t,i=[]){return re(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")}function re(e,t){e.forEach((e=>{if(void 0===t[e])throw new Z("E_REQUIRED_KEY",e)}))}const ne=["realm","nonce"],se=[...ne,"domain","opaque","stale","algorithm","qop"],oe=["username","realm","nonce","uri","response"],ae=[...oe,"algorithm","cnonce","opaque","qop","nc"];function ce(e,t){const i=H().createHash(e);return i.update(t),i.digest("hex")}const de={type:"Digest",parseWWWAuthenticateRest:function(e){return te(e,se,ne)},buildWWWAuthenticateRest:function(e){return ie(e,se,ne)},parseAuthorizationRest:function(e){return te(e,ae,oe)},buildAuthorizationRest:function(e){return ie(e,ae,oe)},computeHash:function(e){const t=e.ha1||ce(e.algorithm,[e.username,e.realm,e.password].join(":")),i=ce(e.algorithm,[e.method,e.uri].join(":"));return ce(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};async function ue(e){let t=[];for(;;){let i=await A(e);if(i=i.trim(),!i)return t;t.push(i)}}function pe(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=r}return t}class le extends class{write(e,t,i){let r=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))r+=`${e}: ${i}\r\n`;r+="\r\n",this.client.write(r),i&&this.client.write(i)}async readMessage(){return ue(this.client)}}{cseq=0;constructor(e){super(),this.url=e;const t=new URL(e),i=parseInt(t.port)||554;e.startsWith("rtsps")?this.client=F().connect({rejectUnauthorized:!1,port:i,host:t.hostname}):this.client=c().connect(i,t.hostname)}writeRequest(e,t,i,r){t=t||{};let n=this.url;i&&(n+="/"+i);const s=new URL(n);s.username="",s.password="",n=s.toString();const o=`${e} ${n} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),this.authorization&&(t.Authorization=this.authorization),this.session&&(t.Session=this.session),this.write(o,t,r)}async request(e,t,i,r,n){this.writeRequest(e,t,i,r);const s=await this.readMessage(),o=s[0],a=pe(s);if(!o.includes("200")&&!a["www-authenticate"])throw new Error(o);if(a["www-authenticate"]){if(n)throw new Error("auth failed");const s=new URL(this.url),o=de.parseWWWAuthenticateRest(a["www-authenticate"]),c=H().createHash("md5").update(`${s.username}:${o.realm}:${s.password}`).digest("hex"),d=H().createHash("md5").update(`${e}:${s.pathname}`).digest("hex"),u=H().createHash("md5").update(`${c}:${o.nonce}:${d}`).digest("hex"),p={username:s.username,realm:o.realm,nonce:o.nonce,uri:s.pathname,algorithm:"MD5",response:u},l=Object.entries(p).map((([e,t])=>{return`${e}=${t&&(i=t,`"${i.replace(/"/g,'\\"')}"`)}`;var i})).join(", ");return this.authorization=`Digest ${l}`,this.request(e,t,i,r,!0)}const c=parseInt(a["content-length"]);return c?{headers:a,body:await O(this.client,c)}:{headers:a,body:void 0}}async options(){return this.request("OPTIONS",{})}async writeGetParameter(){return this.writeRequest("GET_PARAMETER",{})}async describe(e){return this.request("DESCRIBE",{...e||{},Accept:"application/sdp"})}async setup(e,t){const i={Transport:`RTP/AVP/TCP;unicast;interleaved=${e}-${e+1}`},r=await this.request("SETUP",i,t);if(r.headers.session){const e=function(e){const t={};for(const i of e.split(";")){const[e,r]=i.split("=",2);t[e]=r}return t}(r.headers.session);let t=parseInt(e.timeout);if(t){let e=setInterval((()=>this.writeGetParameter()),1e3*(t-5));this.client.once("close",(()=>clearInterval(e)))}this.session=r.headers.session.split(";")[0]}return r}async play(){return await this.request("PLAY",{Range:"npt=0.000-"}),this.client}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class he{udpPorts={video:0,audio:0};constructor(e,t,i){this.client=e,this.sdp=t,this.udp=i,this.session=(0,j.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await A(this.client);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await O(this.client,4),t=e.readUInt16BE(2),i=await O(this.client,t),r=e.readUInt8(1);yield{type:r-r%2===this.videoChannel?"video":"audio",rtcp:r%2==1,header:e,packet:i}}}send(e,t){const i=Buffer.alloc(4);i.writeUInt8(36,0),i.writeUInt8(t,1),i.writeUInt16BE(e.length,2),this.client.write(i),this.client.write(Buffer.from(e))}sendUdp(e,t,i){this.udp.send(t,i?e+1:e,"127.0.0.1")}sendVideo(e,t){if(this.udp&&this.udpPorts.video)this.sendUdp(this.udpPorts.video,e,t);else{if(null==this.videoChannel)throw new Error("rtsp videoChannel not set up");this.send(e,t?this.videoChannel+1:this.videoChannel)}}sendAudio(e,t){if(this.udp&&this.udpPorts.audio)this.sendUdp(this.udpPorts.audio,e,t);else{if(null==this.audioChannel)throw new Error("rtsp audioChannel not set up");this.send(e,t?this.audioChannel+1:this.audioChannel)}}options(e,t){const i={Public:"DESCRIBE, OPTIONS, PAUSE, PLAY, SETUP, TEARDOWN, ANNOUNCE, RECORD"};this.respond(200,"OK",t,i)}describe(e,t){const i={};i["Content-Base"]=e,i["Content-Type"]="application/sdp",this.respond(200,"OK",t,i,Buffer.from(this.sdp))}setup(e,t){const i={},r=t.transport;i.Transport=t.transport,i.Session=this.session;let n=B(this.sdp,"audio"),s=B(this.sdp,"video");if(r.includes("UDP")){const t=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[i,o,a]=t;n&&e.includes(n.trackId)?this.udpPorts.audio=parseInt(o):s&&e.includes(s.trackId)?this.udpPorts.video=parseInt(o):this.console?.warn("unknown track id",e)}else if(r.includes("TCP")){const t=r.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);n&&e.includes(n.trackId)?this.audioChannel=i:s&&e.includes(s.trackId)?this.videoChannel=i:this.console?.warn("unknown track id",e)}}this.respond(200,"OK",t,i)}play(e,t){const i={};let r=B(this.sdp,"audio"),n=B(this.sdp,"video"),s="";r&&(s=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&n&&(s+=","),n&&(s+=`url=${e}/trackID=${n.trackId};seq=0;rtptime=0`),i["RTP-Info"]=s,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),r=await O(this.client,i);this.sdp=r.toString();const n={};n.Session=this.session,this.respond(200,"OK",t,n)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async teardown(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=pe(e);if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,n){let s=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),n&&(r["Content-Length"]=n.length.toString());for(const[e,t]of Object.entries(r))s+=`${e}: ${t}\r\n`;this.console?.log("response headers",s),s+="\r\n",this.client.write(s),n&&this.client.write(n)}}const me=require("stream"),{mediaManager:fe}=t();async function ge(e,t,i,r,n,s){let o=!0;const a=new me.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=parseInt(i.match(/m=audio.* ([0-9]+)/)?.[1]),d=parseInt(i.match(/m=video.* ([0-9]+)/)?.[1]),u=()=>{o&&(a.emit("killed"),a.emit("error",new Error("killed"))),o=!1,t.destroy()};let p,l;t.on("close",u),t.on("error",u),(async()=>{const{resetActivityTimer:e}=M("rtsp",u,a,s?.timeout);for(;;){let i,r;if(n){if(i=await O(t,4),"RTSP"===i.toString()){const e=pe(await ue(t)),i=parseInt(e["content-length"]);i&&await O(t,i);continue}r=i.readUInt16BE(2)}else i=await O(t,2),r=i.readUInt16BE(0);const s=await O(t,r),o=127&s[1];if(!n){const e=Buffer.alloc(2);e[0]=36,o===c?e[1]=0:o===d&&(e[1]=2),i=Buffer.concat([e,i])}let u;o===c?u="rtp-audio":o===d&&(u="rtp-video");const p={chunks:[i,s],type:u};a.emit("rtsp",p),e()}})().finally(u);const h=B(i,"audio"),m=B(i,"video");if(h){const e=h.section.toLowerCase();e.includes("mpeg4")?p="aac":e.includes("pcm")&&(p="pcm")}return m&&m.section.toLowerCase().includes("h264")&&(l="h264"),{sdp:Promise.resolve([Buffer.from(i)]),inputAudioCodec:p,inputVideoCodec:l,inputVideoResolution:void 0,isActive:()=>o,kill:u,mediaStreamOptions:r,emit(e,t){return a.emit(e,t),this},on(e,t){return a.on(e,t),this},once(e,t){return a.once(e,t),this},removeListener(e,t){return a.removeListener(e,t),this}}}const{mediaManager:ye,log:ve,systemManager:Se,deviceManager:be}=t(),we=1e4,Pe="prebufferDuration",Ie="sendKeyframe",Me="Default",Ce="AAC or No Audio",xe=`${Ce} (Copy)`,Oe="Compatible Audio",De="Other Audio",Ae=["aac","mp3","mp2","opus"],Te="-fflags +genpts",ke="Scrypted",Ee="FFmpeg",Re="Default",_e=[Ce,Oe,De],Le=["mpegts","mp4","rtsp"];class Ue{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;constructor(e,t,i,r){this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";_e.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),i=-1!==e.indexOf(Oe),r=-1!==e.indexOf(De);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}canUseRtspParser(e){if(e)return!1;const{isUsingDefaultAudioConfig:t,compatibleAudio:i,aacAudio:r}=this.getAudioConfig();return t||i||r}getParser(e,t,i){if(!this.canUseRtspParser(t))return Ee;const r=e&&"scrypted"===i?.tool?ke:Ee,n=this.storage.getItem(this.rtspParserKey);return n&&n!==Re?n===ke?ke:n===Ee?Ee:r:r}getRebroadcastMode(){const e=this.storage.getItem(this.rebroadcastModeKey),t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),muxingMp4:!t||e?.includes("MP4")}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;const{muxingMp4:n,rtspMode:s}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const o=Date.now()-r,a=Math.round(i/o*8),c=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";e.push({title:"Audio Codec Transcoding",group:c,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Me,choices:[Me,xe,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"Rebroadcast Container",group:c,description:'Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default is "MPEG-TS" for this camera.',placeholder:"MPEG-TS",choices:[Re,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Re});const d=()=>{e.push({title:"FFmpeg Input Arguments Prefix",group:c,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:Te,choices:[Te,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(t){if(this.canUseRtspParser(n)&&s&&"rtsp"===t.mediaStreamOptions?.container){const i=this.getParser(s,n,t.mediaStreamOptions);e.push({key:this.rtspParserKey,group:c,title:"RTSP Parser",description:`Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser is lower latency. The Scrypted Parser is only available when the Audo Codec is not Transcoding and the Rebroadcast Container is RTSP. The default is "${i}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||Re,choices:[Re,Ee,ke]}),i!==ke&&d()}else d();e.push({key:"detectedResolution",group:c,title:"Detected Resolution and Bitrate",readonly:!0,value:`${t?.inputVideoResolution?.[0]||"unknown"} @ ${a||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:c,title:"Detected Video/Audio Codecs",readonly:!0,value:(t?.inputVideoCodec?.toString()||"unknown")+"/"+(t?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:c,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:(this.detectedIdrInterval||0)/1e3||"unknown"})}else d(),e.push({title:"Status",group:c,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0});return e}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(Pe))||we;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,s=i?.audio?.codec,{isUsingDefaultAudioConfig:o,aacAudio:a,compatibleAudio:d,reencodeAudio:p}=this.getAudioConfig(),{rtspMode:l,muxingMp4:h}=this.getRebroadcastMode();let m=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===m&&(m=null);let f=!1;h&&!r&&!s&&o&&void 0===m&&(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),f=!0),!r&&s&&void 0!==m&&m!==s&&this.console.warn("Audio codec plugin reported vs detected mismatch",s,m);const g=void 0===m?s?.toLowerCase():m?.toLowerCase(),y=!Ae.includes(g);!h||f||!1===i?.userConfigurable||r||y&&(o&&ve.a(`${this.mixin.name} is using the ${g} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",g));const v=["-bsf:a","aac_adtstoasc"],S=[];let P;this.audioDisabled=!1;const I=null===m;let x=!1;if(!f&&o&&y&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",g):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),x=!0),r||f)P=["-an"],this.audioDisabled=!0;else if(p||x)P=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(a||I)P=["-acodec","copy"],P.push(...v);else if(d)P=["-acodec","copy"],P.push(...S);else{P=["-acodec","copy"];const e="aac"===g?v:S;P.push(...e)}const O=["-vcodec","copy"],D={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=D.parsers,this.console.log("rebroadcast mode:",l?"rtsp":"mpegts"),l){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,j.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];if("rtp-video"===i.type){const r=31&i.chunks[1].readUInt8(12),n=i.chunks[1].readUInt8(13),s=31&n,o=128&n;if((28===r||29===r)&&5===s&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const n=new he(e);await n.handleSetup(),t(n.sdp);for await(const{type:e,rtcp:t,header:s,packet:o}of n.handleRecord())yield{chunks:[s,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:O,acodec:P});this.sdp=e.sdp,D.parsers.rtsp=e}else D.parsers.mpegts={container:"mpegts",outputArguments:[...(A={vcodec:O,acodec:P})?.vcodec||[],...A?.acodec||[],"-f","mpegts"],parse:(R=188,_=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],i=0;for(;;){const r=e.read();if(!r){await(0,n.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<R)continue;const s=Buffer.concat(t);_?.(s);const o=s.length%R,a=s.slice(0,s.length-o),c=s.slice(s.length-o);t=[c],i=c.length,yield{chunks:[a]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const n=i.chunks[r];let s=0;for(;s+188<n.length;){const i=n.subarray(s,s+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);s+=188}}}return e}};var A,R,_;h&&(D.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=k(e);yield*E(t)},findSyncFrame:T}}({vcodec:O,acodec:P}));const L=await this.mixinDevice.getVideoStream(i),H="x-scrypted/x-rfc4571"===L.mimeType;let $,F;this.storage.removeItem(this.lastDetectedAudioCodecKey);let N=!1;if(l&&H){N=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await ye.convertMediaObjectToJSON(L,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;$=await ge(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return c().connect(parseInt(t.port),t.hostname)}(t),i,r,!1,D),this.sdp=$.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await ye.convertMediaObjectToBuffer(L,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());F=i.mediaStreamOptions;if(this.getParser(l,h,i.mediaStreamOptions)===ke){N=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new le(i.url);await e.options();const t=(await e.describe()).body.toString().trim();this.sdp=Promise.resolve(t);const{audio:n,video:s}=function(e,t=["recvonly","sendrecv"]){return{audio:B(e,"audio",t)?.trackId,video:B(e,"video",t)?.trackId}}(t);let o=0;r||(await e.setup(o,n),o+=2),await e.setup(o,s);const a=await e.play();$=await ge(this.console,a,t,i.mediaStreamOptions,!0,D)}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Te;i.inputArguments.unshift(...e.split(" ")),$=await C(i,D)}}if(N&&h&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||Te,r=await ye.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const n=await async function(e,t,i,r){const n=e.slice();n.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),n.unshift("-hide_banner"),w(r,n);const s=u().spawn(await U.getFFmpegPath(),n,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,s),{cp:s,generator:k(s.stdio[3])}}(r.inputArguments,P,O,this.console),s=()=>{n.cp.kill("SIGKILL"),$.kill(),n.generator.throw(new Error("killed"))};if(!$.isActive)return void s();$.once("killed",s);const{resetActivityTimer:o}=M("mp4",s,$,D.timeout);for await(const e of E(n.generator))o(),$.emit("mp4",e)})).catch((()=>{})),$.inputAudioCodec?Ae.includes($.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",$.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",$.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,$.inputAudioCodec||"null"),"h264"!==$.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),f)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),$.kill(),this.startPrebufferSession();if(this.parserSession=$,be.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),F?.refreshAt){let t,i=F;const r=async()=>{if(!$.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await ye.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),s=JSON.parse(r.toString());i=s.mediaStreamOptions,n(i)},n=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};n(i),$.once("killed",(()=>clearTimeout(t)))}$.once("killed",(()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===$&&(this.parserSession=void 0)}));for(const i of Le){let r=0;$.on(i,(n=>{const s=this.prebuffers[i],o=Date.now(),a=()=>{if(this.prevIdr){const t=!this.detectedIdrInterval;this.detectedIdrInterval=o-this.prevIdr,t&&be.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}this.prevIdr=o};if("mdat"===n.type&&a(),"rtp-video"===n.type){const e=31&n.chunks[1].readUInt8(12),t=n.chunks[1].readUInt8(13),i=31&t,r=128&t;(28!==e&&29!==e||5!==i||128!=r)&&5!=e||a()}for(s.push({time:o,chunk:n});s.length&&s[0].time<o-t;)s.shift(),r++;r>1e3&&(this.prebuffers[i]=s.slice(),r=0)}))}return $}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e){this.printActiveClients(),this.activeClients||(this.stopInactive?(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"),e.kill())}),3e4)):0===this.activeClients&&this.console.log("stopInactive false"))}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(Ie);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:n}=this.getRebroadcastMode(),s=n?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:s;e?.prebuffer&&"mp4"!==o&&"mp4"===e?.container&&(r+=1.5*(this.detectedIdrInterval||4e3));const a=Object.assign({},t.mediaStreamOptions);a.prebuffer=r;const{reencodeAudio:c}=this.getAudioConfig();let d=!1;this.audioDisabled?a.audio=null:c?a.audio={codec:"aac",encoder:"aac",profile:"aac_low"}:d=!0,d&&(a.audio=t?.mediaStreamOptions?.audio?.codec===t?.inputAudioCodec?t?.mediaStreamOptions?.audio:{codec:t?.inputAudioCodec}),a.video&&t.inputVideoResolution?.[2]&&t.inputVideoResolution?.[3]&&Object.assign(a.video,{width:parseInt(t.inputVideoResolution[2]),height:parseInt(t.inputVideoResolution[3])});const u=Date.now();let p=0;const l=this.prebuffers[o];for(const e of l)if(!(e.time<u-r))for(const t of e.chunk.chunks)p+=t.length;const h=Math.max(5e5,p).toString(),m=await(async i=>{const n=this.prebuffers[i];let s,o;if("rtsp"===i){const e=await g();s=e.clientPromise.then((async e=>{let t=await this.sdp;t=function(e){let t=e.split("\n").map((e=>e.trim()));t=t.filter((e=>!e.includes("a=control:")));const i=t.findIndex((e=>e.startsWith("m=video")));-1!==i&&t.splice(i+1,0,"a=control:trackID=video");const r=t.findIndex((e=>e.startsWith("m=audio")));return-1!==r&&t.splice(r+1,0,"a=control:trackID=audio"),t.join("\r\n")}(t);const i=new he(e,t);return await i.handlePlayback(),e})),o=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,o=`tcp://127.0.0.1:${e.port}`}const a=!1!==e?.refresh;return x(s,{connect:(e,s)=>{a&&(this.activeClients++,this.printActiveClients());const o=Date.now(),c=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{s(),t.removeListener(i,c),t.removeListener("killed",d)};t.on(i,c),t.once("killed",d);for(const e of n)e.time<o-r||c(e.chunk);return()=>{a&&(this.activeClients--,this.inactivityCheck(t)),d()}}}),o})(o),f={url:m,container:o,inputArguments:["-analyzeduration","0","-probesize",h,...this.parsers[o].inputArguments||[],"-f",this.parsers[o].container,"-i",m],mediaStreamOptions:a};return ye.createFFmpegMediaObject(f)}}class Be extends o{released=!1;sessions=new Map;constructor(e){super(e),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=e?.id;let i=this.sessions.get(t);return!i||e?.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t),r=i?i.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"),ve.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const o=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let a=0;const c=r.length;for(const e of s){let i=this.sessions.get(e);if(!i){const s=t?.find((t=>t.id===e));s?.prebuffer&&ve.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const d=s?.name,u=!r.includes(e);if(i=new Ue(this,d,e,o||u),this.sessions.set(e,i),e===t?.[0]?.id&&this.sessions.set(void 0,i),o){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(u){this.console.log("stream",d,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;a++,e=!0,this.online=a==c,await(0,n.once)(t,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&a--,e=!1,this.online=a==c}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)")})()}}be.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);t?.length>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:i.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:Pe,value:this.storage.getItem(Pe)||we.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:Ie,value:("false"!==this.storage.getItem(Ie)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const i=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of i.values())e?.parserSessionPromise?.then((e=>e.kill()));this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const i=parseInt(this.storage.getItem(Pe))||we;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const je=new class extends L{constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(Se.getSystemState())){const t=Se.getDeviceById(e);if(t.mixins?.includes(this.id))try{t.getVideoStreamOptions()}catch(e){this.console.error("error triggering prebuffer",t.name,e)}}}));const i=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(i/1e3/60)} minutes`),setTimeout((()=>be.requestRestart()),i)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:n,sdp:s}=r,{audioPayloadTypes:o,videoPayloadTypes:a}=function(e){const t=new Set,i=new Set,r=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},n=e.match(/m=audio.*/)?.[0];r(t,n?.split(" ").slice(3));const s=e.match(/m=video.*/)?.[0];return r(i,s?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(s),d=new URL(n);if(!d.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:u,url:p}=await g(),l={url:p,inputArguments:["-rtsp_transport","tcp","-i",p.replace("tcp","rtsp")]};return u.then((async e=>{const t=new he(e,s);await t.handlePlayback();const i=c().connect(parseInt(d.port),d.hostname);for(e.on("close",(()=>{i.destroy()})),i.on("close",(()=>{e.destroy()}));;){const r=(await O(i,2)).readInt16BE(0),n=await O(i,r),s=127&n[1];if(o.has(s))t.sendAudio(n,!1);else{if(!a.has(s))throw e.destroy(),i.destroy(),new Error("unknown payload type "+s);t.sendVideo(n,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){return i.includes(e.ScryptedInterface.VideoCamera)?[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new Be({mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"})}async releaseMixin(e,t){t.online=!0,t.release()}}})();var n=exports="undefined"==typeof exports?{}:exports;for(var s in r)n[s]=r[s];r.__esModule&&Object.defineProperty(n,"__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.1.189",
3
+ "version": "0.1.192",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -33,6 +33,7 @@ const DEFAULT_FFMPEG_INPUT_ARGUMENTS = '-fflags +genpts';
33
33
 
34
34
  const SCRYPTED_PARSER = 'Scrypted';
35
35
  const FFMPEG_PARSER = 'FFmpeg';
36
+ const STRING_DEFAULT = 'Default';
36
37
 
37
38
  const VALID_AUDIO_CONFIGS = [
38
39
  AAC_AUDIO,
@@ -128,6 +129,41 @@ class PrebufferSession {
128
129
  }
129
130
  }
130
131
 
132
+ canUseRtspParser(muxingMp4: boolean) {
133
+ if (muxingMp4)
134
+ return false;
135
+ // The RTSP demuxer can only be used when not transcoding audio.
136
+ const { isUsingDefaultAudioConfig, compatibleAudio, aacAudio } = this.getAudioConfig();
137
+ const canUseRtspParser = isUsingDefaultAudioConfig || compatibleAudio || aacAudio;
138
+ return canUseRtspParser;
139
+ }
140
+
141
+ getParser(rtspMode: boolean, muxingMp4: boolean, mediaStreamOptions: MediaStreamOptions) {
142
+ if (!this.canUseRtspParser(muxingMp4))
143
+ return FFMPEG_PARSER;
144
+
145
+ const defaultValue = rtspMode && mediaStreamOptions?.tool === 'scrypted' ?
146
+ SCRYPTED_PARSER : FFMPEG_PARSER;
147
+ const rtspParser = this.storage.getItem(this.rtspParserKey);
148
+ if (!rtspParser || rtspParser === STRING_DEFAULT)
149
+ return defaultValue;
150
+ if (rtspParser === SCRYPTED_PARSER)
151
+ return SCRYPTED_PARSER;
152
+ if (rtspParser === FFMPEG_PARSER)
153
+ return FFMPEG_PARSER;
154
+ return defaultValue;
155
+ }
156
+
157
+ getRebroadcastMode() {
158
+ const mode = this.storage.getItem(this.rebroadcastModeKey);
159
+ const rtspMode = mode?.startsWith('RTSP');
160
+
161
+ return {
162
+ rtspMode: mode?.startsWith('RTSP'),
163
+ muxingMp4: !rtspMode || mode?.includes('MP4'),
164
+ };
165
+ }
166
+
131
167
  async getMixinSettings(): Promise<Setting[]> {
132
168
  const settings: Setting[] = [];
133
169
 
@@ -135,8 +171,8 @@ class PrebufferSession {
135
171
 
136
172
  let total = 0;
137
173
  let start = 0;
138
- const { mp4Mode, rtspMode } = this.getRebroadcastMode();
139
- for (const prebuffer of (mp4Mode ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
174
+ const { muxingMp4, rtspMode } = this.getRebroadcastMode();
175
+ for (const prebuffer of (muxingMp4 ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
140
176
  start = start || prebuffer.time;
141
177
  for (const chunk of prebuffer.chunk.chunks) {
142
178
  total += chunk.byteLength;
@@ -162,52 +198,70 @@ class PrebufferSession {
162
198
  TRANSCODE_AUDIO_DESCRIPTION,
163
199
  ],
164
200
  },
165
- {
166
- title: 'FFmpeg Input Arguments Prefix',
167
- group,
168
- description: 'Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.',
169
- key: this.ffmpegInputArgumentsKey,
170
- value: this.storage.getItem(this.ffmpegInputArgumentsKey),
171
- placeholder: DEFAULT_FFMPEG_INPUT_ARGUMENTS,
172
- choices: [
173
- DEFAULT_FFMPEG_INPUT_ARGUMENTS,
174
- '-use_wallclock_as_timestamps 1',
175
- '-v verbose',
176
- ],
177
- combobox: true,
178
- },
179
201
  {
180
202
  title: 'Rebroadcast Container',
181
203
  group,
182
- description: 'Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP may have lower latency and better responsiveness.',
204
+ description: 'Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default is "MPEG-TS" for this camera.',
183
205
  placeholder: 'MPEG-TS',
184
206
  choices: [
207
+ STRING_DEFAULT,
185
208
  'MPEG-TS',
186
209
  'RTSP',
187
210
  // 'RTSP+MP4',
188
211
  ],
189
212
  key: this.rebroadcastModeKey,
190
- value: this.storage.getItem(this.rebroadcastModeKey) || 'MPEG-TS',
213
+ value: this.storage.getItem(this.rebroadcastModeKey) || STRING_DEFAULT,
191
214
  }
192
215
  );
193
216
 
217
+ const addFFmpegInputArgumentsSettings = () => {
218
+ settings.push(
219
+ {
220
+ title: 'FFmpeg Input Arguments Prefix',
221
+ group,
222
+ description: 'Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.',
223
+ key: this.ffmpegInputArgumentsKey,
224
+ value: this.storage.getItem(this.ffmpegInputArgumentsKey),
225
+ placeholder: DEFAULT_FFMPEG_INPUT_ARGUMENTS,
226
+ choices: [
227
+ DEFAULT_FFMPEG_INPUT_ARGUMENTS,
228
+ '-use_wallclock_as_timestamps 1',
229
+ '-v verbose',
230
+ ],
231
+ combobox: true,
232
+ },
233
+ )
234
+ };
235
+
194
236
  if (session) {
195
- // The RTSP demuxer can only be used if also muxing RTSP.
196
- if (rtspMode && session.mediaStreamOptions?.container === 'rtsp') {
197
- const value = this.getParser(rtspMode, session.mediaStreamOptions);
237
+ if (this.canUseRtspParser(muxingMp4)
238
+ && rtspMode
239
+ && session.mediaStreamOptions?.container === 'rtsp') {
240
+
241
+ const value = this.getParser(rtspMode, muxingMp4, session.mediaStreamOptions);
242
+
198
243
  settings.push(
199
244
  {
200
245
  key: this.rtspParserKey,
201
246
  group,
202
247
  title: 'RTSP Parser',
203
- description: 'Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser may have lower latency and better responsiveness.',
204
- value,
248
+ description: `Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser is lower latency. The Scrypted Parser is only available when the Audo Codec is not Transcoding and the Rebroadcast Container is RTSP. The default is "${value}" for this camera.`,
249
+ value: this.storage.getItem(this.rtspParserKey) || STRING_DEFAULT,
205
250
  choices: [
251
+ STRING_DEFAULT,
206
252
  FFMPEG_PARSER,
207
253
  SCRYPTED_PARSER,
208
254
  ],
209
255
  }
210
256
  );
257
+
258
+ if (value !== SCRYPTED_PARSER) {
259
+ // ffmpeg parser is being used, so add ffmpeg input arguments option.
260
+ addFFmpegInputArgumentsSettings();
261
+ }
262
+ }
263
+ else {
264
+ addFFmpegInputArgumentsSettings();
211
265
  }
212
266
 
213
267
  settings.push(
@@ -238,6 +292,10 @@ class PrebufferSession {
238
292
  );
239
293
  }
240
294
  else {
295
+ // adding this option even when using the scrypted parser is a failsafe
296
+ // in case the parsing won't start.
297
+ addFFmpegInputArgumentsSettings();
298
+
241
299
  settings.push(
242
300
  {
243
301
  title: 'Status',
@@ -253,24 +311,6 @@ class PrebufferSession {
253
311
  return settings;
254
312
  }
255
313
 
256
- getParser(rtspMode: boolean, mediaStreamOptions: MediaStreamOptions) {
257
- const defaultValue = rtspMode && mediaStreamOptions?.tool === 'scrypted' ?
258
- SCRYPTED_PARSER : FFMPEG_PARSER;
259
- const rtspParser = this.storage.getItem(this.rtspParserKey);
260
- const value = !rtspParser ? defaultValue : rtspParser === SCRYPTED_PARSER ? SCRYPTED_PARSER : FFMPEG_PARSER;
261
- return value;
262
- }
263
-
264
- getRebroadcastMode() {
265
- const mode = this.storage.getItem(this.rebroadcastModeKey);
266
- const rtspMode = mode?.startsWith('RTSP');
267
-
268
- return {
269
- rtspMode: mode?.startsWith('RTSP'),
270
- mp4Mode: !rtspMode || mode?.includes('MP4'),
271
- };
272
- }
273
-
274
314
  async startPrebufferSession() {
275
315
  this.prebuffers.mp4 = [];
276
316
  this.prebuffers.mpegts = [];
@@ -294,12 +334,20 @@ class PrebufferSession {
294
334
 
295
335
  const { isUsingDefaultAudioConfig, aacAudio, compatibleAudio, reencodeAudio } = this.getAudioConfig();
296
336
 
337
+ const { rtspMode, muxingMp4 } = this.getRebroadcastMode();
338
+
297
339
  let detectedAudioCodec = this.storage.getItem(this.lastDetectedAudioCodecKey) || undefined;
298
340
  if (detectedAudioCodec === 'null')
299
341
  detectedAudioCodec = null;
300
342
 
343
+ // only need to probe the audio under specific circumstances.
344
+ // rtsp only mode (ie, no mp4 mux) does not need probing.
301
345
  let probingAudioCodec = false;
302
- if (!audioSoftMuted && !advertisedAudioCodec && isUsingDefaultAudioConfig && detectedAudioCodec === undefined) {
346
+ if (muxingMp4
347
+ && !audioSoftMuted
348
+ && !advertisedAudioCodec
349
+ && isUsingDefaultAudioConfig
350
+ && detectedAudioCodec === undefined) {
303
351
  this.console.warn('Camera did not report an audio codec, muting the audio stream and probing the codec.');
304
352
  probingAudioCodec = true;
305
353
  }
@@ -316,14 +364,11 @@ class PrebufferSession {
316
364
  ? advertisedAudioCodec?.toLowerCase()
317
365
  : detectedAudioCodec?.toLowerCase();
318
366
 
319
- // rtsp mode can handle any codec, and its generally better to allow it do that.
320
- const { rtspMode, mp4Mode } = this.getRebroadcastMode();
321
- const nonRtsp = !rtspMode || mp4Mode;
322
367
 
323
368
  // after probing the audio codec is complete, alert the user with appropriate instructions.
324
369
  // assume the codec is user configurable unless the camera explictly reports otherwise.
325
370
  const audioIncompatible = !COMPATIBLE_AUDIO_CODECS.includes(assumedAudioCodec);
326
- if (nonRtsp && !probingAudioCodec && mso?.userConfigurable !== false && !audioSoftMuted) {
371
+ if (muxingMp4 && !probingAudioCodec && mso?.userConfigurable !== false && !audioSoftMuted) {
327
372
  if (audioIncompatible) {
328
373
  // show an alert that rebroadcast needs an explicit setting by the user.
329
374
  if (isUsingDefaultAudioConfig) {
@@ -460,7 +505,7 @@ class PrebufferSession {
460
505
  rbo.parsers.rtsp = parser;
461
506
  }
462
507
 
463
- if (mp4Mode) {
508
+ if (muxingMp4) {
464
509
  rbo.parsers.mp4 = createFragmentedMp4Parser({
465
510
  vcodec,
466
511
  acodec,
@@ -492,7 +537,7 @@ class PrebufferSession {
492
537
  const ffmpegInput = JSON.parse(moBuffer.toString()) as FFMpegInput;
493
538
  sessionMso = ffmpegInput.mediaStreamOptions;
494
539
 
495
- const parser = this.getParser(rtspMode, ffmpegInput.mediaStreamOptions);
540
+ const parser = this.getParser(rtspMode, muxingMp4, ffmpegInput.mediaStreamOptions);
496
541
  if (parser === SCRYPTED_PARSER) {
497
542
  usingScryptedParser = true;
498
543
  this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser')
@@ -521,7 +566,7 @@ class PrebufferSession {
521
566
 
522
567
  // if operating in RTSP mode, use a side band ffmpeg process to grab the mp4 segments.
523
568
  // ffmpeg adds latency, as well as rewrites timestamps.
524
- if (usingScryptedParser && mp4Mode) {
569
+ if (usingScryptedParser && muxingMp4) {
525
570
  this.getVideoStream({
526
571
  id: this.streamId,
527
572
  refresh: false,
@@ -623,12 +668,21 @@ class PrebufferSession {
623
668
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
624
669
  const now = Date.now();
625
670
 
671
+ const updateIdr = () => {
672
+ if (this.prevIdr) {
673
+ const sendEvent = !this.detectedIdrInterval;
674
+ this.detectedIdrInterval = now - this.prevIdr;
675
+ // only on the first idr update should we send a settings refresh.
676
+ if (sendEvent)
677
+ deviceManager.onMixinEvent(this.mixin.id, this.mixin.mixinProviderNativeId, ScryptedInterface.Settings, undefined);
678
+ }
679
+ this.prevIdr = now;
680
+ }
681
+
626
682
  // this is only valid for mp4, so its no op for everything else
627
683
  // used to detect idr interval.
628
684
  if (chunk.type === 'mdat') {
629
- if (this.prevIdr)
630
- this.detectedIdrInterval = now - this.prevIdr;
631
- this.prevIdr = now;
685
+ updateIdr();
632
686
  }
633
687
  if (chunk.type === 'rtp-video') {
634
688
  const fragmentType = chunk.chunks[1].readUInt8(12) & 0x1f;
@@ -636,9 +690,7 @@ class PrebufferSession {
636
690
  const nalType = second & 0x1f;
637
691
  const startBit = second & 0x80;
638
692
  if (((fragmentType === 28 || fragmentType === 29) && nalType === 5 && startBit == 128) || fragmentType == 5) {
639
- if (this.prevIdr)
640
- this.detectedIdrInterval = now - this.prevIdr;
641
- this.prevIdr = now;
693
+ updateIdr();
642
694
  }
643
695
  }
644
696