@scrypted/prebuffer-mixin 0.1.172 → 0.1.176

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.
@@ -10,6 +10,7 @@
10
10
  "port": 10081,
11
11
  "request": "attach",
12
12
  "skipFiles": [
13
+ "**/plugin-remote-worker.*",
13
14
  "<node_internals>/**"
14
15
  ],
15
16
  "preLaunchTask": "scrypted: deploy+debug",
@@ -1,2 +1,2 @@
1
- (()=>{var e={510:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||s(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,r(i(268),t);const o=i(268);class n extends o.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=n;class a extends o.DeviceBase{constructor(e,t,i,s,r){super(),this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=s,this._mixinStorageSuffix=r,this._listeners=new Set,this._deviceState=i}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(o.ScryptedInterfaceProperty))Object.defineProperty(n.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:()=>s,ScryptedInterfaceProperty:()=>r,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>n,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>m,ScryptedMimeTypes:()=>h});class s{}let r;!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"}(r||(r={}));const o={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 n,a,c,d,u,p,l,m,h;!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"}(n||(n={})),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"}(m||(m={})),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"}(h||(h={}))}},t={};function i(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,i),o.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 s in t)i.o(t,s)&&!i.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},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 s={};(()=>{"use strict";i.r(s),i.d(s,{default:()=>ae});var e=i(510),t=i.n(e);const r=require("events"),{deviceManager:o}=t();class n extends e.MixinDeviceBase{constructor(t,i,s){super(t,s.mixinDeviceInterfaces,i,s.providerNativeId,s.mixinStorageSuffix),this.settingsGroup=s.group,this.settingsGroupKey=s.groupKey,process.nextTick((()=>o.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(),s=[];try{const e=await t||[];s.push(...e)}catch(e){const t=this.name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),s.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;s.push(...e)}catch(e){const t=o.getDeviceState(this.mixinProviderNativeId).name,i=`${t} Extension settings failed to load.`;this.console.error(i,e),s.push({key:Math.random().toString(),title:t,value:"Settings Error",group:"Errors",description:i,readonly:!0})}return s}async putSetting(t,i){const s=this.settingsGroupKey+":";if(!t?.startsWith(s))return this.mixinDevice.putSetting(t,i);await this.putMixinSetting(t.substring(s.length),i),o.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await o.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 m(e,t){e.bind(t),await(0,r.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function h(e){return m(e,0)}async function f(e,t){return e.bind(t),await(0,r.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,r.once)(e,"listening"),e.address().port}(e);return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:new Promise(((t,i)=>{const s=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(s),t(i)}))}))}}const y=require("process");var S=i.n(y);const v=["decode_slice_header error","no frame!","non-existing PPS"];const{mediaManager:b}=t();async function w(e,t){return new Promise((i=>{const s=r=>{const o=r.toString(),n=o.indexOf(`${t}: `);if(-1!==n){const r=o.substring(n+t.length+1).trim();let a=r.indexOf(" ");const c=r.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",s),e.stderr.removeListener("data",s),i(r.substring(0,a)))}};e.stdout.on("data",s),e.stderr.on("data",s)}))}async function P(e,t){const{console:i}=t;let s,o=!0;const n=new r.EventEmitter;let a,c,d,p,m;n.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{p=e,m=t}));function P(){o&&(n.emit("killed"),n.emit("error",new Error("killed"))),o=!1,O?.kill(),setTimeout((()=>O?.kill("SIGKILL")),1e3),m?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(s)}const M=e.inputArguments.slice();s=setTimeout(P,3e4);const I=e=>{let s;function r(){i.error("timeout waiting for data, killing parser session",e),P()}function o(){t.timeout&&(clearTimeout(s),s=setTimeout(r,t.timeout))}return n.once("killed",(()=>clearTimeout(s))),o(),{resetActivityTimer:o}};let C=!1;const x=["pipe","pipe","pipe"];let D=3;for(const e of Object.keys(t.parsers)){const s=t.parsers[e];if(s.parseDatagram){C=!0;const t=l().createSocket("udp4"),i=await h(t),r=l().createSocket("udp4");await f(r,i.port+1),n.once("killed",(()=>{t.close(),r.close()})),M.push(...s.outputArguments,i.url.replace("udp://","rtp://"));const{resetActivityTimer:o}=I(e);(async()=>{for await(const i of s.parseDatagram(t,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),n.emit(e,i),o()})(),(async()=>{for await(const t of s.parseDatagram(r,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),n.emit(e,t),o()})()}else if(s.tcpProtocol){const t=await g(),r=new URL(s.tcpProtocol);r.port=t.port.toString(),M.push(...s.outputArguments,r.toString());const{resetActivityTimer:o}=I(e);(async()=>{const r=await t.clientPromise;try{for await(const t of s.parse(r,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),n.emit(e,t),o()}catch(e){i.error("rebroadcast parse error",e),P()}})()}else M.push(...s.outputArguments,"pipe:"+D++),x.push("pipe")}C&&(M.push("-sdp_file","pipe:"+D++),x.push("pipe")),M.unshift("-hide_banner"),function(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(" "))}(i,M);const O=u().spawn(await b.getFFmpegPath(),M,{stdio:x});let T;!function(e,t,i,s){const r=!!S().env.SCRYPTED_FFMPEG_NOISY||!!s?.getItem("SCRYPTED_FFMPEG_NOISY");function o(e){const s=o=>{const n=o.toString();for(const e of v)if(-1!==n.indexOf(e))return;if(!(r||i||-1===n.indexOf("frame=")&&-1===n.indexOf("size=")))return e(n),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",s),void t.stderr.removeListener("data",s);e(n)};return s}t.stdout?.on("data",o(e.log)),t.stderr?.on("data",o(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}(i,O,void 0,t?.storage),O.on("exit",P),T=C?new Promise((e=>{const t=[];O.stdio[D-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let A=0;return Object.keys(t.parsers).forEach((async e=>{const s=t.parsers[e];if(!s.parse||s.tcpProtocol)return;const r=O.stdio[3+A];A++;try{const{resetActivityTimer:t}=I(e);for await(const i of s.parse(r,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),n.emit(e,i),t()}catch(e){i.error("rebroadcast parse error",e),P()}})),async function(e){return w(e,"Audio")}(O).then((e=>a=e)),async function(e){return w(e,"Video")}(O).then((e=>c=e)),async function(e){return new Promise((t=>{const i=s=>{const r=s.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(r);o&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(o))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(O).then((e=>d=e)),await y,p=void 0,m=void 0,clearTimeout(s),{sdp:T,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>o,kill:P,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},on(e,t){return n.on(e,t),this},once(e,t){return n.once(e,t),this},removeListener(e,t){return n.removeListener(e,t),this}}}async function M(e,t){const i=await e;let s=!0;const r=()=>{i.removeAllListeners(),i.destroy();const e=o;o=void 0,e?.()};let o=t?.connect((e=>{s&&(s=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),r);i.once("close",(()=>{r()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function I(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,s)=>{const r=()=>{const s=e.read(t);s&&(n(),i(s))},o=()=>{n(),s(new Error(`stream ended during read for minimum ${t} bytes`))},n=()=>{e.removeListener("readable",r),e.removeListener("end",o)};e.on("readable",r),e.on("end",o)}))}const C="\n".charCodeAt(0);async function x(e){return async function(e,t){const i=[];let s=0;for(;;){const r=await I(e,1);if(!r)throw new Error("end of stream");if(r[0]===t)break;i[s++]=r[0]}return Buffer.from(i).toString()}(e,C)}function D(e){return e}function O(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=async function*(e){for(;;){const t=await I(e,8),i=t.readInt32BE(0)-8,s=t.slice(4).toString(),r=await I(e,i);yield{header:t,length:i,type:s,data:r}}}(e);let i,s,r;for await(const e of t)i?s||(s=e):i=e,yield{startStream:r,chunks:[e.header,e.data],type:e.type},i&&s&&!r&&(r=Buffer.concat([i.header,i.data,s.header,s.data]))},findSyncFrame:D}}const{systemManager:T}=t(),A="v4";class k extends e.ScryptedDeviceBase{hasEnabledMixin={};constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=T.getComponent("plugins"),T.listen((async(t,i,s)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(T.getSystemState())){const t=T.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]===A)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]!==A&&(this.hasEnabledMixin[e]=A,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}function E(e){if(!e)return;return e.split("\n").map((e=>e.trim())).find((e=>e.startsWith("a=control:")))?.split("a=control:")?.[1]}const R=require("crypto"),L=require("tls");var _=i.n(L);function B(e){return e}function N(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let s="";-1!==e&&(s=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=s}return t}class H extends class{write(e,t,i){let s=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))s+=`${e}: ${i}\r\n`;s+="\r\n",this.client.write(s),i&&this.client.write(i)}async readMessage(){let e=[];for(;;){let t=await x(this.client);if(t=t.trim(),!t)return e;e.push(t)}}}{cseq=0;constructor(e){super(),this.url=e;const t=new URL(e),i=parseInt(t.port)||554;e.startsWith("rtsps")?this.client=_().connect({rejectUnauthorized:!1,port:i,host:t.hostname}):this.client=c().connect(i,t.hostname)}async request(e,t,i,s){let r;t=t||{},r=i?new URL(i,this.url).toString():this.url;const o=`${e} ${r} RTSP/1.0`;t.CSeq=(this.cseq++).toString(),this.write(o,t,s);const n=N(await this.readMessage()),a=parseInt(n["content-length"]);return a?{headers:n,body:await I(this.client,a)}:{headers:n,body:void 0}}async options(){return this.request("OPTIONS")}async describe(){return this.request("DESCRIBE",{Accept:"application/sdp"})}async setup(e,t){const i={Transport:`RTP/AVP/TCP;unicast;interleaved=${e}-${e+1}`};this.session&&(i.Session=this.session);const s=await this.request("SETUP",i,t);return s.headers.session&&(this.session=s.headers.session),s}async play(){const e={Range:"npt=0.000-"};return this.session&&(e.Session=this.session),await this.request("PLAY",e),this.client}async teardown(){try{return await this.request("TEARDOWN")}finally{this.client.destroy()}}}class U{videoChannel=0;audioChannel=2;udpPorts={video:0,audio:0};constructor(e,t,i){this.client=e,this.sdp=t,this.udp=i,this.session=(0,R.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await x(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 I(this.client,4),t=e.readUInt16BE(2),i=await I(this.client,t),s=e.readUInt8(1);yield{type:s-s%2===this.videoChannel?"video":"audio",rtcp:s%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){this.udp&&this.udpPorts.video?this.sendUdp(this.udpPorts.video,e,t):this.send(e,t?this.videoChannel+1:this.videoChannel)}sendAudio(e,t){this.udp&&this.udpPorts.audio?this.sendUdp(this.udpPorts.audio,e,t):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={},s=t.transport;if(i.Transport=t.transport,i.Session=this.session,s.includes("UDP")){const t=s.match(/.*?client_port=([0-9]+)-([0-9]+)/),[i,r,o]=t;e.includes("audio")?this.udpPorts.audio=parseInt(r):this.udpPorts.video=parseInt(r)}else if(s.includes("TCP")){const t=s.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);e.includes("audio")?this.audioChannel=i:this.videoChannel=i}}this.respond(200,"OK",t,i)}play(e,t){const i={};i["RTP-Info"]=`url=${e}/trackID=0;seq=0;rtptime=0,url=${e}/trackID=1;seq=0;rtptime=0`,i.Range="npt=now-",i.Session=this.session,this.respond(200,"OK",t,i)}async announce(e,t){const i=parseInt(t["content-length"]),s=await I(this.client,i);this.sdp=s.toString();const r={};r.Session=this.session,this.respond(200,"OK",t,r)}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 s=N(e);if(this[t])return await this[t](i,s),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",s,{})}respond(e,t,i,s,r){let o=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(s.CSeq=i.cseq),r&&(s["Content-Length"]=r.length.toString());for(const[e,t]of Object.entries(s))o+=`${e}: ${t}\r\n`;this.console?.log("response headers",o),o+="\r\n",this.client.write(o),r&&this.client.write(r)}}const V=require("stream"),{mediaManager:F}=t();async function j(e,t,i,s,r){let o=!0;const n=new V.EventEmitter,a=parseInt(t.match(/m=audio.* ([0-9]+)/)?.[1]),c=parseInt(t.match(/m=video.* ([0-9]+)/)?.[1]),d=()=>{o&&(n.emit("killed"),n.emit("error",new Error("killed"))),o=!1,e.destroy()};e.on("close",d),e.on("error",d);return(async()=>{const{resetActivityTimer:t}=(e=>{let t;function i(){console.error("timeout waiting for data, killing parser session",e),d()}function s(){r.timeout&&(clearTimeout(t),t=setTimeout(i,r.timeout))}return n.once("killed",(()=>clearTimeout(t))),s(),{resetActivityTimer:s}})("rtsp");for(;;){let i,r;s?(i=await I(e,4),r=i.readUInt16BE(2)):(i=await I(e,2),r=i.readUInt16BE(0));const o=await I(e,r);if(!s){const e=127&o[1],t=Buffer.alloc(2);t[0]=36,e===a?t[1]=0:e===c&&(t[1]=2),i=Buffer.concat([t,i])}const d={chunks:[i,o]};n.emit("rtsp",d),t()}})().finally(d),{sdp:Promise.resolve([Buffer.from(t)]),inputAudioCodec:i.audio.codec,inputVideoCodec:i.video.codec,inputVideoResolution:void 0,isActive:()=>o,kill:d,mediaStreamOptions:i,on(e,t){return n.on(e,t),this},once(e,t){return n.once(e,t),this},removeListener(e,t){return n.removeListener(e,t),this}}}const{mediaManager:K,log:q,systemManager:$,deviceManager:G}=t(),J=1e4,W="prebufferDuration",z="sendKeyframe",Y="Default",Q="AAC or No Audio",X=`${Q} (Copy)`,Z="Compatible Audio",ee="Other Audio",te=["aac","mp3","mp2","opus"],ie="-fflags +genpts",se=[Q,Z,ee],re=["mpegts","mp4","rtsp"];class oe{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;constructor(e,t,i,s){this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=s,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}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)||"";se.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Q),i=-1!==e.indexOf(Z),s=-1!==e.indexOf(ee);return{isUsingDefaultAudioConfig:!(t||i||s),aacAudio:t,compatibleAudio:i,reencodeAudio:s}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,s=0;const{mp4Mode:r}=this.getRebroadcastMode();for(const e of r?this.prebuffers.mp4:this.prebuffers.rtsp){s=s||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const o=Date.now()-s,n=Math.round(i/o*8),a=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";return e.push({title:"Audio Codec Transcoding",group:a,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)||Y,choices:[Y,X,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:a,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:ie,choices:[ie,"-use_wallclock_as_timestamps 1","-v verbose"],combobox:!0},{title:"Rebroadcast Mode",group:a,description:"THIS FEATURE IS IN TESTING. DO NOT CHANGE THIS FROM MPEG-TS. The stream format to use when rebroadcasting.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTSP","RTSP+MP4"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||"MPEG-TS"}),t?e.push({key:"detectedResolution",group:a,title:"Detected Resolution and Bitrate",readonly:!0,value:`${t?.inputVideoResolution?.[0]||"unknown"} @ ${n||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:a,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:a,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:a,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),e}getRebroadcastMode(){const e=this.storage.getItem(this.rebroadcastModeKey),t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),mp4Mode:!t||e?.includes("MP4")}}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(W))||J;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const s=null===i?.audio,o=i?.audio?.codec,{isUsingDefaultAudioConfig:n,aacAudio:a,compatibleAudio:d,reencodeAudio:u}=this.getAudioConfig();let p=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===p&&(p=null);let l=!1;s||o||!n||void 0!==p||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),l=!0),!s&&o&&void 0!==p&&p!==o&&this.console.warn("Audio codec plugin reported vs detected mismatch",o,p);const m=void 0===p?o?.toLowerCase():p?.toLowerCase(),{rtspMode:h,mp4Mode:f}=this.getRebroadcastMode(),g=!h||f,y=!te.includes(m);!g||l||!1===i?.userConfigurable||s||y&&(n&&q.a(`${this.mixin.name} is using the ${m} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",m));const S=["-bsf:a","aac_adtstoasc"],v=[];let b;this.audioDisabled=!1;const w=null===p;let M=!1;if(!l&&n&&y&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",m):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),M=!0),s||l)b=["-an"],this.audioDisabled=!0;else if(u||M)b=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(a||w)b=["-acodec","copy"],b.push(...S);else if(d)b=["-acodec","copy"],b.push(...v);else{b=["-acodec","copy"];const e="aac"===m?S:v;b.push(...e)}const I=["-vcodec","copy"],C={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=C.parsers,this.console.log("rebroadcast mode:",h?"rtsp":"mpegts"),h){const e=function(){let e;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,R.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp","-vcodec","copy","-acodec","copy","-f","rtsp"],findSyncFrame:B,sdp:new Promise((t=>e=t)),async*parse(t,i,s){const r=new U(t);await r.handleSetup(),e(r.sdp);for await(const{type:e,rtcp:t,header:o,packet:n}of r.handleRecord())yield{chunks:[o,n],type:e,width:i,height:s}}}}();this.sdp=e.sdp,C.parsers.rtsp=e}else C.parsers.mpegts={container:"mpegts",outputArguments:[...(x={vcodec:I,acodec:b})?.vcodec||[],...x?.acodec||[],"-f","mpegts"],parse:(D=188,T=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 s=e.read();if(!s){await(0,r.once)(e,"readable");continue}if(t.push(s),i+=s.length,i<D)continue;const o=Buffer.concat(t);T?.(o);const n=o.length%D,a=o.slice(0,o.length-n),c=o.slice(o.length-n);t=[c],i=c.length,yield{chunks:[a]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let s=0;s<i.chunks.length;s++){const r=i.chunks[s];let o=0;for(;o+188<r.length;){const i=r.subarray(o,o+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);o+=188}}}return e}};var x,D,T;f&&(C.parsers.mp4=O({vcodec:I,acodec:b}));const A=await this.mixinDevice.getVideoStream(i),k="x-scrypted/x-rfc4571"===A.mimeType;let L,_;this.storage.removeItem(this.lastDetectedAudioCodecKey);const N=h&&!f;if(N&&k){const e=await K.convertMediaObjectToJSON(A,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:s}=e;L=await j(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,s,!1,C),this.sdp=L.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await K.convertMediaObjectToBuffer(A,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());if(_=i.mediaStreamOptions,N&&"rtsp"===i.mediaStreamOptions?.container&&"scrypted"===i.mediaStreamOptions?.tool){const e=new H(i.url);await e.options();const t=(await e.describe()).body.toString().trim();this.sdp=Promise.resolve(t);const{audio:s,video:r}=function(e){const t=e.split("m="),i=t.find((e=>e.startsWith("audio"))),s=t.find((e=>e.startsWith("video")));return{audio:E(i),video:E(s)}}(t);await e.setup(0,s),await e.setup(2,r);const o=await e.play();L=await j(o,t,i.mediaStreamOptions,!0,C)}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||ie;i.inputArguments.unshift(...e.split(" ")),L=await P(i,C)}}if(L.inputAudioCodec?te.includes(L.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",L.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",L.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,L.inputAudioCodec||"null"),"h264"!==L.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),l)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),L.kill(),this.startPrebufferSession();if(this.parserSession=L,_?.refreshAt){let t,i=_;const s=async()=>{if(!L.isActive)return;const t=await this.mixinDevice.getVideoStream(i),s=await K.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(s.toString());i=o.mediaStreamOptions,r(i)},r=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(s,i)};r(i),L.once("killed",(()=>clearTimeout(t)))}L.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===L&&(this.parserSession=void 0)}));for(const e of re){let i=0;L.on(e,(s=>{const r=this.prebuffers[e],o=Date.now();for("mdat"===s.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),r.push({time:o,chunk:s});r.length&&r[0].time<o-t;)r.shift(),i++;i>1e3&&(this.prebuffers[e]=r.slice(),i=0)}))}return L}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e,t){this.printActiveClients(),this.stopInactive&&(this.activeClients||this.inactivityTimeout&&!t||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.activeClients||(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),e.kill())}),3e4)))}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(z);let s=e?.prebuffer;null==s&&(s=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0),this.console.log(this.streamName,"client request started");const{rtspMode:r}=this.getRebroadcastMode(),o=r?"rtsp":"mpegts",n=this.parsers[e?.container]?e?.container:o;e?.prebuffer&&"mp4"!==n&&"mp4"===e?.container&&(s+=4e3);const a=Object.assign({},t.mediaStreamOptions);a.prebuffer=s;const{reencodeAudio:c}=this.getAudioConfig();let d=!1;r&&"rtsp"===n?d=!0: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[n];for(const e of l)if(!(e.time<u-s))for(const t of e.chunk.chunks)p+=t.length;const m=Math.max(5e5,p).toString(),h=await(async i=>{const r=this.prebuffers[i];let o,n;if("rtsp"===i){this.sdp.then((e=>console.log(e)));const e=await g();o=e.clientPromise.then((async e=>{let t=await this.sdp;const i=new U(e,t);return await i.handlePlayback(),e})),n=e.url.replace("tcp://","rtsp://")}else{const e=await g();o=e.clientPromise,n=`tcp://127.0.0.1:${e.port}`}return M(o,{console:this.console,connect:(o,n)=>{this.activeClients++,this.printActiveClients();const a=Date.now(),c=e=>{o(e)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{n(),this.console.log(this.streamName,"client request ended"),t.removeListener(i,c),t.removeListener("killed",d)};t.on(i,c),t.once("killed",d);for(const e of r)e.time<a-s||c(e.chunk);return()=>{this.activeClients--,this.inactivityCheck(t,!1!==e?.refresh),d()}}}),n})(n),f={url:h,container:n,inputArguments:["-analyzeduration","0","-probesize",m,...this.parsers[n].inputArguments||[],"-f",this.parsers[n].container,"-i",h],mediaStreamOptions:a};return K.createFFmpegMediaObject(f)}}class ne extends n{released=!1;sessions=new Map;constructor(e,t,i,s){super(e,i,{providerNativeId:s,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),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),s=i?i.map((e=>e.id)):[void 0],o=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"),q.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const n=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let a=0;const c=s.length;for(const e of o){let i=this.sessions.get(e);if(!i){const o=t?.find((t=>t.id===e));o?.prebuffer&&q.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const d=o?.name,u=!s.includes(e);if(i=new oe(this,d,e,n||u),this.sessions.set(e,i),e===t?.[0]?.id&&this.sessions.set(void 0,i),n){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(u){this.console.log("stream",d,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;a++,e=!0,this.online=a==c,await(0,r.once)(t,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&a--,e=!1,this.online=a==c}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}G.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:W,value:this.storage.getItem(W)||J.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:z,value:("false"!==this.storage.getItem(z)).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(W))||J;for(const s of e)(this.sessions.get(s.id)?.parserSession||t.includes(s))&&(s.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 ae=new class extends k{constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput;for(const e of Object.keys($.getSystemState())){const t=$.getDeviceById(e);t.mixins?.includes(this.id)&&t.getVideoStreamOptions()}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((()=>G.requestRestart()),i)}async convert(e,t,i){const s=JSON.parse(e.toString()),{url:r,sdp:o}=s,{audioPayloadTypes:n,videoPayloadTypes:a}=function(e){const t=new Set,i=new Set,s=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},r=e.match(/m=audio.*/)?.[0];s(t,r?.split(" ").slice(3));const o=e.match(/m=video.*/)?.[0];return s(i,o?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(o),d=new URL(r);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 U(e,o);await t.handlePlayback();const i=c().connect(parseInt(d.port),d.hostname);for(e.on("close",(()=>{i.destroy()})),i.on("close",(()=>{e.destroy()}));;){const s=(await I(i,2)).readInt16BE(0),r=await I(i,s),o=127&r[1];if(n.has(o))t.sendAudio(r,!1);else{if(!a.has(o))throw e.destroy(),i.destroy(),new Error("unknown payload type "+o);t.sendVideo(r,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){return i.includes(e.ScryptedInterface.VideoCamera)?[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new ne(e,t,i,this.nativeId)}async releaseMixin(e,t){t.online=!0,t.release()}}})();var r=exports="undefined"==typeof exports?{}:exports;for(var o in s)r[o]=s[o];s.__esModule&&Object.defineProperty(r,"__esModule",{value:!0})})();
1
+ (()=>{var e={510:function(e,t,i){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,i,r){void 0===r&&(r=i),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,r){void 0===r&&(r=i),e[r]=t[i]}),n=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||r(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,n(i(268),t);const o=i(268);class s extends o.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=s;class a extends o.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(o.ScryptedInterfaceProperty))Object.defineProperty(s.prototype,i,{set:t(i),get:e(i)}),Object.defineProperty(a.prototype,i,{set:t(i),get:e(i)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/web-sdk instead",e)}t.default=c},268:(e,t,i)=>{"use strict";i.r(t),i.d(t,{DeviceBase:()=>r,ScryptedInterfaceProperty:()=>n,ScryptedInterfaceDescriptors:()=>o,ScryptedDeviceType:()=>s,HumidityMode:()=>a,FanMode:()=>c,TemperatureUnit:()=>d,ThermostatMode:()=>u,LockState:()=>p,MediaPlayerState:()=>l,ScryptedInterface:()=>h,ScryptedMimeTypes:()=>m});class r{}let n;!function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.running="running",e.paused="paused",e.docked="docked",e.thermostatActiveMode="thermostatActiveMode",e.thermostatAvailableModes="thermostatAvailableModes",e.thermostatMode="thermostatMode",e.thermostatSetpoint="thermostatSetpoint",e.thermostatSetpointHigh="thermostatSetpointHigh",e.thermostatSetpointLow="thermostatSetpointLow",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.online="online",e.updateAvailable="updateAvailable",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.binaryState="binaryState",e.intrusionDetected="intrusionDetected",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.humiditySetting="humiditySetting",e.fan="fan"}(n||(n={}));const o={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 s,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"}(s||(s={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(a||(a={})),function(e){e.Auto="Auto",e.Manual="Manual"}(c||(c={})),function(e){e.C="C",e.F="F"}(d||(d={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(u||(u={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(p||(p={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(l||(l={})),function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.VideoCamera="VideoCamera",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.SoftwareUpdate="SoftwareUpdate",e.BufferConverter="BufferConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.IntrusionSensor="IntrusionSensor",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient"}(h||(h={})),function(e){e.AcceptUrlParameter="accept-url",e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.PushEndpoint="text/x-push-endpoint",e.MediaStreamUrl="text/x-media-url",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVSignalingPrefix="x-scrypted-rtc-signaling-",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaObject="x-scrypted/x-scrypted-media-object"}(m||(m={}))}},t={};function i(r){var n=t[r];if(void 0!==n)return n.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,i),o.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:()=>Le});var e=i(510),t=i.n(e);const n=require("events"),{deviceManager:o}=t();class s extends e.MixinDeviceBase{constructor(t){super(t),this.settingsGroup=t.group,this.settingsGroupKey=t.groupKey,process.nextTick((()=>o.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=o.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),o.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}async release(){await o.onMixinEvent(this.id,this,e.ScryptedInterface.Settings,null)}}const a=require("net");var c=i.n(a);const d=require("child_process");var u=i.n(d);const p=require("dgram");var l=i.n(p);async function h(e,t){e.bind(t),await(0,n.once)(e,"listening");const i=e.address().port;return{port:i,url:`udp://127.0.0.1:${i}`}}async function m(e){return h(e,0)}async function f(e,t){return e.bind(t),await(0,n.once)(e,"listening"),{port:t,url:`udp://127.0.0.1:${t}`}}async function g(){const e=new(c().Server),t=await async function(e){return e.listen(0),await(0,n.once)(e,"listening"),e.address().port}(e);return{url:`tcp://127.0.0.1:${t}`,port:t,clientPromise:new Promise(((t,i)=>{const r=setTimeout((()=>{i(new Error("timeout waiting for client"))}),3e4);e.on("connection",(i=>{e.close(),clearTimeout(r),t(i)}))}))}}const y=require("process");var v=i.n(y);const S=["decode_slice_header error","no frame!","non-existing PPS"];function b(e,t,i,r){const n=!!v().env.SCRYPTED_FFMPEG_NOISY||!!r?.getItem("SCRYPTED_FFMPEG_NOISY");function o(e){const r=o=>{const s=o.toString();for(const e of S)if(-1!==s.indexOf(e))return;if(!(n||i||-1===s.indexOf("frame=")&&-1===s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(s)};return r}t.stdout?.on("data",o(e.log)),t.stderr?.on("data",o(e.error)),t.on("exit",(()=>e.log("ffmpeg exited")))}function w(e,t){const i=[];for(const e of t)try{const t=new URL(e);i.push(`${t.protocol}[REDACTED]`)}catch(t){i.push(e)}e.log(i.join(" "))}const{mediaManager:P}=t();async function I(e,t){return new Promise((i=>{const r=n=>{const o=n.toString(),s=o.indexOf(`${t}: `);if(-1!==s){const n=o.substring(s+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),i(n.substring(0,a)))}};e.stdout.on("data",r),e.stderr.on("data",r)}))}function M(e,t,i,r){let n;function o(){console.error("timeout waiting for data, killing parser session",e),t()}function s(){r&&(clearTimeout(n),n=setTimeout(o,r))}return i.once("killed",(()=>clearTimeout(n))),s(),{resetActivityTimer:s}}async function C(e,t){const{console:i}=t;let r,o=!0;const s=new n.EventEmitter;let a,c,d,p,h;s.on("error",(e=>i.error("rebroadcast error",e)));const y=new Promise(((e,t)=>{p=e,h=t}));function v(){o&&(s.emit("killed"),s.emit("error",new Error("killed"))),o=!1,D?.kill(),setTimeout((()=>D?.kill("SIGKILL")),1e3),h?.(new Error("ffmpeg was killed before connecting to the rebroadcast session")),clearTimeout(r)}const S=e.inputArguments.slice();r=setTimeout(v,3e4);let C=!1;const O=["pipe","pipe","pipe"];let x=3;for(const e of Object.keys(t.parsers)){const r=t.parsers[e];if(r.parseDatagram){C=!0;const i=l().createSocket("udp4"),n=await m(i),o=l().createSocket("udp4");await f(o,n.port+1),s.once("killed",(()=>{i.close(),o.close()})),S.push(...r.outputArguments,n.url.replace("udp://","rtp://"));const{resetActivityTimer:a}=M(e,v,s,t?.timeout);(async()=>{for await(const t of r.parseDatagram(i,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),s.emit(e,t),a()})(),(async()=>{for await(const t of r.parseDatagram(o,parseInt(d?.[2]),parseInt(d?.[3]),"rtcp"))p?.(void 0),s.emit(e,t),a()})()}else if(r.tcpProtocol){const n=await g(),o=new URL(r.tcpProtocol);o.port=n.port.toString(),S.push(...r.outputArguments,o.toString());const{resetActivityTimer:a}=M(e,v,s,t?.timeout);(async()=>{const t=await n.clientPromise;try{for await(const i of r.parse(t,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),s.emit(e,i),a()}catch(e){i.error("rebroadcast parse error",e),v()}})()}else S.push(...r.outputArguments,"pipe:"+x++),O.push("pipe")}C&&(S.push("-sdp_file","pipe:"+x++),O.push("pipe")),S.unshift("-hide_banner"),w(i,S);const D=u().spawn(await P.getFFmpegPath(),S,{stdio:O});let A;b(i,D,void 0,t?.storage),D.on("exit",v),A=C?new Promise((e=>{const t=[];D.stdio[x-1].on("data",(i=>{t.push(i),e(t)}))})):Promise.resolve([]);let E=0;return Object.keys(t.parsers).forEach((async e=>{const r=t.parsers[e];if(!r.parse||r.tcpProtocol)return;const n=D.stdio[3+E];E++;try{const{resetActivityTimer:i}=M(e,v,s,t?.timeout);for await(const t of r.parse(n,parseInt(d?.[2]),parseInt(d?.[3])))p?.(void 0),s.emit(e,t),i()}catch(e){i.error("rebroadcast parse error",e),v()}})),async function(e){return I(e,"Audio")}(D).then((e=>a=e)),async function(e){return I(e,"Video")}(D).then((e=>c=e)),async function(e){return new Promise((t=>{const i=r=>{const n=r.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);o&&(e.stdout.removeListener("data",i),e.stderr.removeListener("data",i),t(o))};e.stdout.on("data",i),e.stderr.on("data",i)}))}(D).then((e=>d=e)),await y,p=void 0,h=void 0,clearTimeout(r),{sdp:A,inputAudioCodec:a,inputVideoCodec:c,inputVideoResolution:d,isActive:()=>o,kill:v,mediaStreamOptions:e.mediaStreamOptions||{id:void 0,name:void 0},emit(e,t){return s.emit(e,t),this},on(e,t){return s.on(e,t),this},once(e,t){return s.once(e,t),this},removeListener(e,t){return s.removeListener(e,t),this}}}async function O(e,t){const i=await e;let r=!0;const n=()=>{i.removeAllListeners(),i.destroy();const e=o;o=void 0,e?.()};let o=t?.connect((e=>{r&&(r=!1,e.startStream&&i.write(e.startStream));for(const t of e.chunks)i.write(t);return i.writableLength}),n);i.once("close",(()=>{n()})),i.on("error",(e=>t?.console?.log("client stream ended")))}async function x(e,t){if(!t)return Buffer.alloc(0);{const i=e.read(t);if(i)return i}return new Promise(((i,r)=>{const n=()=>{const r=e.read(t);r&&(s(),i(r))},o=()=>{s(),r(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",n),e.removeListener("end",o)};e.on("readable",n),e.on("end",o)}))}const D="\n".charCodeAt(0);async function A(e){return async function(e,t){const i=[];let r=0;for(;;){const n=await x(e,1);if(!n)throw new Error("end of stream");if(n[0]===t)break;i[r++]=n[0]}return Buffer.from(i).toString()}(e,D)}function E(e){return e}async function*T(e){for(;;){const t=await x(e,8),i=t.readInt32BE(0)-8,r=t.slice(4).toString(),n=await x(e,i);yield{header:t,length:i,type:r,data:n}}}async function*k(e){let t,i,r;for await(const n of e)t?i||(i=n):t=n,yield{startStream:r,chunks:[n.header,n.data],type:n.type},t&&i&&!r&&(r=Buffer.concat([t.header,t.data,i.header,i.data]))}const{systemManager:R}=t(),_="v4";class L extends e.ScryptedDeviceBase{hasEnabledMixin={};constructor(t){super(t);try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=R.getComponent("plugins"),R.listen((async(t,i,r)=>{i.eventInterface!==e.ScryptedInterface.ScryptedDevice||i.property||this.maybeEnableMixin(t)}));for(const e of Object.keys(R.getSystemState())){const t=R.getDeviceById(e);this.maybeEnableMixin(t)}}async shouldEnableMixin(e){return!0}async maybeEnableMixin(e){if(!e||e.mixins?.includes(this.id))return;if(this.hasEnabledMixin[e.id]===_)return;if(!await this.canMixin(e.type,e.interfaces))return;if(!await this.shouldEnableMixin(e))return;this.log.i("auto enabling mixin for "+e.name);const t=(e.mixins||[]).slice();t.push(this.id);const i=await this.pluginsComponent;await i.setMixins(e.id,t),this.setHasEnabledMixin(e.id)}setHasEnabledMixin(e){this.hasEnabledMixin[e]!==_&&(this.hasEnabledMixin[e]=_,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}const{mediaManager:B}=t();function U(e,t,i=["recvonly"]){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 H=require("crypto");var j=i.n(H);const N=require("tls");var V=i.n(N);const F=require("os");function $(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 G(e,arguments,J(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),z(r,e)},K(e)}function G(e,t,i){return G=W()?Reflect.construct:function(e,t,i){var r=[null];r.push.apply(r,t);var n=new(Function.bind.apply(e,r));return i&&z(n,i.prototype),n},G.apply(null,arguments)}function W(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function z(e,t){return z=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},z(e,t)}function J(e){return J=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},J(e)}let Y=function(e){function t(e,i,...r){var n,o,s;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,(n=!(s=J(t).call(this,i))||"object"!=typeof s&&"function"!=typeof s?q(o):s).code=i||"E_UNEXPECTED",n.params=r,n.wrappedErrors=e,n.name=n.toString(),Error.captureStackTrace&&Error.captureStackTrace(q(n),n.constructor),n}var i,r,n;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&z(e,t)}(t,e),i=t,(r=[{key:"toString",value:function(){return(this.wrappedErrors.length?this.wrappedErrors[this.wrappedErrors.length-1].stack+F.EOL:"")+this.constructor.name+": "+this.code+" ("+this.params.join(", ")+")"}}])&&$(i.prototype,r),n&&$(i,n),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 n=X(e.message),o=(e.wrappedErrors||[]).concat(e);return t||(t=n?e.message:"E_UNEXPECTED"),e.message&&!n&&i.push(e.message),r=new Y(o,t,...i),r},Y.cast=function(e,...t){return Q(e)?e:Y.wrap.apply(Y,[e].concat(t))},Y.bump=function(e,...t){return Q(e)?Y.wrap.apply(Y,[e,e.code].concat(e.params)):Y.wrap.apply(Y,[e].concat(t))};const Z=Y,ee=/\w+=(".*?"|[^",]+)(?=,|$)/g;function te(e,t,i=[]){const r=e.trim().match(ee);if(!r)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",e);const n=r.map(((e,t)=>{const i=e.split("=");if(2!==i.length)throw new Z("E_MALFORMED_QUOTEDKEYVALUE",t,e,i.length);return i})).reduce((function(e,[i,r],n){if(-1===t.indexOf(i))throw new Z("E_UNAUTHORIZED_KEY",n,i);return e[i]=r.replace(/^"(.+(?="$))"$/,"$1"),e}),{});return re(i,n),n}function ie(e,t,i=[]){return re(i,e),t.reduce((function(t,i){return e[i]?t+(t?", ":"")+i+'="'+e[i]+'"':t}),"")}function re(e,t){e.forEach((e=>{if(void 0===t[e])throw new Z("E_REQUIRED_KEY",e)}))}const ne=["realm","nonce"],oe=[...ne,"domain","opaque","stale","algorithm","qop"],se=["username","realm","nonce","uri","response"],ae=[...se,"algorithm","cnonce","opaque","qop","nc"];function ce(e,t){const i=j().createHash(e);return i.update(t),i.digest("hex")}const de={type:"Digest",parseWWWAuthenticateRest:function(e){return te(e,oe,ne)},buildWWWAuthenticateRest:function(e){return ie(e,oe,ne)},parseAuthorizationRest:function(e){return te(e,ae,se)},buildAuthorizationRest:function(e){return ie(e,ae,se)},computeHash:function(e){const t=e.ha1||ce(e.algorithm,[e.username,e.realm,e.password].join(":")),i=ce(e.algorithm,[e.method,e.uri].join(":"));return ce(e.algorithm,[t,e.nonce,e.nc,e.cnonce,e.qop,i].join(":"))}};async function ue(e){let t=[];for(;;){let i=await A(e);if(i=i.trim(),!i)return t;t.push(i)}}function pe(e){const t={};for(const i of e.slice(1)){const e=i.indexOf(":");let r="";-1!==e&&(r=i.substring(e+1).trim());t[i.substring(0,e).toLowerCase()]=r}return t}class le extends class{write(e,t,i){let r=`${e}\r\n`;i&&(t["Content-Length"]=i.length.toString());for(const[e,i]of Object.entries(t))r+=`${e}: ${i}\r\n`;r+="\r\n",this.client.write(r),i&&this.client.write(i)}async readMessage(){return ue(this.client)}}{cseq=0;constructor(e){super(),this.url=e;const t=new URL(e),i=parseInt(t.port)||554;e.startsWith("rtsps")?this.client=V().connect({rejectUnauthorized:!1,port:i,host:t.hostname}):this.client=c().connect(i,t.hostname)}writeRequest(e,t,i,r){t=t||{};let n=this.url;i&&(n+="/"+i);const o=new URL(n);o.username="",o.password="",n=o.toString();const s=`${e} ${n} RTSP/1.0`,a=this.cseq++;t.CSeq=a.toString(),this.authorization&&(t.Authorization=this.authorization),this.session&&(t.Session=this.session),this.write(s,t,r)}async request(e,t,i,r,n){this.writeRequest(e,t,i,r);const o=await this.readMessage(),s=o[0],a=pe(o);if(!s.includes("200")&&!a["www-authenticate"])throw new Error(s);if(a["www-authenticate"]){if(n)throw new Error("auth failed");const o=new URL(this.url),s=de.parseWWWAuthenticateRest(a["www-authenticate"]),c=j().createHash("md5").update(`${o.username}:${s.realm}:${o.password}`).digest("hex"),d=j().createHash("md5").update(`${e}:${o.pathname}`).digest("hex"),u=j().createHash("md5").update(`${c}:${s.nonce}:${d}`).digest("hex"),p={username:o.username,realm:s.realm,nonce:s.nonce,uri:o.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 x(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{videoChannel=0;audioChannel=2;udpPorts={video:0,audio:0};constructor(e,t,i){this.client=e,this.sdp=t,this.udp=i,this.session=(0,H.randomBytes)(4).toString("hex"),t&&(t=t.trim())}async handleSetup(){let e=[];for(;;){let t=await A(this.client);if(t=t.trim(),t)e.push(t);else{if(!await this.headers(e))break;e=[]}}}async handlePlayback(){return this.handleSetup()}async handleTeardown(){return this.handleSetup()}async*handleRecord(){for(;;){const e=await x(this.client,4),t=e.readUInt16BE(2),i=await x(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){this.udp&&this.udpPorts.video?this.sendUdp(this.udpPorts.video,e,t):this.send(e,t?this.videoChannel+1:this.videoChannel)}sendAudio(e,t){this.udp&&this.udpPorts.audio?this.sendUdp(this.udpPorts.audio,e,t):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;if(i.Transport=t.transport,i.Session=this.session,r.includes("UDP")){const t=r.match(/.*?client_port=([0-9]+)-([0-9]+)/),[i,n,o]=t;e.includes("audio")?this.udpPorts.audio=parseInt(n):e.includes("video")&&(this.udpPorts.video=parseInt(n))}else if(r.includes("TCP")){const t=r.match(/.*?interleaved=([0-9]+)-([0-9]+)/);if(t){const i=parseInt(t[1]);parseInt(t[2]);e.includes("audio")?this.audioChannel=i:e.includes("video")&&(this.videoChannel=i)}}this.respond(200,"OK",t,i)}play(e,t){const i={};i["RTP-Info"]=`url=${e}/trackID=0;seq=0;rtptime=0,url=${e}/trackID=1;seq=0;rtptime=0`,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 x(this.client,i);this.sdp=r.toString();const n={};n.Session=this.session,this.respond(200,"OK",t,n)}async record(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async teardown(e,t){const i={};i.Session=this.session,this.respond(200,"OK",t,i)}async headers(e){this.console?.log("request headers",e.join("\n"));let[t,i]=e[0].split(" ",2);t=t.toLowerCase();const r=pe(e);if(this[t])return await this[t](i,r),"play"!==t&&"record"!==t&&"teardown"!==t;this.respond(400,"Bad Request",r,{})}respond(e,t,i,r,n){let o=`RTSP/1.0 ${e} ${t}\r\n`;i.cseq&&(r.CSeq=i.cseq),n&&(r["Content-Length"]=n.length.toString());for(const[e,t]of Object.entries(r))o+=`${e}: ${t}\r\n`;this.console?.log("response headers",o),o+="\r\n",this.client.write(o),n&&this.client.write(n)}}const me=require("stream"),{mediaManager:fe}=t();async function ge(e,t,i,r,n,o){let s=!0;const a=new me.EventEmitter;a.on("error",(t=>e.error("rebroadcast error",t)));const c=parseInt(i.match(/m=audio.* ([0-9]+)/)?.[1]),d=parseInt(i.match(/m=video.* ([0-9]+)/)?.[1]),u=()=>{s&&(a.emit("killed"),a.emit("error",new Error("killed"))),s=!1,t.destroy()};let p,l;t.on("close",u),t.on("error",u),(async()=>{const{resetActivityTimer:e}=M("rtsp",u,a,o?.timeout);for(;;){let i,r;if(n){if(i=await x(t,4),"RTSP"===i.toString()){const e=pe(await ue(t)),i=parseInt(e["content-length"]);i&&await x(t,i);continue}r=i.readUInt16BE(2)}else i=await x(t,2),r=i.readUInt16BE(0);const o=await x(t,r),s=127&o[1];if(!n){const e=Buffer.alloc(2);e[0]=36,s===c?e[1]=0:s===d&&(e[1]=2),i=Buffer.concat([e,i])}let u;s===c?u="rtp-audio":s===d&&(u="rtp-video");const p={chunks:[i,o],type:u};a.emit("rtsp",p),e()}})().finally(u);const h=U(i,"audio"),m=U(i,"video");if(h){const e=h.section.toLowerCase();e.includes("mpeg4")?p="aac":e.includes("pcm")&&(p="pcm")}return m&&m.section.toLowerCase().includes("h264")&&(l="h264"),{sdp:Promise.resolve([Buffer.from(i)]),inputAudioCodec:p,inputVideoCodec:l,inputVideoResolution:void 0,isActive:()=>s,kill:u,mediaStreamOptions:r,emit(e,t){return a.emit(e,t),this},on(e,t){return a.on(e,t),this},once(e,t){return a.once(e,t),this},removeListener(e,t){return a.removeListener(e,t),this}}}const{mediaManager:ye,log:ve,systemManager:Se,deviceManager:be}=t(),we=1e4,Pe="prebufferDuration",Ie="sendKeyframe",Me="Default",Ce="AAC or No Audio",Oe=`${Ce} (Copy)`,xe="Compatible Audio",De="Other Audio",Ae=["aac","mp3","mp2","opus"],Ee="-fflags +genpts",Te=[Ce,xe,De],ke=["mpegts","mp4","rtsp"];class Re{prebuffers={mp4:[],mpegts:[],rtsp:[]};detectedIdrInterval=0;prevIdr=0;audioDisabled=!1;activeClients=0;constructor(e,t,i,r){this.mixin=e,this.streamName=t,this.streamId=i,this.stopInactive=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice,this.audioConfigurationKey="audioConfiguration-"+this.streamId,this.ffmpegInputArgumentsKey="ffmpegInputArguments-"+this.streamId,this.rebroadcastModeKey="rebroadcastMode-"+this.streamId,this.lastDetectedAudioCodecKey="lastDetectedAudioCodec-"+this.streamId}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)||"";Te.find((t=>e.startsWith(t)))||(e="");const t=-1!==e.indexOf(Ce),i=-1!==e.indexOf(xe),r=-1!==e.indexOf(De);return{isUsingDefaultAudioConfig:!(t||i||r),aacAudio:t,compatibleAudio:i,reencodeAudio:r}}async getMixinSettings(){const e=[],t=this.parserSession;let i=0,r=0;const{mp4Mode:n}=this.getRebroadcastMode();for(const e of n?this.prebuffers.mp4:this.prebuffers.rtsp){r=r||e.time;for(const t of e.chunk.chunks)i+=t.byteLength}const o=Date.now()-r,s=Math.round(i/o*8),a=this.streamName?`Rebroadcast: ${this.streamName}`:"Rebroadcast";return e.push({title:"Audio Codec Transcoding",group:a,description:"Configuring your camera to output AAC, MP3, MP2, or Opus is recommended. PCM/G711 cameras should set this to Transcode.",type:"string",key:this.audioConfigurationKey,value:this.storage.getItem(this.audioConfigurationKey)||Me,choices:[Me,Oe,"Compatible Audio (Copy)","Other Audio (Transcode)"]},{title:"FFmpeg Input Arguments Prefix",group:a,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},{title:"Rebroadcast Mode",group:a,description:"THIS FEATURE IS IN TESTING. DO NOT CHANGE THIS FROM MPEG-TS. The stream format to use when rebroadcasting.",placeholder:"MPEG-TS",choices:["MPEG-TS","RTSP"],key:this.rebroadcastModeKey,value:this.storage.getItem(this.rebroadcastModeKey)||"MPEG-TS"}),t?e.push({key:"detectedResolution",group:a,title:"Detected Resolution and Bitrate",readonly:!0,value:`${t?.inputVideoResolution?.[0]||"unknown"} @ ${s||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080, 2000Kb/S, Variable Bit Rate, is recommended."},{key:"detectedCodec",group:a,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:a,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:a,key:"status",description:"Rebroadcast is currently idle and will be started automatically on demand.",value:"Idle",readonly:!0}),e}getRebroadcastMode(){const e=this.storage.getItem(this.rebroadcastModeKey),t=e?.startsWith("RTSP");return{rtspMode:e?.startsWith("RTSP"),mp4Mode:!t||e?.includes("MP4")}}async startPrebufferSession(){this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.rtsp=[];const t=parseInt(this.storage.getItem(Pe))||we;let i;try{i=(await this.mixinDevice.getVideoStreamOptions()).find((e=>e.id===this.streamId))}catch(e){}const r=null===i?.audio,o=i?.audio?.codec,{isUsingDefaultAudioConfig:s,aacAudio:a,compatibleAudio:d,reencodeAudio:p}=this.getAudioConfig();let l=this.storage.getItem(this.lastDetectedAudioCodecKey)||void 0;"null"===l&&(l=null);let h=!1;r||o||!s||void 0!==l||(this.console.warn("Camera did not report an audio codec, muting the audio stream and probing the codec."),h=!0),!r&&o&&void 0!==l&&l!==o&&this.console.warn("Audio codec plugin reported vs detected mismatch",o,l);const m=void 0===l?o?.toLowerCase():l?.toLowerCase(),{rtspMode:f,mp4Mode:g}=this.getRebroadcastMode(),y=!f||g,v=!Ae.includes(m);!y||h||!1===i?.userConfigurable||r||v&&(s&&ve.a(`${this.mixin.name} is using the ${m} audio codec. Configuring your Camera to use AAC, MP3, MP2, or Opus audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`),this.console.warn("Configure your camera to output AAC, MP3, MP2, or Opus audio. Suboptimal audio codec in use:",m));const S=["-bsf:a","aac_adtstoasc"],P=[];let I;this.audioDisabled=!1;const O=null===l;let x=!1;if(!h&&s&&v&&(!1===i?.userConfigurable?this.console.log("camera reports it is not user configurable. transcoding due to incompatible codec",m):this.console.log("camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible."),x=!0),r||h)I=["-an"],this.audioDisabled=!0;else if(p||x)I=["-bsf:a","aac_adtstoasc","-acodec","aac","-ar","8k","-b:a","100k","-bufsize","400k","-ac","1","-profile:a","aac_low","-flags","+global_header"];else if(a||O)I=["-acodec","copy"],I.push(...S);else if(d)I=["-acodec","copy"],I.push(...P);else{I=["-acodec","copy"];const e="aac"===m?S:P;I.push(...e)}const D=["-vcodec","copy"],A={console:this.console,timeout:6e4,parsers:{}};if(this.parsers=A.parsers,this.console.log("rebroadcast mode:",f?"rtsp":"mpegts"),f){const e=function(e){let t;return{container:"rtsp",tcpProtocol:"rtsp://127.0.0.1/"+(0,H.randomBytes)(8).toString("hex"),inputArguments:["-rtsp_transport","tcp"],outputArguments:["-rtsp_transport","tcp",...e?.vcodec||[],...e?.acodec||[],"-f","rtsp"],findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];if("rtp-video"===i.type){const r=31&i.chunks[1].readUInt8(12),n=i.chunks[1].readUInt8(13),o=31&n,s=128&n;if((28===r||29===r)&&5===o&&128==s||5==r)return console.log("sent",t,e.length),e.slice(t)}}return e},sdp:new Promise((e=>t=e)),async*parse(e,i,r){const n=new he(e);await n.handleSetup(),t(n.sdp);for await(const{type:e,rtcp:t,header:o,packet:s}of n.handleRecord())yield{chunks:[o,s],type:e,width:i,height:r}}}}({vcodec:D,acodec:r?["-an"]:["-acodec","copy"]});this.sdp=e.sdp,A.parsers.rtsp=e}else A.parsers.mpegts={container:"mpegts",outputArguments:[...(R={vcodec:D,acodec:I})?.vcodec||[],...R?.acodec||[],"-f","mpegts"],parse:(_=188,L=e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")},async function*(e){let t=[],i=0;for(;;){const r=e.read();if(!r){await(0,n.once)(e,"readable");continue}if(t.push(r),i+=r.length,i<_)continue;const o=Buffer.concat(t);L?.(o);const s=o.length%_,a=o.slice(0,o.length-s),c=o.slice(o.length-s);t=[c],i=c.length,yield{chunks:[a]}}}),findSyncFrame(e){for(let t=0;t<e.length;t++){const i=e[t];for(let r=0;r<i.chunks.length;r++){const n=i.chunks[r];let o=0;for(;o+188<n.length;){const i=n.subarray(o,o+188);if(256==((31&i[1])<<8|i[2])&&32&i[3]&&i[4]>0&&64&i[5])return e.slice(t);o+=188}}}return e}};var R,_,L;g&&(A.parsers.mp4=function(e){return{container:"mp4",outputArguments:[...e?.vcodec||[],...e?.acodec||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=T(e);yield*k(t)},findSyncFrame:E}}({vcodec:D,acodec:I}));const j=await this.mixinDevice.getVideoStream(i),N="x-scrypted/x-rfc4571"===j.mimeType;let V,F;this.storage.removeItem(this.lastDetectedAudioCodecKey);const $=f;let q=!1;if($&&N){q=!0,this.console.log("bypassing ffmpeg: using scrypted rfc4571 parser");const e=await ye.convertMediaObjectToJSON(j,"x-scrypted/x-rfc4571"),{url:t,sdp:i,mediaStreamOptions:r}=e;V=await ge(this.console,function(e){const t=new URL(e);if(!t.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");return c().connect(parseInt(t.port),t.hostname)}(t),i,r,!1,A),this.sdp=V.sdp.then((e=>Buffer.concat(e).toString()))}else{const t=await ye.convertMediaObjectToBuffer(j,e.ScryptedMimeTypes.FFmpegInput),i=JSON.parse(t.toString());if(F=i.mediaStreamOptions,$&&"rtsp"===i.mediaStreamOptions?.container&&"scrypted"===i.mediaStreamOptions?.tool){q=!0,this.console.log("bypassing ffmpeg: using scrypted rtsp/rfc4571 parser");const e=new le(i.url);await e.options();const t=(await e.describe()).body.toString().trim();this.sdp=Promise.resolve(t);const{audio:n,video:o}=function(e,t=["recvonly","sendrecv"]){return{audio:U(e,"audio",t)?.trackId,video:U(e,"video",t)?.trackId}}(t);r||await e.setup(0,n),await e.setup(2,o);const s=await e.play();V=await ge(this.console,s,t,i.mediaStreamOptions,!0,A)}else{const e=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee;i.inputArguments.unshift(...e.split(" ")),V=await C(i,A)}}if(q&&g&&this.getVideoStream({id:this.streamId,refresh:!1}).then((async t=>{const i=this.storage.getItem(this.ffmpegInputArgumentsKey)||Ee,r=await ye.convertMediaObjectToJSON(t,e.ScryptedMimeTypes.FFmpegInput);r.inputArguments.unshift(...i.split(" "));const n=await async function(e,t,i,r){const n=e.slice();n.push(...i,...t,"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4","pipe:3"),n.unshift("-hide_banner"),w(r,n);const o=u().spawn(await B.getFFmpegPath(),n,{stdio:["pipe","pipe","pipe","pipe"]});return b(r,o),{cp:o,generator:T(o.stdio[3])}}(r.inputArguments,I,D,this.console),o=()=>{n.cp.kill("SIGKILL"),V.kill(),n.generator.throw(new Error("killed"))};if(!V.isActive)return void o();V.once("killed",o);const{resetActivityTimer:s}=M("mp4",o,V,A.timeout);for await(const e of k(n.generator))s(),V.emit("mp4",e)})).catch((()=>{})),V.inputAudioCodec?Ae.includes(V.inputAudioCodec?.toLowerCase())?this.console.log("Detected audio codec is mp4/mpegts compatible.",V.inputAudioCodec):this.console.log("Detected audio codec is not mp4/mpegts compatible.",V.inputAudioCodec):this.console.log("No audio stream detected."),this.storage.setItem(this.lastDetectedAudioCodecKey,V.inputAudioCodec||"null"),"h264"!==V.inputVideoCodec&&this.console.error("Video codec is not h264. If there are errors, try changing your camera's encoder output."),h)return this.console.warn("Audio probe complete, ending rebroadcast session and restarting with detected codecs."),V.kill(),this.startPrebufferSession();if(this.parserSession=V,F?.refreshAt){let t,i=F;const r=async()=>{if(!V.isActive)return;const t=await this.mixinDevice.getVideoStream(i),r=await ye.convertMediaObjectToBuffer(t,e.ScryptedMimeTypes.FFmpegInput),o=JSON.parse(r.toString());i=o.mediaStreamOptions,n(i)},n=e=>{const i=e.refreshAt-Date.now()-3e4;this.console.log("refreshing media stream in",i),t=setTimeout(r,i)};n(i),V.once("killed",(()=>clearTimeout(t)))}V.once("killed",(()=>{this.parserSessionPromise=void 0,this.parserSession===V&&(this.parserSession=void 0)}));for(const e of ke){let i=0;V.on(e,(r=>{const n=this.prebuffers[e],o=Date.now();if("mdat"===r.type&&(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o),"rtp-video"===r.type){const e=31&r.chunks[1].readUInt8(12),t=r.chunks[1].readUInt8(13),i=31&t,n=128&t;(28!==e&&29!==e||5!==i||128!=n)&&5!=e||(this.prevIdr&&(this.detectedIdrInterval=o-this.prevIdr),this.prevIdr=o)}for(n.push({time:o,chunk:r});n.length&&n[0].time<o-t;)n.shift(),i++;i>1e3&&(this.prebuffers[e]=n.slice(),i=0)}))}return V}printActiveClients(){this.console.log(this.streamName,"active rebroadcast clients:",this.activeClients)}inactivityCheck(e){this.printActiveClients(),this.stopInactive&&(this.activeClients||this.inactivityTimeout||(clearTimeout(this.inactivityTimeout),this.inactivityTimeout=setTimeout((()=>{this.activeClients||(this.console.log(this.streamName,"terminating rebroadcast due to inactivity"),e.kill())}),3e4)))}async getVideoStream(e){this.ensurePrebufferSession();const t=await this.parserSessionPromise,i="false"!==this.storage.getItem(Ie);let r=e?.prebuffer;null==r&&(r=i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);const{rtspMode:n}=this.getRebroadcastMode(),o=n?"rtsp":"mpegts";let s=this.parsers[e?.container]?e?.container:o;e?.prebuffer&&"mp4"!==s&&"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;n&&"rtsp"===s?d=!0: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[s];for(const e of l)if(!(e.time<u-r))for(const t of e.chunk.chunks)p+=t.length;const h=Math.max(5e5,p).toString(),m=await(async i=>{const n=this.prebuffers[i];let o,s;if("rtsp"===i){const e=await g();o=e.clientPromise.then((async e=>{let t=await this.sdp;const i=new he(e,t);return await i.handlePlayback(),e})),s=e.url.replace("tcp://","rtsp://")}else{const e=await g();o=e.clientPromise,s=`tcp://127.0.0.1:${e.port}`}const a=!1!==e?.refresh;return O(o,{console:this.console,connect:(e,o)=>{a?(this.activeClients++,this.printActiveClients()):this.console.log("passive client request started");const s=Date.now(),c=t=>{e(t)>1e8&&(this.console.log("more than 100MB has been buffered, did downstream die? killing connection.",this.streamName),d())},d=()=>{o(),t.removeListener(i,c),t.removeListener("killed",d)};t.on(i,c),t.once("killed",d);for(const e of n)e.time<s-r||c(e.chunk);return()=>{a?(this.activeClients--,this.inactivityCheck(t)):this.console.log("passive client request ended"),d()}}}),s})(s),f={url:m,container:s,inputArguments:["-analyzeduration","0","-probesize",h,...this.parsers[s].inputArguments||[],"-f",this.parsers[s].container,"-i",m],mediaStreamOptions:a};return ye.createFFmpegMediaObject(f)}}class _e extends s{released=!1;sessions=new Map;constructor(e){super(e),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout((()=>this.ensurePrebufferSessions()),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();const t=e?.id;let i=this.sessions.get(t);return!i||e?.directMediaStream?this.mixinDevice.getVideoStream(e):(i.ensurePrebufferSession(),await i.parserSessionPromise,i=this.sessions.get(t),i?i.getVideoStream(e):this.mixinDevice.getVideoStream(e))}async ensurePrebufferSessions(){const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t),r=i?i.map((e=>e.id)):[void 0],o=t?.map((e=>e.id))||[void 0];if("true"!==this.storage.getItem("warnedCloud")){const e=t?.find((e=>"cloud"===e.source));e&&(this.storage.setItem("warnedCloud","true"),ve.a(`${this.name} is a cloud camera. Prebuffering maintains a persistent stream and will not enabled by default. You must enable the Prebuffer stream manually.`))}const s=this.mixinDeviceInterfaces.includes(e.ScryptedInterface.Battery);let a=0;const c=r.length;for(const e of o){let i=this.sessions.get(e);if(!i){const o=t?.find((t=>t.id===e));o?.prebuffer&&ve.a(`Prebuffer is already available on ${this.name}. If this is a grouped device, disable the Rebroadcast extension.`);const d=o?.name,u=!r.includes(e);if(i=new Re(this,d,e,s||u),this.sessions.set(e,i),e===t?.[0]?.id&&this.sessions.set(void 0,i),s){this.console.log("camera is battery powered, prebuffering and rebroadcasting will only work on demand.");continue}if(u){this.console.log("stream",d,"will be rebroadcast on demand.");continue}(async()=>{for(;this.sessions.get(e)===i&&!this.released;){i.ensurePrebufferSession();let e=!1;try{const t=await i.parserSessionPromise;a++,e=!0,this.online=a==c,await(0,n.once)(t,"killed"),this.console.error("prebuffer session ended")}catch(e){this.console.error("prebuffer session ended with error",e)}finally{e&&a--,e=!1,this.online=a==c}this.console.log("restarting prebuffer session in 5 seconds"),await new Promise((e=>setTimeout(e,5e3)))}this.console.log("exiting prebuffer session (released or restarted with new configuration)")})()}}be.onMixinEvent(this.id,this.mixinProviderNativeId,e.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[];try{const t=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOptions(t);t?.length>0&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:i.map((e=>e.name||"")),choices:t.map((e=>e.name)),multiple:!0})}catch(e){throw this.console.error("error in getVideoStreamOptions",e),e}e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:Pe,value:this.storage.getItem(Pe)||we.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:Ie,value:("false"!==this.storage.getItem(Ie)).toString()});for(const t of new Set([...this.sessions.values()]))if(t)try{e.push(...await t.getMixinSettings())}catch(e){throw this.console.error("error in prebuffer session getMixinSettings",e),e}return e}async putMixinSetting(e,t){const i=this.sessions;this.sessions=new Map,"enabledStreams"===e?this.storage.setItem(e,JSON.stringify(t)):this.storage.setItem(e,t.toString());for(const e of i.values())e?.parserSessionPromise?.then((e=>e.kill()));this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(!e)return;try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter((e=>t.includes(e.name)))}catch(e){}const t=e.find((e=>"cloud"!==e.source));return t?[t]:[]}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const i=parseInt(this.storage.getItem(Pe))||we;for(const r of e)(this.sessions.get(r.id)?.parserSession||t.includes(r))&&(r.prebuffer=i);return e}async release(){this.console.log("prebuffer session releasing if started"),this.released=!0;for(const e of this.sessions.values())e&&(e.clearPrebuffers(),e.parserSessionPromise?.then((t=>{this.console.log("prebuffer session released"),t.kill(),e.clearPrebuffers()})))}}const Le=new class extends L{constructor(t){super(t),this.fromMimeType="x-scrypted/x-rfc4571",this.toMimeType=e.ScryptedMimeTypes.FFmpegInput;for(const e of Object.keys(Se.getSystemState())){const t=Se.getDeviceById(e);t.mixins?.includes(this.id)&&t.getVideoStreamOptions()}const i=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(i/1e3/60)} minutes`),setTimeout((()=>be.requestRestart()),i)}async convert(e,t,i){const r=JSON.parse(e.toString()),{url:n,sdp:o}=r,{audioPayloadTypes:s,videoPayloadTypes:a}=function(e){const t=new Set,i=new Set,r=(e,t)=>{for(const i of t||[])e.add(parseInt(i))},n=e.match(/m=audio.*/)?.[0];r(t,n?.split(" ").slice(3));const o=e.match(/m=video.*/)?.[0];return r(i,o?.split(" ").slice(3)),{audioPayloadTypes:t,videoPayloadTypes:i}}(o),d=new URL(n);if(!d.protocol.startsWith("tcp"))throw new Error("rfc4751 url must be tcp");const{clientPromise:u,url:p}=await g(),l={url:p,inputArguments:["-rtsp_transport","tcp","-i",p.replace("tcp","rtsp")]};return u.then((async e=>{const t=new he(e,o);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 x(i,2)).readInt16BE(0),n=await x(i,r),o=127&n[1];if(s.has(o))t.sendAudio(n,!1);else{if(!a.has(o))throw e.destroy(),i.destroy(),new Error("unknown payload type "+o);t.sendVideo(n,!1)}}})),Buffer.from(JSON.stringify(l))}async canMixin(t,i){return i.includes(e.ScryptedInterface.VideoCamera)?[e.ScryptedInterface.VideoCamera,e.ScryptedInterface.Settings,e.ScryptedInterface.Online]:null}async getMixin(e,t,i){return this.setHasEnabledMixin(i.id),new _e({mixinDevice:e,mixinDeviceState:i,mixinProviderNativeId:this.nativeId,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"})}async releaseMixin(e,t){t.online=!0,t.release()}}})();var n=exports="undefined"==typeof exports?{}:exports;for(var o in r)n[o]=r[o];r.__esModule&&Object.defineProperty(n,"__esModule",{value:!0})})();
2
2
  //# sourceMappingURL=main.nodejs.js.map
package/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.1.172",
3
+ "version": "0.1.176",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -2,10 +2,11 @@
2
2
  import { MixinProvider, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput, RequestMediaStreamOptions, BufferConverter, ResponseMediaStreamOptions } from '@scrypted/sdk';
3
3
  import sdk from '@scrypted/sdk';
4
4
  import { once } from 'events';
5
- import { SettingsMixinDeviceBase } from "@scrypted/common/src/settings-mixin";
6
- import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
7
- import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
5
+ import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
6
+ import { handleRebroadcasterClient, ParserOptions, ParserSession, setupActivityTimer, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
7
+ import { createMpegTsParser, createFragmentedMp4Parser, StreamChunk, StreamParser, MP4Atom, parseMp4StreamChunks } from '@scrypted/common/src/stream-parser';
8
8
  import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
9
+ import { startFFMPegFragmentedMP4Session } from '@scrypted/common/src/ffmpeg-mp4-parser-session';
9
10
  import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
10
11
  import { parsePayloadTypes, parseTrackIds } from '@scrypted/common/src/sdp-utils';
11
12
  import { createRtspParser, RtspClient, RtspServer } from '@scrypted/common/src/rtsp-server';
@@ -177,7 +178,7 @@ class PrebufferSession {
177
178
  choices: [
178
179
  'MPEG-TS',
179
180
  'RTSP',
180
- 'RTSP+MP4',
181
+ // 'RTSP+MP4',
181
182
  ],
182
183
  key: this.rebroadcastModeKey,
183
184
  value: this.storage.getItem(this.rebroadcastModeKey) || 'MPEG-TS',
@@ -402,7 +403,13 @@ class PrebufferSession {
402
403
  });
403
404
  }
404
405
  else {
405
- const parser = createRtspParser();
406
+ const parser = createRtspParser({
407
+ vcodec,
408
+ // the rtsp parser should always stream copy unless audio is soft muted.
409
+ acodec: audioSoftMuted
410
+ ? ['-an']
411
+ : ['-acodec', 'copy'],
412
+ });
406
413
  this.sdp = parser.sdp;
407
414
  rbo.parsers.rtsp = parser;
408
415
  }
@@ -423,14 +430,16 @@ class PrebufferSession {
423
430
  // before launching the parser session, clear out the last detected codec.
424
431
  // an erroneous cached codec could cause ffmpeg to fail to start.
425
432
  this.storage.removeItem(this.lastDetectedAudioCodecKey);
426
- const canUseScryptedParser = rtspMode && !mp4Mode;
433
+ const canUseScryptedParser = rtspMode;// && !mp4Mode;
434
+ let usingScryptedParser = false;
427
435
 
428
436
  if (canUseScryptedParser && isRfc4571) {
429
-
437
+ usingScryptedParser = true;
438
+ this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
430
439
  const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
431
440
  const { url, sdp, mediaStreamOptions } = json;
432
441
 
433
- session = await startRFC4571Parser(connectRFC4571Parser(url), sdp, mediaStreamOptions, false, rbo);
442
+ session = await startRFC4571Parser(this.console, connectRFC4571Parser(url), sdp, mediaStreamOptions, false, rbo);
434
443
  this.sdp = session.sdp.then(buffers => Buffer.concat(buffers).toString());
435
444
  }
436
445
  else {
@@ -441,6 +450,8 @@ class PrebufferSession {
441
450
  if (canUseScryptedParser
442
451
  && ffmpegInput.mediaStreamOptions?.container === 'rtsp'
443
452
  && ffmpegInput.mediaStreamOptions?.tool === 'scrypted') {
453
+ usingScryptedParser = true;
454
+ this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser')
444
455
  const rtspClient = new RtspClient(ffmpegInput.url);
445
456
  await rtspClient.options();
446
457
  const sdpResponse = await rtspClient.describe();
@@ -448,10 +459,11 @@ class PrebufferSession {
448
459
  this.sdp = Promise.resolve(sdp);
449
460
  const { audio, video } = parseTrackIds(sdp);
450
461
  // handle no audio?
451
- await rtspClient.setup(0, audio);
462
+ if (!audioSoftMuted)
463
+ await rtspClient.setup(0, audio);
452
464
  await rtspClient.setup(2, video);
453
465
  const socket = await rtspClient.play();
454
- session = await startRFC4571Parser(socket, sdp, ffmpegInput.mediaStreamOptions, true, rbo);
466
+ session = await startRFC4571Parser(this.console, socket, sdp, ffmpegInput.mediaStreamOptions, true, rbo);
455
467
  }
456
468
  else {
457
469
  // create missing pts from dts so mpegts and mp4 muxing does not fail
@@ -461,6 +473,42 @@ class PrebufferSession {
461
473
  }
462
474
  }
463
475
 
476
+ // if operating in RTSP mode, use a side band ffmpeg process to grab the mp4 segments.
477
+ // ffmpeg adds latency, as well as rewrites timestamps.
478
+ if (usingScryptedParser && mp4Mode) {
479
+ this.getVideoStream({
480
+ id: this.streamId,
481
+ refresh: false,
482
+ })
483
+ .then(async (stream) => {
484
+ const extraInputArguments = this.storage.getItem(this.ffmpegInputArgumentsKey) || DEFAULT_FFMPEG_INPUT_ARGUMENTS;
485
+ const ffmpegInput = await mediaManager.convertMediaObjectToJSON<FFMpegInput>(stream, ScryptedMimeTypes.FFmpegInput);
486
+ ffmpegInput.inputArguments.unshift(...extraInputArguments.split(' '));
487
+ const mp4Session = await startFFMPegFragmentedMP4Session(ffmpegInput.inputArguments, acodec, vcodec, this.console);
488
+
489
+ const kill = () => {
490
+ mp4Session.cp.kill('SIGKILL');
491
+ session.kill();
492
+ mp4Session.generator.throw(new Error('killed'));
493
+ };
494
+
495
+ if (!session.isActive) {
496
+ kill();
497
+ return;
498
+ }
499
+
500
+ session.once('killed', kill);
501
+
502
+ const { resetActivityTimer } = setupActivityTimer('mp4', kill, session, rbo.timeout);
503
+
504
+ for await (const chunk of parseMp4StreamChunks(mp4Session.generator)) {
505
+ resetActivityTimer();
506
+ session.emit('mp4', chunk);
507
+ }
508
+ })
509
+ .catch(() => { });
510
+ }
511
+
464
512
  if (!session.inputAudioCodec) {
465
513
  this.console.log('No audio stream detected.');
466
514
  }
@@ -532,6 +580,17 @@ class PrebufferSession {
532
580
  this.detectedIdrInterval = now - this.prevIdr;
533
581
  this.prevIdr = now;
534
582
  }
583
+ if (chunk.type === 'rtp-video') {
584
+ const fragmentType = chunk.chunks[1].readUInt8(12) & 0x1f;
585
+ const second = chunk.chunks[1].readUInt8(13);
586
+ const nalType = second & 0x1f;
587
+ const startBit = second & 0x80;
588
+ if (((fragmentType === 28 || fragmentType === 29) && nalType === 5 && startBit == 128) || fragmentType == 5) {
589
+ if (this.prevIdr)
590
+ this.detectedIdrInterval = now - this.prevIdr;
591
+ this.prevIdr = now;
592
+ }
593
+ }
535
594
 
536
595
  prebufferContainer.push({
537
596
  time: now,
@@ -557,7 +616,7 @@ class PrebufferSession {
557
616
  this.console.log(this.streamName, 'active rebroadcast clients:', this.activeClients);
558
617
  }
559
618
 
560
- inactivityCheck(session: ParserSession<PrebufferParsers>, refresh: boolean) {
619
+ inactivityCheck(session: ParserSession<PrebufferParsers>) {
561
620
  this.printActiveClients();
562
621
  if (!this.stopInactive)
563
622
  return;
@@ -567,7 +626,7 @@ class PrebufferSession {
567
626
  // by default, clients disconnecting will reset the inactivity timeout.
568
627
  // but in some cases, like optimistic prebuffer stream snapshots (google sdm)
569
628
  // we do not want that behavior.
570
- if (this.inactivityTimeout && !refresh)
629
+ if (this.inactivityTimeout)
571
630
  return;
572
631
 
573
632
  clearTimeout(this.inactivityTimeout)
@@ -588,6 +647,7 @@ class PrebufferSession {
588
647
  let requestedPrebuffer = options?.prebuffer;
589
648
  if (requestedPrebuffer == null) {
590
649
  if (sendKeyframe) {
650
+ // get into the general area of finding a sync frame.
591
651
  requestedPrebuffer = Math.max(4000, (this.detectedIdrInterval || 4000)) * 1.5;
592
652
  }
593
653
  else {
@@ -595,7 +655,16 @@ class PrebufferSession {
595
655
  }
596
656
  }
597
657
 
598
- this.console.log(this.streamName, 'client request started');
658
+ const { rtspMode } = this.getRebroadcastMode();
659
+ const defaultContainer = rtspMode ? 'rtsp' : 'mpegts';
660
+
661
+ let container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
662
+
663
+ // If a mp4 prebuffer was explicitly requested, but an mp4 prebuffer is not available (rtsp mode),
664
+ // rewind a little bit earlier to gaurantee a valid full segment of that length is sent.
665
+ if (options?.prebuffer && container !== 'mp4' && options?.container === 'mp4') {
666
+ requestedPrebuffer += (this.detectedIdrInterval || 4000) * 1.5;
667
+ }
599
668
 
600
669
  const createContainerServer = async (container: PrebufferParsers) => {
601
670
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
@@ -604,7 +673,6 @@ class PrebufferSession {
604
673
  let containerUrl: string;
605
674
 
606
675
  if (container === 'rtsp') {
607
- this.sdp.then(sdp => console.log(sdp));
608
676
  const client = await listenZeroSingleClient();
609
677
  socketPromise = client.clientPromise.then(async (socket) => {
610
678
  let sdp = await this.sdp;
@@ -621,11 +689,18 @@ class PrebufferSession {
621
689
  containerUrl = `tcp://127.0.0.1:${client.port}`
622
690
  }
623
691
 
692
+ const isActiveClient = options?.refresh !== false;
693
+
624
694
  handleRebroadcasterClient(socketPromise, {
625
695
  console: this.console,
626
696
  connect: (writeData, destroy) => {
627
- this.activeClients++;
628
- this.printActiveClients();
697
+ if (isActiveClient) {
698
+ this.activeClients++;
699
+ this.printActiveClients();
700
+ }
701
+ else {
702
+ this.console.log('passive client request started');
703
+ }
629
704
 
630
705
  const now = Date.now();
631
706
 
@@ -639,7 +714,6 @@ class PrebufferSession {
639
714
 
640
715
  const cleanup = () => {
641
716
  destroy();
642
- this.console.log(this.streamName, 'client request ended');
643
717
  session.removeListener(container, safeWriteData);
644
718
  session.removeListener('killed', cleanup);
645
719
  }
@@ -665,8 +739,13 @@ class PrebufferSession {
665
739
  }
666
740
 
667
741
  return () => {
668
- this.activeClients--;
669
- this.inactivityCheck(session, options?.refresh !== false);
742
+ if (isActiveClient) {
743
+ this.activeClients--;
744
+ this.inactivityCheck(session);
745
+ }
746
+ else {
747
+ this.console.log('passive client request ended');
748
+ }
670
749
  cleanup();
671
750
  };
672
751
  }
@@ -675,18 +754,6 @@ class PrebufferSession {
675
754
  return containerUrl;
676
755
  }
677
756
 
678
- const { rtspMode } = this.getRebroadcastMode();
679
- const defaultContainer = rtspMode ? 'rtsp' : 'mpegts';
680
-
681
- const container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
682
-
683
- // If a mp4 prebuffer was explicitly requested, but an mp4 prebuffer is not available (rtsp mode),
684
- // rewind a little bit earlier to gaurantee a full segment of that length
685
- // is sent.
686
- if (options?.prebuffer && container !== 'mp4' && options?.container === 'mp4') {
687
- requestedPrebuffer += 4000;
688
- }
689
-
690
757
  const mediaStreamOptions: MediaStreamOptions = Object.assign({}, session.mediaStreamOptions);
691
758
 
692
759
  mediaStreamOptions.prebuffer = requestedPrebuffer;
@@ -769,13 +836,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
769
836
  released = false;
770
837
  sessions = new Map<string, PrebufferSession>();
771
838
 
772
- constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
773
- super(mixinDevice, mixinDeviceState, {
774
- providerNativeId,
775
- mixinDeviceInterfaces,
776
- group: "Prebuffer Settings",
777
- groupKey: "prebuffer",
778
- });
839
+ constructor(options: SettingsMixinDeviceOptions<VideoCamera>) {
840
+ super(options);
779
841
 
780
842
  this.delayStart();
781
843
  }
@@ -1086,7 +1148,14 @@ class PrebufferProvider extends AutoenableMixinProvider implements MixinProvider
1086
1148
 
1087
1149
  async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
1088
1150
  this.setHasEnabledMixin(mixinDeviceState.id);
1089
- return new PrebufferMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
1151
+ return new PrebufferMixin({
1152
+ mixinDevice,
1153
+ mixinDeviceState,
1154
+ mixinProviderNativeId: this.nativeId,
1155
+ mixinDeviceInterfaces,
1156
+ group: "Prebuffer Settings",
1157
+ groupKey: "prebuffer",
1158
+ });
1090
1159
  }
1091
1160
  async releaseMixin(id: string, mixinDevice: any) {
1092
1161
  mixinDevice.online = true;
package/src/rfc4571.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { ParserOptions, ParserSession } from "@scrypted/common/src/ffmpeg-rebroadcast";
2
- import { readLength } from "@scrypted/common/src/read-stream";
1
+ import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
2
+ import { readLength, readLine } from "@scrypted/common/src/read-stream";
3
3
  import sdk, { MediaObject, 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";
7
- import { RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
7
+ import { parseHeaders, readMessage, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
8
+ import { findTrack } from "@scrypted/common/src/sdp-utils";
8
9
 
9
10
 
10
11
  const { mediaManager } = sdk;
@@ -19,9 +20,11 @@ export function connectRFC4571Parser(url: string) {
19
20
  }
20
21
 
21
22
 
22
- export async function startRFC4571Parser(socket: net.Socket, sdp: string, mediaStreamOptions: MediaStreamOptions, hasRstpPrefix?: boolean, options?: ParserOptions<"rtsp">): Promise<ParserSession<"rtsp">> {
23
+ export async function startRFC4571Parser(console: Console, socket: net.Socket, sdp: string, mediaStreamOptions: MediaStreamOptions, hasRstpPrefix?: boolean, options?: ParserOptions<"rtsp">): Promise<ParserSession<"rtsp">> {
23
24
  let isActive = true;
24
25
  const events = new EventEmitter();
26
+ // need this to prevent kill from throwing due to uncaught Error during cleanup
27
+ events.on('error', e => console.error('rebroadcast error', e));
25
28
 
26
29
  const audioPt = parseInt((sdp as string).match(/m=audio.* ([0-9]+)/)?.[1]);
27
30
  const videoPt = parseInt((sdp as string).match(/m=video.* ([0-9]+)/)?.[1]);
@@ -38,38 +41,26 @@ export async function startRFC4571Parser(socket: net.Socket, sdp: string, mediaS
38
41
  socket.on('close', kill);
39
42
  socket.on('error', kill);
40
43
 
41
- const setupActivityTimer = (container: string) => {
42
- let dataTimeout: NodeJS.Timeout;
43
-
44
- function dataKill() {
45
- console.error('timeout waiting for data, killing parser session', container);
46
- kill();
47
- }
48
-
49
- function resetActivityTimer() {
50
- if (!options.timeout)
51
- return;
52
- clearTimeout(dataTimeout);
53
- dataTimeout = setTimeout(dataKill, options.timeout);
54
- }
55
-
56
- events.once('killed', () => clearTimeout(dataTimeout));
57
-
58
- resetActivityTimer();
59
- return {
60
- resetActivityTimer,
61
- }
62
- }
63
-
64
-
65
44
  (async () => {
66
- const { resetActivityTimer } = setupActivityTimer('rtsp');
45
+ const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.timeout);
67
46
 
68
47
  while (true) {
69
48
  let header: Buffer;
70
49
  let length: number;
71
50
  if (hasRstpPrefix) {
72
51
  header = await readLength(socket, 4);
52
+ // rtsp over tcp will actually interleave RTSP request/responses
53
+ // within the RTSP data stream. The only way to tell if it's a request/response
54
+ // is to see if the header + data starts with RTSP/1.0 message line.
55
+ // Or RTSP, if looking at only the header bytes. Then grab the response out.
56
+ if (header.toString() === 'RTSP') {
57
+ const response = parseHeaders(await readMessage(socket));
58
+ const cl = parseInt(response['content-length']);
59
+ if (cl)
60
+ await readLength(socket, cl);
61
+ continue;
62
+ }
63
+
73
64
  length = header.readUInt16BE(2);
74
65
  }
75
66
  else {
@@ -77,9 +68,9 @@ export async function startRFC4571Parser(socket: net.Socket, sdp: string, mediaS
77
68
  length = header.readUInt16BE(0);
78
69
  }
79
70
  const data = await readLength(socket, length);
71
+ const pt = data[1] & 0x7f;
80
72
 
81
73
  if (!hasRstpPrefix) {
82
- const pt = data[1] & 0x7f;
83
74
  const prefix = Buffer.alloc(2);
84
75
  prefix[0] = RTSP_FRAME_MAGIC;
85
76
  if (pt === audioPt) {
@@ -91,23 +82,51 @@ export async function startRFC4571Parser(socket: net.Socket, sdp: string, mediaS
91
82
  header = Buffer.concat([prefix, header]);
92
83
  }
93
84
 
85
+ let type: string;
86
+ if (pt === audioPt)
87
+ type = 'rtp-audio';
88
+ else if (pt === videoPt)
89
+ type = 'rtp-video';
90
+
94
91
  const chunk: StreamChunk = {
95
92
  chunks: [header, data],
96
- }
93
+ type,
94
+ };
97
95
  events.emit('rtsp', chunk);
98
96
  resetActivityTimer();
99
97
  }
100
98
  })()
101
99
  .finally(kill);
102
100
 
101
+ let inputAudioCodec: string;
102
+ let inputVideoCodec: string;
103
+ // todo: multiple codecs may be offered, default is the first one in the sdp.
104
+ const audio = findTrack(sdp, 'audio');
105
+ const video = findTrack(sdp, 'video');
106
+ if (audio) {
107
+ const lc = audio.section.toLowerCase();
108
+ if (lc.includes('mpeg4'))
109
+ inputAudioCodec = 'aac';
110
+ else if (lc.includes('pcm'))
111
+ inputAudioCodec = 'pcm';
112
+ }
113
+ if (video) {
114
+ if (video.section.toLowerCase().includes('h264'))
115
+ inputVideoCodec = 'h264';
116
+ }
117
+
103
118
  return {
104
119
  sdp: Promise.resolve([Buffer.from(sdp)]),
105
- inputAudioCodec: mediaStreamOptions.audio.codec,
106
- inputVideoCodec: mediaStreamOptions.video.codec,
120
+ inputAudioCodec,
121
+ inputVideoCodec,
107
122
  inputVideoResolution: undefined,
108
123
  isActive() { return isActive },
109
124
  kill,
110
125
  mediaStreamOptions,
126
+ emit(container: 'rtsp', chunk: StreamChunk) {
127
+ events.emit(container, chunk);
128
+ return this;
129
+ },
111
130
  on(event: string, cb: any) {
112
131
  events.on(event, cb);
113
132
  return this;