@scrypted/prebuffer-mixin 0.1.200 → 0.1.203
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +99 -32
- package/src/rfc4571.ts +2 -2
package/dist/main.nodejs.js
CHANGED
|
@@ -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]}),s=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,s(i(268),t);const n=i(268);class o extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}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(n.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:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let s;!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"}(s||(s={}));const n={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 s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,i),n.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:()=>$e});var e=i(510),t=i.n(e);const s=require("events"),{deviceManager:n}=t();class o extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>n.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=n.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),n.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await n.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,s.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,s.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,s.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 s=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const r=n=>{const o=n.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(s||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",n(e.log)),t.stderr?.on("data",n(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=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(s.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let s;function n(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(s),s=setTimeout(n,r))}return i.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o}}async function x(e,t){const{console:i}=t;let r,n=!0;const o=new s.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(){n&&(o.emit("killed"),o.emit("error",new Error("killed"))),n=!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 x=!1;const C=["pipe","pipe","pipe"];let O=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){x=!0;const i=l().createSocket("udp4"),s=await m(i),n=l().createSocket("udp4");await f(n,s.port+1),o.once("killed",(()=>{i.close(),n.close()})),S.push(...r.outputArguments,s.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(n,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),o.emit(e,t),a()})()}else if(r.tcpProtocol){const s=await g(),n=new URL(r.tcpProtocol);n.port=s.port.toString(),S.push(...r.outputArguments,n.toString());const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{const t=await s.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++),C.push("pipe")}x&&(S.push("-sdp_file","pipe:"+O++),C.push("pipe")),S.unshift("-hide_banner"),w(i,S);const D=u().spawn(await P.getFFmpegPath(),S,{stdio:C});let k;b(i,D,void 0,t?.storage),D.on("exit",v),k=x?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 s=D.stdio[3+T];T++;try{const{resetActivityTimer:i}=M(e,v,o,t?.timeout);for await(const t of r.parse(s,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 s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(D).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:k,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>n,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 C(e,t){const i=await e;let r=!0;const s=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,e?.()};let n=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),s);i.once("close",(()=>{s()})),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 s=()=>{const r=e.read(t);r&&(o(),i(r))},n=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const D="\n".charCodeAt(0);async function k(e){return async function(e,t){const i=[];let r=0;for(;;){const s=await O(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;i[r++]=s[0]}return Buffer.from(i).toString()}(e,D)}function T(e){return e}async function*A(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),s=await O(e,i);yield{header:t,length:i,type:r,data:s}}}async function*E(e){let t,i,r;for await(const s of e)t?i||(i=s):t=s,yield{startStream:r,chunks:[s.header,s.data],type:s.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 N=i.n(j);const $=require("tls");var F=i.n($);const H=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 q(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function K(e){var t="function"==typeof Map?new Map:void 0;return K=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,z(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),J(r,e)},K(e)}function W(e,t,i){return W=G()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var s=new(Function.bind.apply(e,r));return i&&J(s,i.prototype),s},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 J(e,t){return J=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},J(e,t)}function z(e){return z=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},z(e)}let Y=function(e){function t(e,i,...r){var s,n,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=[]),n=this,(s=!(o=z(t).call(this,i))||"object"!=typeof o&&"function"!=typeof o?q(n):o).code=i||"E_UNEXPECTED",s.params=r,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(q(s),s.constructor),s}var i,r,s;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&&J(e,t)}(t,e),i=t,(r=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+H.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&V(i.prototype,r),s&&V(i,s),t}(K(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 s=X(e.message),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&i.push(e.message),r=new Y(n,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 s=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],s){if(-1===t.indexOf(i))throw new Z("E_UNAUTHORIZED_KEY",s,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return re(i,s),s}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 se=["realm","nonce"],ne=[...se,"domain","opaque","stale","algorithm","qop"],oe=["username","realm","nonce","uri","response"],ae=[...oe,"algorithm","cnonce","opaque","qop","nc"];function ce(e,t){const i=N().createHash(e);return i.update(t),i.digest("hex")}const de={type:"Digest",parseWWWAuthenticateRest:function(e){return te(e,ne,se)},buildWWWAuthenticateRest:function(e){return ie(e,ne,se)},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{constructor(e){this.console=e}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),this.console?.log("rtsp outgpoing message\n",r),this.console?.log(),i&&this.client.write(i)}async readMessage(){const e=await ue(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;constructor(e,t){super(t),this.url=e;const i=new URL(e),r=parseInt(i.port)||554;e.startsWith("rtsps")?this.client=F().connect({rejectUnauthorized:!1,port:r,host:i.hostname}):this.client=c().connect(r,i.hostname)}writeRequest(e,t,i,r){t=t||{};let s=this.url;i&&(s+="/"+i);const n=new URL(s);n.username="",n.password="",s=n.toString();const o=`${e} ${s} 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,s){this.writeRequest(e,t,i,r);const n=await this.readMessage(),o=n[0],a=pe(n);if(!o.includes("200")&&!a["www-authenticate"])throw new Error(o);if(a["www-authenticate"]){if(s)throw new Error("auth failed");const n=new URL(this.url),o=de.parseWWWAuthenticateRest(a["www-authenticate"]),c=N().createHash("md5").update(`${n.username}:${o.realm}:${n.password}`).digest("hex"),d=N().createHash("md5").update(`${e}:${n.pathname}`).digest("hex"),u=N().createHash("md5").update(`${c}:${o.nonce}:${d}`).digest("hex"),p={username:n.username,realm:o.realm,nonce:o.nonce,uri:n.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,r){this.client=e,this.sdp=t,this.udp=i,this.checkRequest=r,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=r,i.Session=this.session;let s=B(this.sdp,"audio"),n=B(this.sdp,"video");if(r.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const i=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[o,a,c]=i;s&&e.includes(s.trackId)?this.udpPorts.audio=parseInt(a):n&&e.includes(n.trackId)?this.udpPorts.video=parseInt(a):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]);s&&e.includes(s.trackId)?this.audioChannel=i:n&&e.includes(n.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"),s=B(this.sdp,"video"),n="";r&&(n=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&s&&(n+=","),s&&(n+=`url=${e}/trackID=${s.trackId};seq=0;rtptime=0`),i["RTP-Info"]=n,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 s={};s.Session=this.session,this.respond(200,"OK",t,s)}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.checkRequest){let s;try{s=await this.checkRequest(t,i,r,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",r,{}),this.client.destroy(),new Error("check request failed")}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,s){let n=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),s&&(r["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(r))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&this.client.write(s)}}const{systemManager:me}=t();class fe{values={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const i=t[e],r=()=>this.getItem(e);let s;s="clippath"!==i.type?r:()=>{try{return JSON.parse(r())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)})}}async getSettings(){const e=[];for(const[t,i]of Object.entries(this.settings)){const r=Object.assign({},i);r.hide||(r.key=t,r.value=this.getItem(t),e.push(r),delete r.onPut,delete r.mapPut)}return e}async putSetting(t,i){const r=this.settings[t];let s;r&&(s=this.getItem(t)),r?.noStore||(r.mapPut&&(i=r.mapPut(s,i)),"object"==typeof i?this.device.storage.setItem(t,JSON.stringify(i)):this.device.storage.setItem(t,i?.toString())),r?.onPut?.(s,i),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItem(e){const t=this.settings[e];if(!t)return this.device.storage.getItem(e);const i=t.multiple?"array":t.type;return function(e,t,i){if("boolean"===t)return"true"===e||"false"!==e&&(i||!1);if("number"===t)return parseFloat(e)||i||0;if("integer"===t)return parseInt(e)||i||0;if("array"===t)try{return JSON.parse(e)}catch(e){return[]}return"device"===t?me.getDeviceById(e):e||i}(this.device.storage.getItem(e),i,t.defaultValue)}}const ge=require("stream"),{mediaManager:ye}=t();async function ve(e,t,i,r,s,n){let o=!0;const a=new ge.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,n?.timeout);for(;;){let i,r;if(s){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 n=await O(t,r),o=127&n[1];if(!s){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,n],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:Se,log:be,systemManager:we,deviceManager:Pe}=t(),Ie=1e4,Me="prebufferDuration",xe="sendKeyframe",Ce="Default",Oe="AAC or No Audio",De=`${Oe} (Copy)`,ke="Compatible Audio",Te="Other Audio",Ae=["aac","mp3","mp2","opus"],Ee="-fflags +genpts",Re="Scrypted",_e="FFmpeg",Le="Default",Ue=[Oe,ke,Te],Be=["mpegts","mp4","rtsp"];class je{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;constructor(e,t,i){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=i,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const r="rtspServerPathKey-"+this.streamId;this.rtspServerPath=this.storage.getItem(r),this.rtspServerPath||(this.rtspServerPath=N().randomBytes(8).toString("hex"),this.storage.setItem(r,this.rtspServerPath))}get streamId(){return this.advertisedMediaStreamOptions.id}get streamName(){return this.advertisedMediaStreamOptions.name}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";Ue.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Oe),i=-1!==e.indexOf(ke),r=-1!==e.indexOf(Te);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:i,compatibleAudio:r,aacAudio:s}=this.getAudioConfig();return i||r||s}getParser(e,t,i){if(!this.canUseRtspParser(t,i))return _e;const r=e&&"scrypted"===i?.tool?Re:_e,s=this.storage.getItem(this.rtspParserKey);return s&&s!==Le?s===Re?Re:s===_e?_e: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:s,rtspMode:n}=this.getRebroadcastMode();for(const e of s?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)||Ce,choices:[Ce,De,"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:[Le,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||Le});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:Ee,choices:[Ee,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(s,this.advertisedMediaStreamOptions)&&n&&"rtsp"===this.advertisedMediaStreamOptions?.container){const t=this.getParser(n,s,this.advertisedMediaStreamOptions);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 "${t}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||Le,choices:[Le,_e,Re]}),t!==Re&&d()}else d();return t?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"}):e.push({title:"Status",group:c,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),n&&e.push({group:c,key:"rtspRebroadcastUrl",title:"RTSP Rebroadcast Url",description:"The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.",readonly:!0,value:`rtsp://localhost:${this.mixin.plugin.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`}),e}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(Me))||Ie;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,n=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&&!n&&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&&n&&void 0!==m&&m!==n&&this.console.warn("Audio codec plugin reported vs detected mismatch",n,m);const g=void 0===m?n?.toLowerCase():m?.toLowerCase(),y=!Ae.includes(g);!h||f||!1===i?.userConfigurable||r||y&&(o&&be.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 C=!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."),C=!0),r||f)P=["-an"],this.audioDisabled=!0;else if(p||C)P=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-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),s=i.chunks[1].readUInt8(13),n=31&s,o=128&s;if((28===r||29===r)&&5===n&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const s=new he(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:O,acodec:o?["-acodec","copy"]:P});this.sdp=e.sdp,D.parsers.rtsp=e}else D.parsers.mpegts={container:"mpegts",outputArguments:[...(k={vcodec:O,acodec:P})?.vcodec||[],...k?.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,s.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<R)continue;const n=Buffer.concat(t);_?.(n);const o=n.length%R,a=n.slice(0,n.length-o),c=n.slice(n.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 s=i.chunks[r];let n=0;for(;n+188<s.length;){const i=s.subarray(n,n+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);n+=188}}}return e}};var k,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=A(e);yield*E(t)},findSyncFrame:T}}({vcodec:O,acodec:P}));const L=await this.mixinDevice.getVideoStream(i),N="x-scrypted/x-rfc4571"===L.mimeType;let $,F;this.storage.removeItem(this.lastDetectedAudioCodecKey);let H=!1;if(l&&N){H=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Se.convertMediaObjectToJSON(L,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;$=await ve(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 Se.convertMediaObjectToBuffer(L,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());F=i.mediaStreamOptions;if(this.getParser(l,h,i.mediaStreamOptions)===Re){H=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new le(i.url,this.console);await e.options();const t=(await e.describe()).body.toString().trim();this.console.log("sdp",t),this.sdp=Promise.resolve(t);const{audio:s,video:n}=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,s),o+=2),await e.setup(o,n);const a=await e.play();$=await ve(this.console,a,t,i.mediaStreamOptions,!0,D);const c=$.kill.bind($);$.kill=async()=>{e.teardown().finally(c),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500),c()}}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;i.inputArguments.unshift(...e.split(" ")),$=await x(i,D)}}if(H&&h&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee,r=await Se.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const s=await async function(e,t,i,r){const s=e.slice();s.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),s.unshift("-hide_banner"),w(r,s);const n=u().spawn(await U.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,n),{cp:n,generator:A(n.stdio[3])}}(r.inputArguments,P,O,this.console),n=()=>{s.cp.kill("SIGKILL"),$.kill(),s.generator.throw(new Error("killed"))};if(!$.isActive)return void n();$.once("killed",n);const{resetActivityTimer:o}=M("mp4",n,$,D.timeout);for await(const e of E(s.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=$,Pe.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 Se.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,s(i)},s=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};s(i),$.once("killed",(()=>clearTimeout(t)))}$.once("killed",(()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===$&&(this.parserSession=void 0)}));for(const i of Be){let r=0;$.on(i,(s=>{const n=this.prebuffers[i],o=Date.now(),a=()=>{if(this.prevIdr){const t=!this.detectedIdrInterval;this.detectedIdrInterval=o-this.prevIdr,t&&Pe.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}this.prevIdr=o};if("mdat"===s.type&&a(),"rtp-video"===s.type){const e=31&s.chunks[1].readUInt8(12),t=s.chunks[1].readUInt8(13),i=31&t,r=128&t;(28!==e&&29!==e||5!==i||128!=r)&&5!=e||a()}for(n.push({time:o,chunk:s});n.length&&n[0].time<o-t;)n.shift(),r++;r>1e3&&(this.prebuffers[i]=n.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 handleRebroadcasterClient(e){const{isActiveClient:t,container:i,session:r,socketPromise:s,requestedPrebuffer:n}=e;C(s,{connect:(e,s)=>{t&&(this.activeClients++,this.printActiveClients());const o=Date.now(),a=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),r.removeListener(i,a),r.removeListener("killed",c)};r.on(i,a),r.once("killed",c);const d=this.prebuffers[i];for(const e of d)e.time<o-n||a(e.chunk);return()=>{t&&(this.activeClients--,this.inactivityCheck(r)),c()}}})}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(xe);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:s}=this.getRebroadcastMode(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;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=>{let s,n;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})),n=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,n=`tcp://127.0.0.1:${e.port}`}const o=!1!==e?.refresh;return this.handleRebroadcasterClient({isActiveClient:o,container:i,requestedPrebuffer:r,socketPromise:s,session:t}),n})(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 Se.createFFmpegMediaObject(f)}}class Ne extends o{released=!1;sessions=new Map;constructor(e,t){super(t),this.plugin=e,this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){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],n=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"),be.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;r.length;for(const e of n){let i=this.sessions.get(e);if(!i){const n=t?.find((t=>t.id===e));n?.prebuffer&&be.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const c=n?.name,d=!r.includes(e);if(i=new je(this,n,o||d),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(d){this.console.log("stream",c,"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,await(0,s.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}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)")})()}}Pe.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:Me,value:this.storage.getItem(Me)||Ie.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:xe,value:("false"!==this.storage.getItem(xe)).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(Me))||Ie;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 $e=new class extends L{storageSettings=new fe(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});currentMixins=new Map;constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(we.getSystemState())){const t=we.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((()=>Pe.requestRestart()),i),this.startRtspServer()}startRtspServer(){!async function(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}(this.rtspServer),this.rtspServer=new(c().Server)((async e=>{let t;const i=new he(e,void 0,void 0,(async(e,r,s,n)=>{i.checkRequest=void 0;const o=new URL(r);for(const e of this.currentMixins.keys()){const r=this.currentMixins.get(e);for(const e of r.sessions.values())if(o.pathname.endsWith(e.rtspServerPath))return i.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,i.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),i.console=this.console;try{await i.handlePlayback();const r=await t.parserSessionPromise,s=1.5*Math.max(4e3,t.detectedIdrInterval||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:r,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await i.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:s,sdp:n}=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))},s=e.match(/m=audio.*/)?.[0];r(t,s?.split(" ").slice(3));const n=e.match(/m=video.*/)?.[0];return r(i,n?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(n),d=new URL(s);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,n);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),s=await O(i,r),n=127&s[1];if(o.has(n))t.sendAudio(s,!1);else{if(!a.has(n))throw e.destroy(),i.destroy(),new Error("unknown payload type "+n);t.sendVideo(s,!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){this.setHasEnabledMixin(i.id);const r=new Ne(this,{mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"});return this.currentMixins.set(i.id,r),r}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in r)s[n]=r[n];r.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
1
|
+
(()=>{var e={747:(e,t,i)=>{"use strict";Object.defineProperty(t,"rh",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"Nu",{enumerable:!0,get:function(){return n.default}});var r=a(i(945)),s=a(i(816)),n=a(i(243)),o=a(i(828));function a(e){return e&&e.__esModule?e:{default:e}}const c=[s.default,n.default,o.default];s.default,n.default,o.default},816:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["realm"],a=o,c={type:"Basic",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");const{username:t,password:i}=c.decodeHash(e);return{hash:e,username:t,password:i}},buildAuthorizationRest:function({hash:e,username:t,password:i}){if(t&&i)return c.computeHash({username:t,password:i});if(!e)throw new s.default("E_NO_HASH");return e},computeHash:function({username:e,password:t}){return Buffer.from(e+":"+t).toString("base64")},decodeHash:function(e){const[t,...i]=Buffer.from(e,"base64").toString().split(":");return{username:t,password:i.join(":")}}};var d=c;t.default=d},828:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=(r=i(945))&&r.__esModule?r:{default:r},n=i(761);const o=["invalid_request","invalid_token","insufficient_scope"],a=["realm","scope","error","error_description"];var c={type:"Bearer",parseWWWAuthenticateRest:function(e){return(0,n.parseHTTPHeadersQuotedKeyValueSet)(e,a,[])},buildWWWAuthenticateRest:function(e){if(e.error&&-1===o.indexOf(e.error))throw new s.default("E_INVALID_ERROR",e.error,o);return(0,n.buildHTTPHeadersQuotedKeyValueSet)(e,a,[])},parseAuthorizationRest:function(e){if(!e)throw new s.default("E_EMPTY_AUTH");return{hash:e}},buildAuthorizationRest:function({hash:e}){if(!e)throw new s.default("E_NO_HASH");return e}};t.default=c},243:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,s=i(761),n=(r=i(113))&&r.__esModule?r:{default:r};const o=["realm","nonce"],a=[...o,"domain","opaque","stale","algorithm","qop"],c=["username","realm","nonce","uri","response"],d=[...c,"algorithm","cnonce","opaque","qop","nc"];function u(e,t){const i=n.default.createHash(e);return i.update(t),i.digest("hex")}var p={type:"Digest",parseWWWAuthenticateRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,a,o)},buildWWWAuthenticateRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,a,o)},parseAuthorizationRest:function(e){return(0,s.parseHTTPHeadersQuotedKeyValueSet)(e,d,c)},buildAuthorizationRest:function(e){return(0,s.buildHTTPHeadersQuotedKeyValueSet)(e,d,c)},computeHash:function(e){const t=e.ha1||u(e.algorithm,[e.username,e.realm,e.password].join(":")),i=u(e.algorithm,[e.method,e.uri].join(":"));return u(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};t.default=p},761:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){const r=e.trim().match(n);if(!r)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",e);const a=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new s.default("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new s.default("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return o(i,a),a},t.buildHTTPHeadersQuotedKeyValueSet=function(e,t,i=[]){return o(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")};var r,s=(r=i(945))&&r.__esModule?r:{default:r};const n=/\w+=(".*?"|[^",]+)(?=,|$)/g;function o(e,t){e.forEach((e=>{if(void 0===t[e])throw new s.default("E_REQUIRED_KEY",e)}))}},510:function(e,t,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]}),s=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,s(i(268),t);const n=i(268);class o extends n.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=o;class a extends n.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.mixinProviderNativeId=e.mixinProviderNativeId}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,t=this.id+(e?":"+e:"");this._storage=deviceManager.getMixinStorage(t,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(deviceManager.getMixinConsole?this._console=deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}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(n.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:()=>s,ScryptedInterfaceDescriptors:()=>n,ScryptedDeviceType:()=>o,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let s;!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"}(s||(s={}));const n={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:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:[]},VideoClips:{name:"VideoClips",methods:["getVideoClip","getVideoClipThumbnail","getVideoClips","removeVideoClips"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Authenticator:{name:"Authenticator",methods:["checkPassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},SoftwareUpdate:{name:"SoftwareUpdate",methods:["checkForUpdate","installUpdate"],properties:["updateAvailable"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},IntrusionSensor:{name:"IntrusionSensor",methods:[],properties:["intrusionDetected"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","getDetectionModel"],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]}};let o,a,c,d,u,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.VideoRecorder="VideoRecorder",e.VideoClips="VideoClips",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))},113:e=>{"use strict";e.exports=require("crypto")},945:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>m});const r=require("os");function s(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 n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e){var t="function"==typeof Map?new Map:void 0;return o=function(e){if(null===e||(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 a(e,arguments,u(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),d(r,e)},o(e)}function a(e,t,i){return a=c()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var s=new(Function.bind.apply(e,r));return i&&d(s,i.prototype),s},a.apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function d(e,t){return d=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},d(e,t)}function u(e){return u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},u(e)}let p=function(e){function t(e,i,...r){var s,o,a;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),e instanceof Array||(r=(void 0===i?[]:[i]).concat(r),i=e,e=[]),o=this,(s=!(a=u(t).call(this,i))||"object"!=typeof a&&"function"!=typeof a?n(o):a).code=i||"E_UNEXPECTED",s.params=r,s.wrappedErrors=e,s.name=s.toString(),Error.captureStackTrace&&Error.captureStackTrace(n(s),s.constructor),s}var i,o,a;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),i=t,(o=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+r.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&s(i.prototype,o),a&&s(i,a),t}(o(Error));function l(e){return e instanceof p||e.constructor&&e.constructor.name&&e.constructor.name.endsWith("Error")&&"string"==typeof e.code&&h(e.code)&&e.params&&e.params instanceof Array}function h(e){return/^([A-Z0-9_]+)$/.test(e)}p.wrap=function(e,t,...i){let r=null;const s=h(e.message),n=(e.wrappedErrors||[]).concat(e);return t||(t=s?e.message:"E_UNEXPECTED"),e.message&&!s&&i.push(e.message),r=new p(n,t,...i),r},p.cast=function(e,...t){return l(e)?e:p.wrap.apply(p,[e].concat(t))},p.bump=function(e,...t){return l(e)?p.wrap.apply(p,[e,e.code].concat(e.params)):p.wrap.apply(p,[e].concat(t))};const m=p}},t={};function i(r){var s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,i),n.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:()=>be});var e=i(510),t=i.n(e);const s=require("events"),{deviceManager:n}=t();class o extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>n.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=n.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),n.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await n.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,s.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,s.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,s.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 s=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function n(e){const r=n=>{const o=n.toString();for(const e of S)if(-1!==o.indexOf(e))return;if(!(s||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",n(e.log)),t.stderr?.on("data",n(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];let r=!1;for(const e of t){try{if(r){const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}else i.push(e)}catch(t){i.push(e)}r="-i"===e}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=s=>{const n=s.toString(),o=n.indexOf(`${t}: `);if(-1!==o){const s=n.substring(o+t.length+1).trim();let a=s.indexOf(" ");const c=s.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(s.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let s;function n(){console.error("timeout waiting for data, killing parser session",e),t()}function o(){r&&(clearTimeout(s),s=setTimeout(n,r))}return i.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o}}async function x(e,t){const{console:i}=t;let r,n=!0;const o=new s.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(){n&&(o.emit("killed"),o.emit("error",new Error("killed"))),n=!1,T?.kill(),setTimeout((()=>T?.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 x=!1;const C=["pipe","pipe","pipe"];let O=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){x=!0;const i=l().createSocket("udp4"),s=await m(i),n=l().createSocket("udp4");await f(n,s.port+1),o.once("killed",(()=>{i.close(),n.close()})),S.push(...r.outputArguments,s.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(n,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),o.emit(e,t),a()})()}else if(r.tcpProtocol){const s=await g(),n=new URL(r.tcpProtocol);n.port=s.port.toString(),S.push(...r.outputArguments,n.toString());const{resetActivityTimer:a}=M(e,v,o,t?.timeout);(async()=>{const t=await s.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++),C.push("pipe")}x&&(S.push("-sdp_file","pipe:"+O++),C.push("pipe")),S.unshift("-hide_banner"),w(i,S);const T=u().spawn(await P.getFFmpegPath(),S,{stdio:C});let D;b(i,T,void 0,t?.storage),T.on("exit",v),D=x?new Promise((e=>{const t=[];T.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 s=T.stdio[3+A];A++;try{const{resetActivityTimer:i}=M(e,v,o,t?.timeout);for await(const t of r.parse(s,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")}(T).then((e=>a=e)),async function(e){return I(e,"Video")}(T).then((e=>c=e)),async function(e){return new Promise((t=>{const i=r=>{const s=r.toString(),n=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(s);n&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(n))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(T).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:D,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>n,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 C(e,t){const i=await e;let r=!0;const s=()=>{i.removeAllListeners(),i.destroy();const e=n;n=void 0,e?.()};let n=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),s);i.once("close",(()=>{s()})),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 s=()=>{const r=e.read(t);r&&(o(),i(r))},n=()=>{o(),r(new Error(`stream ended during read for minimum ${t} bytes`))},o=()=>{e.removeListener("readable",s),e.removeListener("end",n)};e.on("readable",s),e.on("end",n)}))}const T="\n".charCodeAt(0);async function D(e){return async function(e,t){const i=[];let r=0;for(;;){const s=await O(e,1);if(!s)throw new Error("end of stream");if(s[0]===t)break;i[r++]=s[0]}return Buffer.from(i).toString()}(e,T)}function A(e){return e}async function*k(e){for(;;){const t=await O(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),s=await O(e,i);yield{header:t,length:i,type:r,data:s}}}async function*E(e){let t,i,r;for await(const s of e)t?i||(i=s):t=s,yield{startStream:r,chunks:[s.header,s.data],type:s.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:R}=t(),_="v4";class B 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:L}=t();function H(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()}}var V=i(113),U=i.n(V);const j=require("tls");var K=i.n(j),N=i(747);class $ extends Error{constructor(){super("Operation Timed Out")}}async function q(e){let t=[];for(;;){let i=await D(e);if(i=i.trim(),!i)return t;t.push(i)}}function F(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 W extends class{constructor(e){this.console=e}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),this.console?.log("rtsp outgpoing message\n",r),this.console?.log(),i&&this.client.write(i)}async readMessage(){const e=await q(this.client);return this.console?.log("rtsp incoming message\n",e.join("\n")),this.console?.log(),e}}{cseq=0;constructor(e,t){super(t),this.url=e;const i=new URL(e),r=parseInt(i.port)||554;e.startsWith("rtsps")?this.client=K().connect({rejectUnauthorized:!1,port:r,host:i.hostname}):this.client=c().connect(r,i.hostname)}writeRequest(e,t,i,r){t=t||{};let s=this.url;i&&(s+="/"+i);const n=new URL(s);n.username="",n.password="",s=n.toString();const o=`${e} ${s} 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,s){this.writeRequest(e,t,i,r);const n=this.requestTimeout?await(o=this.requestTimeout,a=this.readMessage(),new Promise(((e,t)=>{setTimeout((()=>t(new $)),o),a.then(e),a.catch(t)}))):await this.readMessage();var o,a;const c=n[0],d=F(n);if(!c.includes("200")&&!d["www-authenticate"])throw new Error(c);const u=d["www-authenticate"];if(u){if(s)throw new Error("auth failed");const n=new URL(this.url);if(u.includes("Basic")){const{username:e,password:t}=n;if(e&&t){const e=N.rh.computeHash(n);this.authorization=`Basic ${e}`}}else{const t=N.Nu.parseWWWAuthenticateRest(u),i=U().createHash("md5").update(`${n.username}:${t.realm}:${n.password}`).digest("hex"),r=U().createHash("md5").update(`${e}:${n.pathname}`).digest("hex"),s=U().createHash("md5").update(`${i}:${t.nonce}:${r}`).digest("hex"),o={username:n.username,realm:t.realm,nonce:t.nonce,uri:n.pathname,algorithm:"MD5",response:s},a=Object.entries(o).map((([e,t])=>{return`${e}=${t&&(i=t,`"${i.replace(/"/g,'\\"')}"`)}`;var i})).join(", ");this.authorization=`Digest ${a}`}return this.request(e,t,i,r,!0)}const p=parseInt(d["content-length"]);return p?{headers:d,body:await O(this.client,p)}:{headers:d,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,i){const r={Transport:`RTP/AVP/${i?"UDP":"TCP"};unicast;${i?"client_port":"interleaved"}=${e}-${e+1}`},s=await this.request("SETUP",r,t);if(s.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}(s.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=s.headers.session.split(";")[0]}return s}async play(e="0.000"){const t={Range:`npt=${e}-`};return await this.request("PLAY",t),this.client}async pause(){return await this.request("PAUSE"),this.client}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class z{udpPorts={video:0,audio:0};constructor(e,t,i,r){this.client=e,this.sdp=t,this.udp=i,this.checkRequest=r,this.session=(0,V.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await D(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=r,i.Session=this.session;let s=H(this.sdp,"audio"),n=H(this.sdp,"video");if(r.includes("UDP")){if(!this.udp)return void this.respond(461,"Unsupported Transport",t,{});const i=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[o,a,c]=i;s&&e.includes(s.trackId)?this.udpPorts.audio=parseInt(a):n&&e.includes(n.trackId)?this.udpPorts.video=parseInt(a):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]);s&&e.includes(s.trackId)?this.audioChannel=i:n&&e.includes(n.trackId)?this.videoChannel=i:this.console?.warn("unknown track id",e)}}this.respond(200,"OK",t,i)}play(e,t){const i={};let r=H(this.sdp,"audio"),s=H(this.sdp,"video"),n="";r&&(n=`url=${e}/trackID=${r.trackId};seq=0;rtptime=0`),r&&s&&(n+=","),s&&(n+=`url=${e}/trackID=${s.trackId};seq=0;rtptime=0`),i["RTP-Info"]=n,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 s={};s.Session=this.session,this.respond(200,"OK",t,s)}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=F(e);if(this.checkRequest){let s;try{s=await this.checkRequest(t,i,r,e)}catch(e){this.console?.error("error checking request",e)}if(!s)throw this.respond(400,"Bad Request",r,{}),this.client.destroy(),new Error("check request failed")}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,s){let n=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),s&&(r["Content-Length"]=s.length.toString());for(const[e,t]of Object.entries(r))n+=`${e}: ${t}\r\n`;this.console?.log("response headers",n),n+="\r\n",this.client.write(n),s&&this.client.write(s)}}const{systemManager:G}=t();class J{values={};constructor(e,t){this.device=e,this.settings=t;for(const e of Object.keys(t)){const i=t[e],r=()=>this.getItem(e);let s;s="clippath"!==i.type?r:()=>{try{return JSON.parse(r())}catch(e){}},Object.defineProperty(this.values,e,{get:s,set:t=>this.putSetting(e,t)})}}async getSettings(){const e=[];for(const[t,i]of Object.entries(this.settings)){const r=Object.assign({},i);r.hide||(r.key=t,r.value=this.getItem(t),e.push(r),delete r.onPut,delete r.mapPut)}return e}async putSetting(t,i){const r=this.settings[t];let s;r&&(s=this.getItem(t)),r?.noStore||(r.mapPut&&(i=r.mapPut(s,i)),"object"==typeof i?this.device.storage.setItem(t,JSON.stringify(i)):this.device.storage.setItem(t,i?.toString())),r?.onPut?.(s,i),this.device.onDeviceEvent(e.ScryptedInterface.Settings,void 0)}getItem(e){const t=this.settings[e];if(!t)return this.device.storage.getItem(e);const i=t.multiple?"array":t.type;return function(e,t,i){if("boolean"===t)return"true"===e||"false"!==e&&(i||!1);if("number"===t)return parseFloat(e)||i||0;if("integer"===t)return parseInt(e)||i||0;if("array"===t)try{return JSON.parse(e)}catch(e){return[]}return"device"===t?G.getDeviceById(e):e||i}(this.device.storage.getItem(e),i,t.defaultValue)}}const Q=require("stream"),{mediaManager:Y}=t();async function X(e,t,i,r,s,n){let o=!0;const a=new Q.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,n?.timeout);for(;;){let i,r;if(s){if(i=await O(t,4),"RTSP"===i.toString()){const e=F(await q(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 n=await O(t,r),o=127&n[1];if(!s){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,n],type:u};a.emit("rtsp",p),e()}})().finally(u);const h=H(i,"audio"),m=H(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:Z,log:ee,systemManager:te,deviceManager:ie}=t(),re=1e4,se="prebufferDuration",ne="sendKeyframe",oe="Default",ae="AAC or No Audio",ce=`${ae} (Copy)`,de="Compatible Audio",ue="Other Audio",pe=["aac","mp3","mp2","opus"],le="-fflags +genpts",he="Scrypted",me="FFmpeg",fe="Default",ge=[ae,de,ue],ye=["mpegts","mp4","rtsp"];class ve{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;needBitrateReset=!1;constructor(e,t,i){this.mixin=e,this.advertisedMediaStreamOptions=t,this.stopInactive=i,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId,this.rtspParserKey="rtspParser-"+this.streamId;const r="rtspServerPathKey-"+this.streamId;this.maxBitrateKey="maxBitrate-"+this.streamId,this.rtspServerPath=this.storage.getItem(r),this.rtspServerPath||(this.rtspServerPath=U().randomBytes(8).toString("hex"),this.storage.setItem(r,this.rtspServerPath))}get maxBitrate(){let e=parseInt(this.storage.getItem(this.maxBitrateKey));return e||(e=this.advertisedMediaStreamOptions?.video?.maxBitrate,this.storage.setItem(this.maxBitrateKey,e?.toString())),e||void 0}async resetBitrate(){this.console.log("Resetting bitrate after adaptive streaming session",this.maxBitrate),this.needBitrateReset=!1,this.mixinDevice.setVideoStreamOptions({id:this.streamId,video:{bitrate:this.maxBitrate}})}get streamId(){return this.advertisedMediaStreamOptions.id}get streamName(){return this.advertisedMediaStreamOptions.name}clearPrebuffers(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[]}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log(this.streamName,"prebuffer session started"),this.parserSessionPromise=this.startPrebufferSession(),this.parserSessionPromise.catch((()=>this.parserSessionPromise=void 0)))}getAudioConfig(){let e=this.storage.getItem(this.audioConfigurationKey)||"";ge.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(ae),i=-1!==e.indexOf(de),r=-1!==e.indexOf(ue);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}canUseRtspParser(e,t){if(e)return!1;if("rtsp"!==t?.container)return!1;const{isUsingDefaultAudioConfig:i,compatibleAudio:r,aacAudio:s}=this.getAudioConfig();return i||r||s}getParser(e,t,i){if(!this.canUseRtspParser(t,i))return me;const r=e&&"scrypted"===i?.tool?he:me,s=this.storage.getItem(this.rtspParserKey);return s&&s!==fe?s===he?he:s===me?me:r:r}getRebroadcastMode(){let e=this.storage.getItem(this.rebroadcastModeKey)||"Default",t="MPEG-TS";"scrypted"===this.advertisedMediaStreamOptions?.tool&&this.advertisedMediaStreamOptions?.container?.startsWith("rtsp")&&(t="RTSP"),"Default"===e&&(e=t);const i=e?.startsWith("RTSP");return{defaultMode:t,rtspMode:e?.startsWith("RTSP"),muxingMp4:!i||e?.includes("MP4")}}async getMixinSettings(){const t=[],i=this.parserSession;let r=0,s=0;const{muxingMp4:n,rtspMode:o,defaultMode:a}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunk.chunks)r+=t.byteLength}const c=Date.now()-s,d=Math.round(r/c*8),u=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";t.push({title:"Audio Codec Transcoding",group:u,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)||oe,choices:[oe,ce,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"Rebroadcast Container",group:u,description:`Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default mode for this camera is ${a}.`,placeholder:"MPEG-TS",choices:[fe,"MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||fe});const p=()=>{t.push({title:"FFmpeg Input Arguments Prefix",group:u,description:"Optional/Advanced: Additional input arguments to pass to the ffmpeg command. These will be placed before the input arguments.",key:this.ffmpegInputArgumentsKey,value:this.storage.getItem(this.ffmpegInputArgumentsKey),placeholder:le,choices:[le,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0})};if(this.canUseRtspParser(n,this.advertisedMediaStreamOptions)&&o&&"rtsp"===this.advertisedMediaStreamOptions?.container){const e=this.getParser(o,n,this.advertisedMediaStreamOptions);t.push({key:this.rtspParserKey,group:u,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 "${e}" for this camera.`,value:this.storage.getItem(this.rtspParserKey)||fe,choices:[fe,me,he]}),e!==he&&p()}else p();return i?t.push({key:"detectedResolution",group:u,title:"Detected Resolution and Bitrate",readonly:!0,value:`${i?.inputVideoResolution?.[0]||"unknown"} @ ${d||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:u,title:"Detected Video/Audio Codecs",readonly:!0,value:(i?.inputVideoCodec?.toString()||"unknown")+"/"+(i?.inputAudioCodec?.toString()||"unknown"),description:"Configuring your camera to H264 video and AAC/MP3/MP2/Opus audio is recommended."},{key:"detectedKeyframe",group:u,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).",readonly:!0,value:(this.detectedIdrInterval||0)/1e3||"unknown"}):t.push({title:"Status",group:u,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),o&&t.push({group:u,key:"rtspRebroadcastUrl",title:"RTSP Rebroadcast Url",description:"The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.",readonly:!0,value:`rtsp://localhost:${this.mixin.plugin.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`}),this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&t.push({group:u,key:this.maxBitrateKey,title:"Max Bitrate",description:"This camera supports Adaptive Bitrate. Set the maximum bitrate to be allowed while using adaptive bitrate streaming. This will also serve as the default bitrate.",type:"number",value:this.maxBitrate.toString()}),t}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(se))||re;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,n=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&&!n&&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&&n&&void 0!==m&&m!==n&&this.console.warn("Audio codec plugin reported vs detected mismatch",n,m);const g=void 0===m?n?.toLowerCase():m?.toLowerCase(),y=!pe.includes(g);!h||f||!1===i?.userConfigurable||r||y&&(o&&ee.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 C=!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."),C=!0),r||f)P=["-an"],this.audioDisabled=!0;else if(p||C)P=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","32k","-b:a","32k","-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"],T={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=T.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,V.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),s=i.chunks[1].readUInt8(13),n=31&s,o=128&s;if((28===r||29===r)&&5===n&&128==o||5==r)return e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const s=new z(e);await s.handleSetup(),t(s.sdp);for await(const{type:e,rtcp:t,header:n,packet:o}of s.handleRecord())yield{chunks:[n,o],type:`${t?"rtcp":"rtp"}-${e}`,width:i,height:r}}}}({vcodec:O,acodec:o?["-acodec","copy"]:P});this.sdp=e.sdp,T.parsers.rtsp=e}else T.parsers.mpegts={container:"mpegts",outputArguments:[...(D={vcodec:O,acodec:P})?.vcodec||[],...D?.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,s.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<R)continue;const n=Buffer.concat(t);_?.(n);const o=n.length%R,a=n.slice(0,n.length-o),c=n.slice(n.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 s=i.chunks[r];let n=0;for(;n+188<s.length;){const i=s.subarray(n,n+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);n+=188}}}return e}};var D,R,_;h&&(T.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:A}}({vcodec:O,acodec:P}));const B=await this.mixinDevice.getVideoStream(i),U="x-scrypted/x-rfc4571"===B.mimeType;let j,K;this.storage.removeItem(this.lastDetectedAudioCodecKey);let N=!1;if(l&&U){N=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await Z.convertMediaObjectToJSON(B,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;j=await X(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,T),this.sdp=j.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await Z.convertMediaObjectToBuffer(B,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());K=i.mediaStreamOptions;if(this.getParser(l,h,i.mediaStreamOptions)===he){N=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new W(i.url,this.console);try{e.requestTimeout=1e4,await e.options();const t=(await e.describe()).body.toString().trim();this.console.log("sdp",t),this.sdp=Promise.resolve(t);const{audio:s,video:n}=function(e,t=["recvonly","sendrecv"]){return{audio:H(e,"audio",t)?.trackId,video:H(e,"video",t)?.trackId}}(t);let o=0;r||(await e.setup(o,s),o+=2),await e.setup(o,n);const a=await e.play();j=await X(this.console,a,t,i.mediaStreamOptions,!0,T);const c=j.kill.bind(j);j.kill=async()=>{e.teardown().finally(c),await async function(e){await new Promise((t=>setTimeout(t,e)))}(500),c()}}catch(t){throw e.client.destroy(),t}}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||le;i.inputArguments.unshift(...e.split(" ")),j=await x(i,T)}}if(N&&h&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||le,r=await Z.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const s=await async function(e,t,i,r){const s=e.slice();s.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),s.unshift("-hide_banner"),w(r,s);const n=u().spawn(await L.getFFmpegPath(),s,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,n),{cp:n,generator:k(n.stdio[3])}}(r.inputArguments,P,O,this.console),n=()=>{s.cp.kill("SIGKILL"),j.kill(),s.generator.throw(new Error("killed"))};if(!j.isActive)return void n();j.once("killed",n);const{resetActivityTimer:o}=M("mp4",n,j,T.timeout);for await(const e of E(s.generator))o(),j.emit("mp4",e)})).catch((()=>{})),j.inputAudioCodec?pe.includes(j.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",j.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",j.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,j.inputAudioCodec||"null"),"h264"!==j.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."),j.kill(),this.startPrebufferSession();if(this.parserSession=j,ie.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0),K?.refreshAt){let t,i=K;const r=async()=>{if(!j.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await Z.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),n=JSON.parse(r.toString());i=n.mediaStreamOptions,s(i)},s=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};s(i),j.once("killed",(()=>clearTimeout(t)))}j.once("killed",(()=>{clearTimeout(this.inactivityTimeout),this.parserSessionPromise=void 0,this.parserSession===j&&(this.parserSession=void 0)}));for(const i of ye){let r=0;j.on(i,(s=>{const n=this.prebuffers[i],o=Date.now(),a=()=>{if(this.prevIdr){const t=!this.detectedIdrInterval;this.detectedIdrInterval=o-this.prevIdr,t&&ie.onMixinEvent(this.mixin.id,this.mixin.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}this.prevIdr=o};if("mdat"===s.type&&a(),"rtp-video"===s.type){const e=31&s.chunks[1].readUInt8(12),t=s.chunks[1].readUInt8(13),i=31&t,r=128&t;(28!==e&&29!==e||5!==i||128!=r)&&5!=e||a()}for(n.push({time:o,chunk:s});n.length&&n[0].time<o-t;)n.shift(),r++;r>1e3&&(this.prebuffers[i]=n.slice(),r=0)}))}return j}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(t){this.printActiveClients(),this.activeClients||(this.mixin.mixinDeviceInterfaces.includes(e.ScryptedInterface.VideoCameraConfiguration)&&this.resetBitrate(),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"),t.kill())}),3e4)))}async handleRebroadcasterClient(e){const{isActiveClient:t,container:i,session:r,socketPromise:s,requestedPrebuffer:n}=e;C(s,{connect:(e,s)=>{t&&(this.activeClients++,this.printActiveClients());const o=Date.now(),a=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),c())},c=()=>{s(),r.removeListener(i,a),r.removeListener("killed",c)};r.on(i,a),r.once("killed",c);const d=this.prebuffers[i];for(const e of d)e.time<o-n||a(e.chunk);return()=>{t&&(this.activeClients--,this.inactivityCheck(r)),c()}}})}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(ne);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:s}=this.getRebroadcastMode(),n=s?"rtsp":"mpegts";let o=this.parsers[e?.container]?e?.container:n;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=>{let s,n;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 z(e,t);return await i.handlePlayback(),e})),n=e.url.replace("tcp://","rtsp://")}else{const e=await g();s=e.clientPromise,n=`tcp://127.0.0.1:${e.port}`}const o=!1!==e?.refresh;return this.handleRebroadcasterClient({isActiveClient:o,container:i,requestedPrebuffer:r,socketPromise:s,session:t}),n})(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 Z.createFFmpegMediaObject(f)}}class Se extends o{released=!1;sessions=new Map;constructor(e,t){super(t),this.plugin=e,this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){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],n=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"),ee.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;for(const e of n){let i=this.sessions.get(e);if(!i){const n=t?.find((t=>t.id===e));n?.prebuffer&&ee.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const c=n?.name,d=!r.includes(e);if(i=new ve(this,n,o||d),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(d){this.console.log("stream",c,"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,await(0,s.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}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)")})()}}ie.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:se,value:this.storage.getItem(se)||re.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:ne,value:("false"!==this.storage.getItem(ne)).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(se))||re;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}setVideoStreamOptions(e){const t=this.sessions.get(e.id);if(t&&e?.video?.bitrate){t.needBitrateReset=!0;const i=t.maxBitrate;i&&e?.video?.bitrate>i&&(this.console.log("clamping max bitrate request",e.video.bitrate,i),e.video.bitrate=i)}return this.mixinDevice.setVideoStreamOptions(e)}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const be=new class extends B{storageSettings=new J(this,{rebroadcastPort:{title:"Rebroadcast Port",description:"The port of the RTSP server that will rebroadcast your streams.",type:"number"}});currentMixins=new Map;constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput,process.nextTick((()=>{for(const e of Object.keys(te.getSystemState())){const t=te.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((()=>ie.requestRestart()),i),this.startRtspServer()}startRtspServer(){!async function(e){if(e)try{await new Promise((t=>e.close(t)))}catch(e){}}(this.rtspServer),this.rtspServer=new(c().Server)((async e=>{let t;const i=new z(e,void 0,void 0,(async(e,r,s,n)=>{i.checkRequest=void 0;const o=new URL(r);for(const e of this.currentMixins.keys()){const r=this.currentMixins.get(e);for(const e of r.sessions.values())if(o.pathname.endsWith(e.rtspServerPath))return i.console=e.console,t=e,t.ensurePrebufferSession(),await t.parserSessionPromise,i.sdp=await t.sdp,!0}return!1}));this.console.log("RTSP Rebroadcast connection started."),i.console=this.console;try{await i.handlePlayback();const r=await t.parserSessionPromise,s=1.5*Math.max(4e3,t.detectedIdrInterval||4e3);t.handleRebroadcasterClient({isActiveClient:!0,container:"rtsp",session:r,socketPromise:Promise.resolve(e),requestedPrebuffer:s}),await i.handleTeardown()}catch(t){e.destroy()}this.console.log("RTSP Rebroadcast connection finished.")})),this.storageSettings.values.rebroadcastPort||(this.storageSettings.values.rebroadcastPort=Math.round(1e4*Math.random()+3e4)),this.rtspServer.listen(this.storageSettings.values.rebroadcastPort)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:s,sdp:n}=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))},s=e.match(/m=audio.*/)?.[0];r(t,s?.split(" ").slice(3));const n=e.match(/m=video.*/)?.[0];return r(i,n?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(n),d=new URL(s);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 z(e,n);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),s=await O(i,r),n=127&s[1];if(o.has(n))t.sendAudio(s,!1);else{if(!a.has(n))throw e.destroy(),i.destroy(),new Error("unknown payload type "+n);t.sendVideo(s,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){if(!i.includes(e.ScryptedInterface.VideoCamera))return null;const r=[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online];return i.includes(e.ScryptedInterface.VideoCameraConfiguration)&&r.push(e.ScryptedInterface.VideoCameraConfiguration),r}async getMixin(e,t,i){this.setHasEnabledMixin(i.id);const r=new Se(this,{mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"});return this.currentMixins.set(i.id,r),r}async releaseMixin(e,t){t.online=!0,await t.release(),this.currentMixins.delete(e)}}})();var s=exports="undefined"==typeof exports?{}:exports;for(var n in r)s[n]=r[n];r.__esModule&&Object.defineProperty(s,"__esModule",{value:!0})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput, RequestMediaStreamOptions, BufferConverter, ResponseMediaStreamOptions } from '@scrypted/sdk';
|
|
2
|
+
import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput, RequestMediaStreamOptions, BufferConverter, ResponseMediaStreamOptions, VideoCameraConfiguration } from '@scrypted/sdk';
|
|
3
3
|
import sdk from '@scrypted/sdk';
|
|
4
4
|
import { once } from 'events';
|
|
5
5
|
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
|
@@ -74,7 +74,7 @@ class PrebufferSession {
|
|
|
74
74
|
prevIdr = 0;
|
|
75
75
|
audioDisabled = false;
|
|
76
76
|
|
|
77
|
-
mixinDevice: VideoCamera;
|
|
77
|
+
mixinDevice: VideoCamera & VideoCameraConfiguration;
|
|
78
78
|
console: Console;
|
|
79
79
|
storage: Storage;
|
|
80
80
|
|
|
@@ -85,7 +85,9 @@ class PrebufferSession {
|
|
|
85
85
|
lastDetectedAudioCodecKey: string;
|
|
86
86
|
rebroadcastModeKey: string;
|
|
87
87
|
rtspParserKey: string;
|
|
88
|
+
maxBitrateKey: string;
|
|
88
89
|
rtspServerPath: string;
|
|
90
|
+
needBitrateReset = false;
|
|
89
91
|
|
|
90
92
|
constructor(public mixin: PrebufferMixin, public advertisedMediaStreamOptions: MediaStreamOptions, public stopInactive: boolean) {
|
|
91
93
|
this.storage = mixin.storage;
|
|
@@ -97,6 +99,7 @@ class PrebufferSession {
|
|
|
97
99
|
this.lastDetectedAudioCodecKey = 'lastDetectedAudioCodec-' + this.streamId;
|
|
98
100
|
this.rtspParserKey = 'rtspParser-' + this.streamId;
|
|
99
101
|
const rtspServerPathKey = 'rtspServerPathKey-' + this.streamId;
|
|
102
|
+
this.maxBitrateKey = 'maxBitrate-' + this.streamId;
|
|
100
103
|
|
|
101
104
|
this.rtspServerPath = this.storage.getItem(rtspServerPathKey);
|
|
102
105
|
if (!this.rtspServerPath) {
|
|
@@ -105,6 +108,26 @@ class PrebufferSession {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
get maxBitrate() {
|
|
112
|
+
let ret = parseInt(this.storage.getItem(this.maxBitrateKey));
|
|
113
|
+
if (!ret) {
|
|
114
|
+
ret = this.advertisedMediaStreamOptions?.video?.maxBitrate;
|
|
115
|
+
this.storage.setItem(this.maxBitrateKey, ret?.toString());
|
|
116
|
+
}
|
|
117
|
+
return ret || undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async resetBitrate() {
|
|
121
|
+
this.console.log('Resetting bitrate after adaptive streaming session', this.maxBitrate);
|
|
122
|
+
this.needBitrateReset = false;
|
|
123
|
+
this.mixinDevice.setVideoStreamOptions({
|
|
124
|
+
id: this.streamId,
|
|
125
|
+
video: {
|
|
126
|
+
bitrate: this.maxBitrate,
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
get streamId() {
|
|
109
132
|
return this.advertisedMediaStreamOptions.id;
|
|
110
133
|
}
|
|
@@ -177,10 +200,19 @@ class PrebufferSession {
|
|
|
177
200
|
}
|
|
178
201
|
|
|
179
202
|
getRebroadcastMode() {
|
|
180
|
-
|
|
203
|
+
let mode = this.storage.getItem(this.rebroadcastModeKey) || 'Default';
|
|
204
|
+
let defaultMode = 'MPEG-TS';
|
|
205
|
+
if (this.advertisedMediaStreamOptions?.tool === 'scrypted'
|
|
206
|
+
&& this.advertisedMediaStreamOptions?.container?.startsWith('rtsp')) {
|
|
207
|
+
defaultMode = 'RTSP';
|
|
208
|
+
}
|
|
209
|
+
if (mode === 'Default') {
|
|
210
|
+
mode = defaultMode;
|
|
211
|
+
}
|
|
181
212
|
const rtspMode = mode?.startsWith('RTSP');
|
|
182
213
|
|
|
183
214
|
return {
|
|
215
|
+
defaultMode,
|
|
184
216
|
rtspMode: mode?.startsWith('RTSP'),
|
|
185
217
|
muxingMp4: !rtspMode || mode?.includes('MP4'),
|
|
186
218
|
};
|
|
@@ -193,7 +225,7 @@ class PrebufferSession {
|
|
|
193
225
|
|
|
194
226
|
let total = 0;
|
|
195
227
|
let start = 0;
|
|
196
|
-
const { muxingMp4, rtspMode } = this.getRebroadcastMode();
|
|
228
|
+
const { muxingMp4, rtspMode, defaultMode } = this.getRebroadcastMode();
|
|
197
229
|
for (const prebuffer of (muxingMp4 ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
|
|
198
230
|
start = start || prebuffer.time;
|
|
199
231
|
for (const chunk of prebuffer.chunk.chunks) {
|
|
@@ -223,7 +255,7 @@ class PrebufferSession {
|
|
|
223
255
|
{
|
|
224
256
|
title: 'Rebroadcast Container',
|
|
225
257
|
group,
|
|
226
|
-
description:
|
|
258
|
+
description: `Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP is lower latency. The default mode for this camera is ${defaultMode}.`,
|
|
227
259
|
placeholder: 'MPEG-TS',
|
|
228
260
|
choices: [
|
|
229
261
|
STRING_DEFAULT,
|
|
@@ -339,6 +371,17 @@ class PrebufferSession {
|
|
|
339
371
|
});
|
|
340
372
|
}
|
|
341
373
|
|
|
374
|
+
if (this.mixin.mixinDeviceInterfaces.includes(ScryptedInterface.VideoCameraConfiguration)) {
|
|
375
|
+
settings.push({
|
|
376
|
+
group,
|
|
377
|
+
key: this.maxBitrateKey,
|
|
378
|
+
title: 'Max Bitrate',
|
|
379
|
+
description: 'This camera supports Adaptive Bitrate. Set the maximum bitrate to be allowed while using adaptive bitrate streaming. This will also serve as the default bitrate.',
|
|
380
|
+
type: 'number',
|
|
381
|
+
value: this.maxBitrate.toString(),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
342
385
|
return settings;
|
|
343
386
|
}
|
|
344
387
|
|
|
@@ -570,28 +613,35 @@ class PrebufferSession {
|
|
|
570
613
|
const parser = this.getParser(rtspMode, muxingMp4, ffmpegInput.mediaStreamOptions);
|
|
571
614
|
if (parser === SCRYPTED_PARSER) {
|
|
572
615
|
usingScryptedParser = true;
|
|
573
|
-
this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser')
|
|
616
|
+
this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser');
|
|
574
617
|
const rtspClient = new RtspClient(ffmpegInput.url, this.console);
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
618
|
+
try {
|
|
619
|
+
rtspClient.requestTimeout = 10000;
|
|
620
|
+
await rtspClient.options();
|
|
621
|
+
const sdpResponse = await rtspClient.describe();
|
|
622
|
+
const sdp = sdpResponse.body.toString().trim();
|
|
623
|
+
this.console.log('sdp', sdp);
|
|
624
|
+
this.sdp = Promise.resolve(sdp);
|
|
625
|
+
const { audio, video } = parseTrackIds(sdp);
|
|
626
|
+
let channel = 0;
|
|
627
|
+
if (!audioSoftMuted) {
|
|
628
|
+
await rtspClient.setup(channel, audio);
|
|
629
|
+
channel += 2;
|
|
630
|
+
}
|
|
631
|
+
await rtspClient.setup(channel, video);
|
|
632
|
+
const socket = await rtspClient.play();
|
|
633
|
+
session = await startRFC4571Parser(this.console, socket, sdp, ffmpegInput.mediaStreamOptions, true, rbo);
|
|
634
|
+
const sessionKill = session.kill.bind(session);
|
|
635
|
+
session.kill = async () => {
|
|
636
|
+
// issue a teardown to upstream to close gracefully but don't rely on it responding.
|
|
637
|
+
rtspClient.teardown().finally(sessionKill);
|
|
638
|
+
await sleep(500);
|
|
639
|
+
sessionKill();
|
|
640
|
+
}
|
|
585
641
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const sessionKill = session.kill.bind(session);
|
|
590
|
-
session.kill = async () => {
|
|
591
|
-
// issue a teardown to upstream to close gracefully but don't rely on it responding.
|
|
592
|
-
rtspClient.teardown().finally(sessionKill);
|
|
593
|
-
await sleep(500);
|
|
594
|
-
sessionKill();
|
|
642
|
+
catch (e) {
|
|
643
|
+
rtspClient.client.destroy();
|
|
644
|
+
throw e;
|
|
595
645
|
}
|
|
596
646
|
}
|
|
597
647
|
else {
|
|
@@ -760,9 +810,12 @@ class PrebufferSession {
|
|
|
760
810
|
this.printActiveClients();
|
|
761
811
|
if (this.activeClients)
|
|
762
812
|
return;
|
|
813
|
+
|
|
814
|
+
if (this.mixin.mixinDeviceInterfaces.includes(ScryptedInterface.VideoCameraConfiguration)) {
|
|
815
|
+
this.resetBitrate();
|
|
816
|
+
}
|
|
817
|
+
|
|
763
818
|
if (!this.stopInactive) {
|
|
764
|
-
if (this.activeClients === 0)
|
|
765
|
-
this.console.log('stopInactive false');
|
|
766
819
|
return;
|
|
767
820
|
}
|
|
768
821
|
|
|
@@ -985,12 +1038,11 @@ class PrebufferSession {
|
|
|
985
1038
|
}
|
|
986
1039
|
}
|
|
987
1040
|
|
|
988
|
-
class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements VideoCamera, Settings {
|
|
1041
|
+
class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera & VideoCameraConfiguration> implements VideoCamera, Settings, VideoCameraConfiguration {
|
|
989
1042
|
released = false;
|
|
990
1043
|
sessions = new Map<string, PrebufferSession>();
|
|
991
1044
|
|
|
992
|
-
|
|
993
|
-
constructor(public plugin: PrebufferProvider, options: SettingsMixinDeviceOptions<VideoCamera>) {
|
|
1045
|
+
constructor(public plugin: PrebufferProvider, options: SettingsMixinDeviceOptions<VideoCamera & VideoCameraConfiguration>) {
|
|
994
1046
|
super(options);
|
|
995
1047
|
|
|
996
1048
|
this.delayStart();
|
|
@@ -1034,7 +1086,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
|
|
1034
1086
|
const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
|
|
1035
1087
|
|
|
1036
1088
|
let active = 0;
|
|
1037
|
-
const total = enabledIds.length;
|
|
1038
1089
|
for (const id of ids) {
|
|
1039
1090
|
let session = this.sessions.get(id);
|
|
1040
1091
|
if (!session) {
|
|
@@ -1194,6 +1245,19 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
|
|
1194
1245
|
return ret;
|
|
1195
1246
|
}
|
|
1196
1247
|
|
|
1248
|
+
setVideoStreamOptions(options: MediaStreamOptions): Promise<void> {
|
|
1249
|
+
const session = this.sessions.get(options.id);
|
|
1250
|
+
if (session && options?.video?.bitrate) {
|
|
1251
|
+
session.needBitrateReset = true;
|
|
1252
|
+
const maxBitrate = session.maxBitrate;
|
|
1253
|
+
if (maxBitrate && options?.video?.bitrate > maxBitrate) {
|
|
1254
|
+
this.console.log('clamping max bitrate request', options.video.bitrate, maxBitrate);
|
|
1255
|
+
options.video.bitrate = maxBitrate;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return this.mixinDevice.setVideoStreamOptions(options);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1197
1261
|
async release() {
|
|
1198
1262
|
this.console.log('prebuffer session releasing if started');
|
|
1199
1263
|
this.released = true;
|
|
@@ -1376,7 +1440,10 @@ class PrebufferProvider extends AutoenableMixinProvider implements MixinProvider
|
|
|
1376
1440
|
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
|
1377
1441
|
if (!interfaces.includes(ScryptedInterface.VideoCamera))
|
|
1378
1442
|
return null;
|
|
1379
|
-
|
|
1443
|
+
const ret = [ScryptedInterface.VideoCamera, ScryptedInterface.Settings, ScryptedInterface.Online];
|
|
1444
|
+
if (interfaces.includes(ScryptedInterface.VideoCameraConfiguration))
|
|
1445
|
+
ret.push(ScryptedInterface.VideoCameraConfiguration);
|
|
1446
|
+
return ret;
|
|
1380
1447
|
}
|
|
1381
1448
|
|
|
1382
1449
|
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
|
package/src/rfc4571.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
|
2
|
-
import { readLength
|
|
3
|
-
import sdk, {
|
|
2
|
+
import { readLength } from "@scrypted/common/src/read-stream";
|
|
3
|
+
import sdk, { MediaStreamOptions } from "@scrypted/sdk";
|
|
4
4
|
import { EventEmitter } from "stream";
|
|
5
5
|
import net from 'net';
|
|
6
6
|
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|