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