@scrypted/prebuffer-mixin 0.1.61 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- !function(e,t){for(var r in t)e[r]=t[r]}(window,function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}([function(e,t,r){"use strict";var n=Object.create?function(e,t,r,n){void 0===n&&(n=r),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]},o=function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||n(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,o(r(2),t);const i=r(2);class s{constructor(e){this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=s;class a{constructor(e,t,r,n){this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=n,this._deviceState=r}get storage(){return this._storage||(this._storage=deviceManager.getMixinStorage(this.id,this.mixinProviderNativeId)),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.mixinProviderNativeId,e,t)}_lazyLoadDeviceState(){}release(){}}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 r of Object.values(i.ScryptedInterfaceProperty))Object.defineProperty(s.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(a.prototype,r,{set:t(r),get:e(r)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager:deviceManager,endpointManager:endpointManager,mediaManager:mediaManager,systemManager:systemManager,pluginHostAPI:pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=c},function(e,t){e.exports=require("events")},function(e,t,r){"use strict";let n,o,i,s,a,c,d,u;Object.defineProperty(t,"__esModule",{value:!0}),t.ScryptedInterfaceDescriptors=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterface=t.MediaPlayerState=t.LockState=t.ThermostatMode=t.TemperatureUnit=t.ScryptedDeviceType=void 0,t.ScryptedDeviceType=n,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(n||(t.ScryptedDeviceType=n={})),t.TemperatureUnit=o,function(e){e.C="C",e.F="F"}(o||(t.TemperatureUnit=o={})),t.ThermostatMode=i,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"}(i||(t.ThermostatMode=i={})),t.LockState=s,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(s||(t.LockState=s={})),t.MediaPlayerState=a,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(a||(t.MediaPlayerState=a={})),t.ScryptedInterface=c,function(e){e.ScryptedDevice="ScryptedDevice",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.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",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.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.MediaSource="MediaSource",e.MessagingEndpoint="MessagingEndpoint",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector"}(c||(t.ScryptedInterface=c={})),t.ScryptedInterfaceProperty=d,function(e){e.id="id",e.interfaces="interfaces",e.mixins="mixins",e.info="info",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.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.motionDetected="motionDetected",e.audioDetected="audioDetected",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position"}(d||(t.ScryptedInterfaceProperty=d={})),t.ScryptedMimeTypes=u,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.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(u||(t.ScryptedMimeTypes=u={}));t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",properties:["id","interfaces","mixins","info","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"],methods:["listen","setName","setRoom","setType"]},OnOff:{name:"OnOff",properties:["on"],methods:["turnOff","turnOn"]},Brightness:{name:"Brightness",properties:["brightness"],methods:["setBrightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",properties:["colorTemperature"],methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",properties:["rgb"],methods:["setRgb"]},ColorSettingHsv:{name:"ColorSettingHsv",properties:["hsv"],methods:["setHsv"]},Notifier:{name:"Notifier",properties:[],methods:["sendNotification"]},StartStop:{name:"StartStop",properties:["running"],methods:["start","stop"]},Pause:{name:"Pause",properties:["paused"],methods:["pause","resume"]},Dock:{name:"Dock",properties:["docked"],methods:["dock"]},TemperatureSetting:{name:"TemperatureSetting",properties:["thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"],methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"]},Thermometer:{name:"Thermometer",properties:["temperature","temperatureUnit"],methods:[]},HumiditySensor:{name:"HumiditySensor",properties:["humidity"],methods:[]},Camera:{name:"Camera",properties:[],methods:["takePicture"]},VideoCamera:{name:"VideoCamera",properties:[],methods:["getVideoStream","getVideoStreamOptions"]},Intercom:{name:"Intercom",properties:[],methods:["startIntercom","stopIntercom"]},Lock:{name:"Lock",properties:["lockState"],methods:["lock","unlock"]},PasswordStore:{name:"PasswordStore",properties:[],methods:["addPassword","getPasswords","removePassword"]},Authenticator:{name:"Authenticator",properties:[],methods:["checkPassword"]},Scene:{name:"Scene",properties:[],methods:["activate","deactivate","isReversible"]},Entry:{name:"Entry",properties:[],methods:["closeEntry","openEntry"]},EntrySensor:{name:"EntrySensor",properties:["entryOpen"],methods:[]},DeviceProvider:{name:"DeviceProvider",properties:[],methods:["discoverDevices","getDevice"]},Battery:{name:"Battery",properties:["batteryLevel"],methods:[]},Refresh:{name:"Refresh",properties:[],methods:["getRefreshFrequency","refresh"]},MediaPlayer:{name:"MediaPlayer",properties:[],methods:["getMediaStatus","load","seek","skipNext","skipPrevious"]},Online:{name:"Online",properties:["online"],methods:[]},SoftwareUpdate:{name:"SoftwareUpdate",properties:["updateAvailable"],methods:["checkForUpdate","installUpdate"]},BufferConverter:{name:"BufferConverter",properties:["fromMimeType","toMimeType"],methods:["convert"]},Settings:{name:"Settings",properties:[],methods:["getSettings","putSetting"]},BinarySensor:{name:"BinarySensor",properties:["binaryState"],methods:[]},IntrusionSensor:{name:"IntrusionSensor",properties:["intrusionDetected"],methods:[]},PowerSensor:{name:"PowerSensor",properties:["powerDetected"],methods:[]},AudioSensor:{name:"AudioSensor",properties:["audioDetected"],methods:[]},MotionSensor:{name:"MotionSensor",properties:["motionDetected"],methods:[]},OccupancySensor:{name:"OccupancySensor",properties:["occupied"],methods:[]},FloodSensor:{name:"FloodSensor",properties:["flooded"],methods:[]},UltravioletSensor:{name:"UltravioletSensor",properties:["ultraviolet"],methods:[]},LuminanceSensor:{name:"LuminanceSensor",properties:["luminance"],methods:[]},PositionSensor:{name:"PositionSensor",properties:["position"],methods:[]},MediaSource:{name:"MediaSource",properties:[],methods:["getMedia"]},MessagingEndpoint:{name:"MessagingEndpoint",properties:[],methods:[]},OauthClient:{name:"OauthClient",properties:[],methods:["getOauthUrl","onOauthCallback"]},MixinProvider:{name:"MixinProvider",properties:[],methods:["canMixin","getMixin","releaseMixin"]},HttpRequestHandler:{name:"HttpRequestHandler",properties:[],methods:["onRequest"]},EngineIOHandler:{name:"EngineIOHandler",properties:[],methods:["onConnection"]},PushHandler:{name:"PushHandler",properties:[],methods:["onPush"]},Program:{name:"Program",properties:[],methods:["run"]},Scriptable:{name:"Scriptable",properties:[],methods:["saveScript","loadScripts","eval"]},ObjectDetector:{name:"ObjectDetector",properties:[],methods:["getDetectionInput","getObjectTypes"]}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(10);Object.keys(n).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===n[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return n[e]}}))}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n,o=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=p();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if(Object.prototype.hasOwnProperty.call(e,o)){var i=n?Object.getOwnPropertyDescriptor(e,o):null;i&&(i.get||i.set)?Object.defineProperty(r,o,i):r[o]=e[o]}r.default=e,t&&t.set(e,r);return r}(r(0)),i=(n=r(1))&&n.__esModule?n:{default:n},s=r(5),a=r(6),c=r(3),d=r(11),u=r(13);function p(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return p=function(){return e},e}function l(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}const{mediaManager:m,log:f,systemManager:g,deviceManager:h}=o.default,v=["aac","mp3","mp2","",void 0,null];class y extends s.SettingsMixinDeviceBase{constructor(e,t,r,n){super(e,r,{providerNativeId:n,mixinDeviceInterfaces:t,group:"Rebroadcast and Prebuffer Settings",groupKey:"prebuffer"}),l(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),l(this,"events",new i.default),l(this,"released",!1),l(this,"detectedIdrInterval",0),l(this,"prevIdr",0),l(this,"incompatibleDetected",!1),l(this,"allowImmediateRestart",!1),this.console.log("prebuffer session starting in 10 seconds"),setTimeout(()=>this.ensurePrebufferSession(),1e4)}async getMixinSettings(){var e,t,r;const n=[],o=await this.mixinDevice.getVideoStreamOptions(),i=this.getEnabledMediaStreamOption(o);i&&n.push({title:"Prebuffered Stream",description:"The stream to prebuffer for recordings.",key:"enabledStream",value:i.name,choices:o.map(e=>e.name)});const s=this.session;return n.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:"prebufferDuration",value:this.storage.getItem("prebufferDuration")||15e3.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:"sendKeyframe",value:("false"!==this.storage.getItem("sendKeyframe")).toString()},{title:"Audio Codec Transcoding",description:"Configuring your camera to output AAC, MP3, or MP2 is recommended. PCM/G711 cameras should set this to Reencode.",type:"string",key:"audioConfiguration",value:this.storage.getItem("audioConfiguration")||"MPEG-TS/MP4 Compatible or No Audio (Copy)",choices:["MPEG-TS/MP4 Compatible or No Audio (Copy)","Other Audio (Transcode)","PCM Audio (Copy, !Experimental!)"]},{group:"Media Information",title:"Detected Resolution",readonly:!0,key:"detectedAcodec",value:""+((null==s||null===(e=s.inputVideoResolution)||void 0===e?void 0:e[0])||"unknown"),description:"Configuring your camera to 1920x1080 is recommended."},{group:"Media Information",title:"Detected Video/Audio Codecs",readonly:!0,key:"detectedVcodec",value:((null==s||null===(t=s.inputVideoCodec)||void 0===t?void 0:t.toString())||"unknown")+"/"+((null==s||null===(r=s.inputAudioCodec)||void 0===r?void 0:r.toString())||"unknown"),description:"Configuring your camera to H264 video (2000Kb/s) and AAC/MP3/MP2 audio is recommended."},{group:"Media Information",title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR = FPS * 4 seconds).",readonly:!0,key:"detectedIdr",value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}),n}async putMixinSetting(e,t){var r;this.storage.setItem(e,t.toString()),null===(r=this.prebufferSession)||void 0===r||r.then(e=>e.kill())}ensurePrebufferSession(){this.prebufferSession||this.released||(console.log("prebuffer session started"),this.prebufferSession=this.startPrebufferSession())}getAudioConfig(){const e=this.storage.getItem("audioConfiguration")||"",t=-1!==e.indexOf("PCM Audio"),r=-1!==e.indexOf("Other Audio")||!t&&this.incompatibleDetected;return{audioConfig:e,pcmAudio:t,reencodeAudio:r}}async startPrebufferSession(){var e,t;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const r=parseInt(this.storage.getItem("prebufferDuration"))||15e3,n=this.storage.getItem("enabledStream"),i=await(0,c.probeVideoCamera)(this.mixinDevice);let s;i.options&&(s=i.options.find(e=>e.name===n));const u=null==i||null===(e=i.options)||void 0===e||null===(t=e[0].audio)||void 0===t?void 0:t.codec;this.incompatibleDetected=this.incompatibleDetected||u&&!v.includes(u),this.incompatibleDetected&&this.console.warn("configure your camera to output aac, mp3, or mp2 audio. incompatibl audio codec detected",u);const p=JSON.parse((await m.convertMediaObjectToBuffer(await this.mixinDevice.getVideoStream(s),o.ScryptedMimeTypes.FFmpegInput)).toString()),{audioConfig:l,pcmAudio:g,reencodeAudio:h}=this.getAudioConfig();let y;y=i.noAudio||g?["-an"]:h?[]:["-acodec","copy"];const S=["-vcodec","copy"],b={console:this.console,parsers:{mp4:(0,d.createFragmentedMp4Parser)({vcodec:S,acodec:y}),mpegts:(0,d.createMpegTsParser)({vcodec:S,acodec:y})},parseOnly:!0};g&&(b.parsers.s16le=(0,d.createPCMParser)()),this.parsers=b.parsers;const M=await(0,a.startRebroadcastSession)(p,b);let P;this.session=M;const O=()=>{clearTimeout(P),P=setTimeout(()=>{this.console.error("watchdog for mp4 parser timed out... killing ffmpeg session"),M.kill()},6e4)};M.events.on("mp4-data",O),M.events.once("killed",()=>{this.prebufferSession=void 0,M.events.removeListener("mp4-data",O),clearTimeout(P)}),O(),M.inputAudioCodec?v.includes(M.inputAudioCodec)||(this.console.error("Detected audio codec was not AAC.",M.inputAudioCodec),l||(f.a(`${this.name} is using ${M.inputAudioCodec} audio. Enable Reencode Audio in Rebroadcast Settings Audio Configuration to disable this alert.`),this.incompatibleDetected=!0,this.allowImmediateRestart=!0)):this.console.warn("no audio detected."),"h264"!==M.inputVideoCodec&&this.console.error("video codec is not h264. If there are errors, try changing your camera's encoder output.");for(const e of["mpegts","mp4","s16le"]){const t=e+"-data";let n=this.prebuffers[e],o=0;M.events.on(t,i=>{const s=Date.now();for("mdat"===i.type&&(this.prevIdr&&(this.detectedIdrInterval=s-this.prevIdr),this.prevIdr=s),n.push({time:s,chunk:i});n.length&&n[0].time<s-r;)n.shift(),o++;o>1e3&&(n=this.prebuffers[e]=n.slice(),o=0),this.events.emit(t,i)})}return M}async getVideoStream(e){var t,r,n;this.ensurePrebufferSession();const o=await this.prebufferSession;if(void 0!==(null==e?void 0:e.id)&&e.id!==(null===(t=o.ffmpegInputs.mpegts.mediaStreamOptions)||void 0===t?void 0:t.id))return this.console.log("rebroadcast session cant be used here",e),this.mixinDevice.getVideoStream(e);const i="false"!==this.storage.getItem("sendKeyframe"),s=(null==e?void 0:e.prebuffer)||(i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);if(!(null!=e&&e.prebuffer||i)){return m.createFFmpegMediaObject(o.ffmpegInputs.mpegts)}this.console.log("prebuffer request started");const c=async e=>{const t=e+"-data",r=this.prebuffers[e],{server:n,port:i}=await(0,a.createRebroadcaster)({connect:(e,i)=>{n.close();const a=Date.now(),c=()=>{i(),this.console.log("prebuffer request ended"),this.events.removeListener(t,e),o.events.removeListener("killed",c)};this.events.on(t,e),o.events.once("killed",c);for(const t of r)t.time<a-s||e(t.chunk);return c}});return setTimeout(()=>n.close(),3e4),i},d=(null==e?void 0:e.container)||"mpegts",u=o.ffmpegInputs[d].mediaStreamOptions?Object.assign({},o.ffmpegInputs[d].mediaStreamOptions):{};u.prebuffer=s;const{audioConfig:p,pcmAudio:l,reencodeAudio:f}=this.getAudioConfig();f&&(u.audio={}),u.video&&null!==(r=o.inputVideoResolution)&&void 0!==r&&r[2]&&null!==(n=o.inputVideoResolution)&&void 0!==n&&n[3]&&Object.assign(u.video,{width:parseInt(o.inputVideoResolution[2]),height:parseInt(o.inputVideoResolution[3])});const g={inputArguments:["-f",d,"-i","tcp://127.0.0.1:"+await c(d)],mediaStreamOptions:u};l&&g.inputArguments.push("-f","s16le","-i","tcp://127.0.0.1:"+await c("s16le")),this.console.log("prebuffer ffmpeg input",g.inputArguments);return m.createFFmpegMediaObject(g)}getEnabledMediaStreamOption(e){if((null==e?void 0:e.length)>1){const t=this.storage.getItem("enabledStream");return e.find(e=>e.name===t)||e[0]}}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOption(e);return t||(t={},e.push(t)),t.prebuffer=parseInt(this.storage.getItem("prebufferDuration"))||15e3,e}release(){var e;this.console.log("prebuffer releasing if started"),this.released=!0,null===(e=this.prebufferSession)||void 0===e||e.then(e=>{this.console.log("prebuffer released"),e.kill()})}}class S extends u.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(g.getSystemState())){var t;const r=g.getDeviceById(e);null!==(t=r.mixins)&&void 0!==t&&t.includes(this.id)&&r.getVideoStreamOptions()}const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout(()=>h.requestRestart(),r)}async canMixin(e,t){return t.includes(o.ScryptedInterface.VideoCamera)?[o.ScryptedInterface.VideoCamera,o.ScryptedInterface.Settings]:null}async getMixin(e,t,r){return this.setHasEnabledMixin(r.id),new y(e,t,r,this.nativeId)}async releaseMixin(e,t){t.release()}}var b=new S;t.default=b},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=o();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(Object.prototype.hasOwnProperty.call(e,i)){var s=n?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(r,i,s):r[i]=e[i]}r.default=e,t&&t.set(e,r);return r}(r(0));function o(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return o=function(){return e},e}const{deviceManager:i}=n.default;class s extends n.MixinDeviceBase{constructor(e,t,r){super(e,r.mixinDeviceInterfaces,t,r.providerNativeId),this.settingsGroup=r.group,this.settingsGroupKey=r.groupKey}async getSettings(){const e=(this.mixinDeviceInterfaces.includes(n.ScryptedInterface.Settings)?await this.mixinDevice.getSettings():[])||[],t=await this.getMixinSettings();for(const e of t)e.group=e.group||this.settingsGroup,e.key=this.settingsGroupKey+":"+e.key;return e.push(...t),e}async putSetting(e,t){var r;const o=this.settingsGroupKey+":";if(null==e||!e.startsWith(o))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(o.length),t),null===(r=i.onMixinEvent)||void 0===r||r.call(i,this.id,this.mixinProviderNativeId,n.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseResolution=p,t.parseVideoCodec=m,t.parseAudioCodec=f,t.startRebroadcastSession=async function(e,t){let r,a=0,d=!0;const l=new s.EventEmitter,{console:h}=t;let v,y,S;function b(){d&&l.emit("killed"),d=!1,null==D||D.kill();for(const e of I)null==e||e.close()}function M(){t.timeout&&(clearTimeout(r),r=setTimeout(b,t.timeout))}M();const P={},O=e.inputArguments.slice(),I=[];let w;const x=new Promise(e=>w=e);for(const o of Object.keys(t.parsers)){const s=t.parsers[o],c=o+"-data";if(P[o]={mediaStreamOptions:e.mediaStreamOptions},!t.parseOnly){const{server:t,port:n}=await g({connect:(e,t)=>{a++,clearTimeout(r);const n=()=>{l.removeListener(c,e),l.removeListener("killed",t),a--,0===a&&M(),t()};return l.on(c,e),l.once("killed",n),n}});I.push(t),e.inputArguments=["-f",o,"-i","tcp://127.0.0.1:"+n]}const d=(0,n.createServer)(async e=>{d.close(),w(e);try{const n=o+"-data";for await(const o of s.parse(e,parseInt(null===(t=S)||void 0===t?void 0:t[2]),parseInt(null===(r=S)||void 0===r?void 0:r[3]))){var t,r;l.emit(n,o)}}catch(e){h.error("rebroadcast parse error",e),b()}});I.push(d);const u=await(0,i.listenZeroCluster)(d);O.push(...s.outputArguments,"tcp://127.0.0.1:"+u)}O.unshift("-hide_banner"),h.log(O);const D=o.default.spawn(await u.getFFmpegPath(),O);return(0,c.ffmpegLogInitialOutput)(h,D),D.on("exit",b),f(D).then(e=>v=e),m(D).then(e=>y=e),p(D).then(e=>S=e),await x,{inputAudioCodec:v,inputVideoCodec:y,inputVideoResolution:S,events:l,resetActivityTimer:M,isActive:()=>d,kill:b,servers:I,cp:D,ffmpegInputs:P}},t.createRebroadcaster=g;var n=r(7),o=d(r(8)),i=r(9),s=r(1),a=d(r(0)),c=r(3);function d(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;async function p(e){return new Promise(t=>{const r=n=>{const o=n.toString(),i=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(o);i&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(i))};e.stdout.on("data",r),e.stderr.on("data",r)})}async function l(e,t){return new Promise(r=>{const n=o=>{const i=o.toString(),s=i.indexOf(t+": ");if(-1!==s){const o=i.substring(s+t.length+1).trim();let a=o.indexOf(" ");const c=o.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",n),e.stderr.removeListener("data",n),r(o.substring(0,a)))}};e.stdout.on("data",n),e.stderr.on("data",n)})}async function m(e){return l(e,"Video")}async function f(e){return l(e,"Audio")}async function g(e){const t=(0,n.createServer)(t=>{let r=!0;const n=()=>{t.removeAllListeners(),t.destroy();const e=o;o=void 0,null==e||e()};let o=null==e?void 0:e.connect(e=>{r&&(r=!1,e.startStream&&t.write(e.startStream));for(const r of e.chunks)t.write(r)},n);t.on("end",n),t.on("close",n),t.on("error",n)});return{server:t,port:await(0,i.listenZeroCluster)(t)}}},function(e,t){e.exports=require("net")},function(e,t){e.exports=require("child_process")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.listenZeroCluster=async function(e){for(;;){const t=1e4+Math.round(3e4*Math.random());e.listen(t);try{return await(0,n.once)(e,"listening"),e.address().port}catch(e){}}};var n=r(1)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,r){var o,i;function s(e){const o=i=>{const s=i.toString();for(const e of n)if(-1!==s.indexOf(e))return;if(!r&&(-1!==s.indexOf("frame=")||-1!==s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",o),void t.stderr.removeListener("data",o);e(s)};return o}null===(o=t.stdout)||void 0===o||o.on("data",s(e.log)),null===(i=t.stderr)||void 0===i||i.on("data",s(e.error)),t.on("exit",()=>e.log("ffmpeg exited"))},t.probeVideoCamera=async function(e){let t;try{t=await e.getVideoStreamOptions()||[]}catch(e){}const r=t&&t.length&&null===t[0].audio;return{options:t,noAudio:r}};const n=["decode_slice_header error","no frame!","non-existing PPS"]},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:i}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let n=0;n<r.chunks.length;n++){const o=r.chunks[n];let i=0;for(;i+188<o.length;){const r=o.subarray(i,i+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);i+=188}}}return e}}},t.parseFragmentedMP4=a,t.createFragmentedMp4Parser=function(e){return{container:"mp4",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=a(e);let r,n,o;for await(const e of t)r?n||(n=e):r=e,yield{startStream:o,chunks:[e.header,e.data],type:e.type},r&&n&&!o&&(o=Buffer.concat([r.header,r.data,n.header,n.data]))},findSyncFrame:i}},t.createRawVideoParser=function(e){let t;e=e||{};const{size:r,everyNFrames:n}=e;r&&(t=`scale=${r.width}:${r.height}`);n&&n>1&&(t?t+=",":t="",t+=`select=not(mod(n\\,${n}))`);return{container:"rawvideo",outputArguments:[...t?["-vf",t]:[],"-an","-vcodec","rawvideo","-pix_fmt","yuv420p","-f","rawvideo"],async*parse(e,t,n){if(!t||!n)throw new Error("error parsing rawvideo, unknown width and height");const i=(t=(null==r?void 0:r.width)||t)*(n=(null==r?void 0:r.height)||n)*1.5;for(;;){const r=await(0,o.readLength)(e,i);yield{chunks:[r],width:t,height:n}}},findSyncFrame:i}};var n=r(1),o=r(12);function i(e){return e}function s(e,t){return async function*(r){let o=[],i=0;for(;;){const s=r.read();if(!s){await(0,n.once)(r,"readable");continue}if(o.push(s),i+=s.length,i<e)continue;const a=Buffer.concat(o);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);o=[u],i=u.length,yield{chunks:[d]}}}}async function*a(e){for(;;){const t=await(0,o.readLength)(e,8),r=t.readInt32BE(0)-8,n=t.slice(4).toString(),i=await(0,o.readLength)(e,r);yield{header:t,length:r,type:n,data:i}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise((r,n)=>{const o=()=>{const n=e.read(t);n&&(s(),r(n))},i=()=>{s(),n(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",o),e.removeListener("end",i)};e.on("readable",o),e.on("end",i)})}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=o();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(Object.prototype.hasOwnProperty.call(e,i)){var s=n?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(r,i,s):r[i]=e[i]}r.default=e,t&&t.set(e,r);return r}(r(0));function o(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return o=function(){return e},e}const{systemManager:i}=n.default;class s extends n.ScryptedDeviceBase{constructor(e){var t,r,o;super(e),o={},(r="hasEnabledMixin")in(t=this)?Object.defineProperty(t,r,{value:o,enumerable:!0,configurable:!0,writable:!0}):t[r]=o;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=i.getComponent("plugins"),i.listen(async(e,t,r)=>{t.eventInterface!==n.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)});for(const e of Object.keys(i.getSystemState())){const t=i.getDeviceById(e);this.maybeEnableMixin(t)}}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id])return;if(!await this.canMixin(e.type,e.interfaces))return;this.log.i("auto enabling mixin for "+e.name);const r=e.mixins||[];r.push(this.id);const n=await this.pluginsComponent;await n.setMixins(e.id,r)}setHasEnabledMixin(e){this.hasEnabledMixin[e]||(this.hasEnabledMixin[e]=!0,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=s}]));
1
+ !function(e,t){for(var r in t)e[r]=t[r]}(window,function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}([function(e,t,r){"use strict";var n=Object.create?function(e,t,r,n){void 0===n&&(n=r),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]},i=function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||n(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,i(r(2),t);const o=r(2);class s{constructor(e){this.nativeId=e}get storage(){return this._storage||(this._storage=deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=deviceManager.getDeviceConsole(this.nativeId)),this._console}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=deviceManager.getDeviceState(this.nativeId):this._deviceState=deviceManager.getDeviceState())}onDeviceEvent(e,t){return deviceManager.onDeviceEvent(this.nativeId,e,t)}}t.ScryptedDeviceBase=s;class a{constructor(e,t,r,n){this.mixinDevice=e,this.mixinDeviceInterfaces=t,this.mixinProviderNativeId=n,this._deviceState=r}get storage(){return this._storage||(this._storage=deviceManager.getMixinStorage(this.id,this.mixinProviderNativeId)),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.mixinProviderNativeId,e,t)}_lazyLoadDeviceState(){}release(){}}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 r of Object.values(o.ScryptedInterfaceProperty))Object.defineProperty(s.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(a.prototype,r,{set:t(r),get:e(r)})}();let c={};try{c=Object.assign(c,{log:deviceManager.getDeviceLogger(void 0),deviceManager:deviceManager,endpointManager:endpointManager,mediaManager:mediaManager,systemManager:systemManager,pluginHostAPI:pluginHostAPI})}catch(e){console.error("sdk initialization error, import @scrypted/sdk/types instead",e)}t.default=c},function(e,t){e.exports=require("events")},function(e,t,r){"use strict";let n,i,o,s,a,c,d,u;Object.defineProperty(t,"__esModule",{value:!0}),t.ScryptedInterfaceDescriptors=t.ScryptedMimeTypes=t.ScryptedInterfaceProperty=t.ScryptedInterface=t.MediaPlayerState=t.LockState=t.ThermostatMode=t.TemperatureUnit=t.ScryptedDeviceType=void 0,t.ScryptedDeviceType=n,function(e){e.Builtin="Builtin",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.Speaker="Speaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.Unknown="Unknown"}(n||(t.ScryptedDeviceType=n={})),t.TemperatureUnit=i,function(e){e.C="C",e.F="F"}(i||(t.TemperatureUnit=i={})),t.ThermostatMode=o,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"}(o||(t.ThermostatMode=o={})),t.LockState=s,function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(s||(t.LockState=s={})),t.MediaPlayerState=a,function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(a||(t.MediaPlayerState=a={})),t.ScryptedInterface=c,function(e){e.ScryptedDevice="ScryptedDevice",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.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Authenticator="Authenticator",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",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.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.MediaSource="MediaSource",e.MessagingEndpoint="MessagingEndpoint",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ObjectDetector="ObjectDetector"}(c||(t.ScryptedInterface=c={})),t.ScryptedInterfaceProperty=d,function(e){e.id="id",e.interfaces="interfaces",e.mixins="mixins",e.info="info",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.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.motionDetected="motionDetected",e.audioDetected="audioDetected",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position"}(d||(t.ScryptedInterfaceProperty=d={})),t.ScryptedMimeTypes=u,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.FFmpegInput="x-scrypted/x-ffmpeg-input",e.RTCAVOffer="x-scrypted/x-rtc-av-offer",e.RTCAVAnswer="x-scrypted/x-rtc-av-answer"}(u||(t.ScryptedMimeTypes=u={}));t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",properties:["id","interfaces","mixins","info","name","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"],methods:["listen","setName","setRoom","setType"]},OnOff:{name:"OnOff",properties:["on"],methods:["turnOff","turnOn"]},Brightness:{name:"Brightness",properties:["brightness"],methods:["setBrightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",properties:["colorTemperature"],methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",properties:["rgb"],methods:["setRgb"]},ColorSettingHsv:{name:"ColorSettingHsv",properties:["hsv"],methods:["setHsv"]},Notifier:{name:"Notifier",properties:[],methods:["sendNotification"]},StartStop:{name:"StartStop",properties:["running"],methods:["start","stop"]},Pause:{name:"Pause",properties:["paused"],methods:["pause","resume"]},Dock:{name:"Dock",properties:["docked"],methods:["dock"]},TemperatureSetting:{name:"TemperatureSetting",properties:["thermostatAvailableModes","thermostatMode","thermostatSetpoint","thermostatSetpointHigh","thermostatSetpointLow"],methods:["setThermostatMode","setThermostatSetpoint","setThermostatSetpointHigh","setThermostatSetpointLow"]},Thermometer:{name:"Thermometer",properties:["temperature","temperatureUnit"],methods:[]},HumiditySensor:{name:"HumiditySensor",properties:["humidity"],methods:[]},Camera:{name:"Camera",properties:[],methods:["takePicture"]},VideoCamera:{name:"VideoCamera",properties:[],methods:["getVideoStream","getVideoStreamOptions"]},Intercom:{name:"Intercom",properties:[],methods:["startIntercom","stopIntercom"]},Lock:{name:"Lock",properties:["lockState"],methods:["lock","unlock"]},PasswordStore:{name:"PasswordStore",properties:[],methods:["addPassword","getPasswords","removePassword"]},Authenticator:{name:"Authenticator",properties:[],methods:["checkPassword"]},Scene:{name:"Scene",properties:[],methods:["activate","deactivate","isReversible"]},Entry:{name:"Entry",properties:[],methods:["closeEntry","openEntry"]},EntrySensor:{name:"EntrySensor",properties:["entryOpen"],methods:[]},DeviceProvider:{name:"DeviceProvider",properties:[],methods:["discoverDevices","getDevice"]},Battery:{name:"Battery",properties:["batteryLevel"],methods:[]},Refresh:{name:"Refresh",properties:[],methods:["getRefreshFrequency","refresh"]},MediaPlayer:{name:"MediaPlayer",properties:[],methods:["getMediaStatus","load","seek","skipNext","skipPrevious"]},Online:{name:"Online",properties:["online"],methods:[]},SoftwareUpdate:{name:"SoftwareUpdate",properties:["updateAvailable"],methods:["checkForUpdate","installUpdate"]},BufferConverter:{name:"BufferConverter",properties:["fromMimeType","toMimeType"],methods:["convert"]},Settings:{name:"Settings",properties:[],methods:["getSettings","putSetting"]},BinarySensor:{name:"BinarySensor",properties:["binaryState"],methods:[]},IntrusionSensor:{name:"IntrusionSensor",properties:["intrusionDetected"],methods:[]},PowerSensor:{name:"PowerSensor",properties:["powerDetected"],methods:[]},AudioSensor:{name:"AudioSensor",properties:["audioDetected"],methods:[]},MotionSensor:{name:"MotionSensor",properties:["motionDetected"],methods:[]},OccupancySensor:{name:"OccupancySensor",properties:["occupied"],methods:[]},FloodSensor:{name:"FloodSensor",properties:["flooded"],methods:[]},UltravioletSensor:{name:"UltravioletSensor",properties:["ultraviolet"],methods:[]},LuminanceSensor:{name:"LuminanceSensor",properties:["luminance"],methods:[]},PositionSensor:{name:"PositionSensor",properties:["position"],methods:[]},MediaSource:{name:"MediaSource",properties:[],methods:["getMedia"]},MessagingEndpoint:{name:"MessagingEndpoint",properties:[],methods:[]},OauthClient:{name:"OauthClient",properties:[],methods:["getOauthUrl","onOauthCallback"]},MixinProvider:{name:"MixinProvider",properties:[],methods:["canMixin","getMixin","releaseMixin"]},HttpRequestHandler:{name:"HttpRequestHandler",properties:[],methods:["onRequest"]},EngineIOHandler:{name:"EngineIOHandler",properties:[],methods:["onConnection"]},PushHandler:{name:"PushHandler",properties:[],methods:["onPush"]},Program:{name:"Program",properties:[],methods:["run"]},Scriptable:{name:"Scriptable",properties:[],methods:["saveScript","loadScripts","eval"]},ObjectDetector:{name:"ObjectDetector",properties:[],methods:["getDetectionInput","getObjectTypes"]}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(10);Object.keys(n).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===n[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return n[e]}}))}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n,i=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=p();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(Object.prototype.hasOwnProperty.call(e,i)){var o=n?Object.getOwnPropertyDescriptor(e,i):null;o&&(o.get||o.set)?Object.defineProperty(r,i,o):r[i]=e[i]}r.default=e,t&&t.set(e,r);return r}(r(0)),o=(n=r(1))&&n.__esModule?n:{default:n},s=r(5),a=r(6),c=r(3),d=r(11),u=r(13);function p(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return p=function(){return e},e}function l(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}const{mediaManager:m,log:f,systemManager:h,deviceManager:g}=i.default,v=["aac","mp3","mp2","",void 0,null];class y{constructor(e,t,r){l(this,"prebuffers",{mp4:[],mpegts:[],s16le:[]}),l(this,"events",new o.default),l(this,"detectedIdrInterval",0),l(this,"prevIdr",0),l(this,"incompatibleDetected",!1),l(this,"allowImmediateRestart",!1),l(this,"AUDIO_CONFIGURATION","audioConfiguration-"+this.streamId),this.mixin=e,this.streamName=t,this.streamId=r,this.storage=e.storage,this.console=e.console,this.mixinDevice=e.mixinDevice}ensurePrebufferSession(){this.parserSessionPromise||this.mixin.released||(this.console.log("prebuffer session started",this.streamId),this.parserSessionPromise=this.startPrebufferSession())}getAudioConfig(){const e=this.storage.getItem(this.AUDIO_CONFIGURATION)||"",t=-1!==e.indexOf("PCM Audio"),r=-1!==e.indexOf("Other Audio")||!t&&this.incompatibleDetected;return{audioConfig:e,pcmAudio:t,reencodeAudio:r}}async getMixinSettings(){var e,t,r;const n=[],i=this.parserSession;let o=0,s=0;for(const e of this.prebuffers.mp4){s=s||e.time;for(const t of e.chunk.chunks)o+=t.byteLength}const a=Date.now()-s,c=Math.round(o/a*8),d=this.streamName?"Rebroadcast: "+this.streamName:"Rebroadcast";return n.push({title:"Audio Codec Transcoding",group:d,description:"Configuring your camera to output AAC, MP3, or MP2 is recommended. PCM/G711 cameras should set this to Reencode.",type:"string",key:this.AUDIO_CONFIGURATION,value:this.storage.getItem(this.AUDIO_CONFIGURATION)||"MPEG-TS/MP4 Compatible or No Audio (Copy)",choices:["MPEG-TS/MP4 Compatible or No Audio (Copy)","Other Audio (Transcode)","PCM Audio (Copy, !Experimental!)"]},{key:"detectedResolution",group:d,title:"Detected Resolution and Bitrate",readonly:!0,value:`${(null==i||null===(e=i.inputVideoResolution)||void 0===e?void 0:e[0])||"unknown"} @ ${c||"unknown"} Kb/s`,description:"Configuring your camera to 1920x1080 is recommended."},{key:"detectedCodec",group:d,title:"Detected Video/Audio Codecs",readonly:!0,value:((null==i||null===(t=i.inputVideoCodec)||void 0===t?void 0:t.toString())||"unknown")+"/"+((null==i||null===(r=i.inputAudioCodec)||void 0===r?void 0:r.toString())||"unknown"),description:"Configuring your camera to H264 video (2000Kb/s) and AAC/MP3/MP2 audio is recommended."},{key:"detectedKeyframe",group:d,title:"Detected Keyframe Interval",description:"Configuring your camera to 4 seconds is recommended (IDR = FPS * 4 seconds).",readonly:!0,value:((this.detectedIdrInterval||0)/1e3).toString()||"none"}),n}async startPrebufferSession(){var e,t;this.prebuffers.mp4=[],this.prebuffers.mpegts=[],this.prebuffers.s16le=[];const r=parseInt(this.storage.getItem("prebufferDuration"))||1e4,n=await(0,c.probeVideoCamera)(this.mixinDevice);let o;n.options&&(o=n.options.find(e=>e.id===this.streamId));const s=null==n||null===(e=n.options)||void 0===e||null===(t=e[0].audio)||void 0===t?void 0:t.codec;this.incompatibleDetected=this.incompatibleDetected||s&&!v.includes(s),this.incompatibleDetected&&this.console.warn("configure your camera to output aac, mp3, or mp2 audio. incompatibl audio codec detected",s);const u=JSON.parse((await m.convertMediaObjectToBuffer(await this.mixinDevice.getVideoStream(o),i.ScryptedMimeTypes.FFmpegInput)).toString()),{audioConfig:p,pcmAudio:l,reencodeAudio:h}=this.getAudioConfig();let g;g=n.noAudio||l?["-an"]:h?[]:["-acodec","copy"];const y=["-vcodec","copy"],S={console:this.console,parsers:{mp4:(0,d.createFragmentedMp4Parser)({vcodec:y,acodec:g}),mpegts:(0,d.createMpegTsParser)({vcodec:y,acodec:g})},parseOnly:!0};l&&(S.parsers.s16le=(0,d.createPCMParser)()),this.parsers=S.parsers;const b=await(0,a.startRebroadcastSession)(u,S);let M;this.parserSession=b;const P=()=>{clearTimeout(M),M=setTimeout(()=>{this.console.error("watchdog for mp4 parser timed out... killing ffmpeg session"),b.kill()},6e4)};b.events.on("mp4-data",P),b.events.once("killed",()=>{this.parserSessionPromise=void 0,b.events.removeListener("mp4-data",P),clearTimeout(M)}),P(),b.inputAudioCodec?v.includes(b.inputAudioCodec)||(this.console.error("Detected audio codec was not AAC.",b.inputAudioCodec),p||(f.a(`${this.mixin.name} is using ${b.inputAudioCodec} audio. Enable Reencode Audio in Rebroadcast Settings Audio Configuration to disable this alert.`),this.incompatibleDetected=!0,this.allowImmediateRestart=!0)):this.console.warn("no audio detected."),"h264"!==b.inputVideoCodec&&this.console.error("video codec is not h264. If there are errors, try changing your camera's encoder output.");for(const e of["mpegts","mp4","s16le"]){const t=e+"-data";let n=this.prebuffers[e],i=0;b.events.on(t,o=>{const s=Date.now();for("mdat"===o.type&&(this.prevIdr&&(this.detectedIdrInterval=s-this.prevIdr),this.prevIdr=s),n.push({time:s,chunk:o});n.length&&n[0].time<s-r;)n.shift(),i++;i>1e3&&(n=this.prebuffers[e]=n.slice(),i=0),this.events.emit(t,o)})}return b}async getVideoStream(e){var t,r;this.ensurePrebufferSession();const n=await this.parserSessionPromise,i="false"!==this.storage.getItem("sendKeyframe"),o=(null==e?void 0:e.prebuffer)||(i?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0);if(!(null!=e&&e.prebuffer||i)){return m.createFFmpegMediaObject(n.ffmpegInputs.mpegts)}this.console.log("prebuffer request started",this.streamId);const s=async e=>{const t=e+"-data",r=this.prebuffers[e],{server:i,port:s}=await(0,a.createRebroadcaster)({connect:(e,s)=>{i.close();const a=Date.now(),c=()=>{s(),this.console.log("prebuffer request ended"),this.events.removeListener(t,e),n.events.removeListener("killed",c)};this.events.on(t,e),n.events.once("killed",c);for(const t of r)t.time<a-o||e(t.chunk);return c}});return setTimeout(()=>i.close(),3e4),s},c=(null==e?void 0:e.container)||"mpegts",d=n.ffmpegInputs[c].mediaStreamOptions?Object.assign({},n.ffmpegInputs[c].mediaStreamOptions):{};d.prebuffer=o;const{audioConfig:u,pcmAudio:p,reencodeAudio:l}=this.getAudioConfig();l&&(d.audio={}),d.video&&null!==(t=n.inputVideoResolution)&&void 0!==t&&t[2]&&null!==(r=n.inputVideoResolution)&&void 0!==r&&r[3]&&Object.assign(d.video,{width:parseInt(n.inputVideoResolution[2]),height:parseInt(n.inputVideoResolution[3])});const f={inputArguments:["-f",c,"-i","tcp://127.0.0.1:"+await s(c)],mediaStreamOptions:d};p&&f.inputArguments.push("-f","s16le","-i","tcp://127.0.0.1:"+await s("s16le")),this.console.log("prebuffer ffmpeg input",f.inputArguments);return m.createFFmpegMediaObject(f)}}class S extends s.SettingsMixinDeviceBase{constructor(e,t,r,n){super(e,r,{providerNativeId:n,mixinDeviceInterfaces:t,group:"Prebuffer Settings",groupKey:"prebuffer"}),l(this,"released",!1),l(this,"sessions",new Map),this.delayStart()}delayStart(){this.console.log("prebuffer sessions starting in 5 seconds"),setTimeout(()=>this.ensurePrebufferSessions(),5e3)}async getVideoStream(e){await this.ensurePrebufferSessions();let t=null==e?void 0:e.id;if(!t&&!this.sessions.has(t)){var r;const n=await this.mixinDevice.getVideoStream(e),o=JSON.parse((await m.convertMediaObjectToBuffer(n,i.ScryptedMimeTypes.FFmpegInput)).toString());t=null==o||null===(r=o.mediaStreamOptions)||void 0===r?void 0:r.id,this.sessions.set(null==e?void 0:e.id,this.sessions.get(t))}let n=this.sessions.get(t);return n?(n.ensurePrebufferSession(),await n.parserSessionPromise,n=this.sessions.get(t),n?n.getVideoStream(e):this.mixinDevice.getVideoStream(e)):this.mixinDevice.getVideoStream(e)}async ensurePrebufferSessions(){const e=await this.mixinDevice.getVideoStreamOptions(),t=this.getEnabledMediaStreamOptions(e),r=t?t.map(e=>e.id):[void 0];for(const t of r){let r=this.sessions.get(t);if(!r){var n;const i=null===(n=e.find(e=>e.id===t))||void 0===n?void 0:n.name;r=new y(this,i,t),r.ensurePrebufferSession(),this.sessions.set(t,r)}}g.onMixinEvent(this.id,this.mixinProviderNativeId,i.ScryptedInterface.Settings,void 0)}async getMixinSettings(){const e=[],t=await this.mixinDevice.getVideoStreamOptions(),r=this.getEnabledMediaStreamOptions(t);r&&(null==t?void 0:t.length)>1&&e.push({title:"Prebuffered Streams",description:"The streams to prebuffer. Enable only as necessary to reduce traffic.",key:"enabledStreams",value:r.map(e=>e.name||""),choices:t.map(e=>e.name),multiple:!0}),e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:"prebufferDuration",value:this.storage.getItem("prebufferDuration")||1e4.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:"sendKeyframe",value:("false"!==this.storage.getItem("sendKeyframe")).toString()});for(const t of new Set([...this.sessions.values()]))e.push(...await t.getMixinSettings());return e}async putMixinSetting(e,t){const r=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 r.values()){var n;null===(n=e.parserSessionPromise)||void 0===n||n.then(e=>e.kill())}this.ensurePrebufferSessions()}getEnabledMediaStreamOptions(e){if(e&&e.length){try{const t=JSON.parse(this.storage.getItem("enabledStreams"));return e.filter(e=>t.includes(e.name))}catch(e){}return[e[0]]}}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=this.getEnabledMediaStreamOptions(e);const r=parseInt(this.storage.getItem("prebufferDuration"))||1e4;if(t)for(const e of t)e.prebuffer=r;else e.push({prebuffer:r});return e}release(){this.console.log("prebuffer releasing if started"),this.released=!0;for(const t of this.sessions.values()){var e;null===(e=t.parserSessionPromise)||void 0===e||e.then(e=>{this.console.log("prebuffer released"),e.kill()})}}}class b extends u.AutoenableMixinProvider{constructor(e){super(e);for(const e of Object.keys(h.getSystemState())){var t;const r=h.getDeviceById(e);null!==(t=r.mixins)&&void 0!==t&&t.includes(this.id)&&r.getVideoStreamOptions()}const r=function(){var e=new Date;return e.setHours(24),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e.getTime()-(new Date).getTime()}()+72e5;this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(r/1e3/60)} minutes`),setTimeout(()=>g.requestRestart(),r)}async canMixin(e,t){return t.includes(i.ScryptedInterface.VideoCamera)?[i.ScryptedInterface.VideoCamera,i.ScryptedInterface.Settings]:null}async getMixin(e,t,r){return this.setHasEnabledMixin(r.id),new S(e,t,r,this.nativeId)}async releaseMixin(e,t){t.release()}}var M=new b;t.default=M},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=i();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if(Object.prototype.hasOwnProperty.call(e,o)){var s=n?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(r,o,s):r[o]=e[o]}r.default=e,t&&t.set(e,r);return r}(r(0));function i(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return i=function(){return e},e}const{deviceManager:o}=n.default;class s extends n.MixinDeviceBase{constructor(e,t,r){super(e,r.mixinDeviceInterfaces,t,r.providerNativeId),this.settingsGroup=r.group,this.settingsGroupKey=r.groupKey}async getSettings(){const e=(this.mixinDeviceInterfaces.includes(n.ScryptedInterface.Settings)?await this.mixinDevice.getSettings():[])||[],t=await this.getMixinSettings();for(const e of t)e.group=e.group||this.settingsGroup,e.key=this.settingsGroupKey+":"+e.key;return e.push(...t),e}async putSetting(e,t){var r;const i=this.settingsGroupKey+":";if(null==e||!e.startsWith(i))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(i.length),t),null===(r=o.onMixinEvent)||void 0===r||r.call(o,this.id,this.mixinProviderNativeId,n.ScryptedInterface.Settings,null)}}t.SettingsMixinDeviceBase=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseResolution=p,t.parseVideoCodec=m,t.parseAudioCodec=f,t.startRebroadcastSession=async function(e,t){let r,a=0,d=!0;const l=new s.EventEmitter,{console:g}=t;let v,y,S;function b(){d&&l.emit("killed"),d=!1,null==D||D.kill();for(const e of I)null==e||e.close()}function M(){t.timeout&&(clearTimeout(r),r=setTimeout(b,t.timeout))}M();const P={},O=e.inputArguments.slice(),I=[];let w;const x=new Promise(e=>w=e);for(const i of Object.keys(t.parsers)){const s=t.parsers[i],c=i+"-data";if(P[i]={mediaStreamOptions:e.mediaStreamOptions},!t.parseOnly){const{server:t,port:n}=await h({connect:(e,t)=>{a++,clearTimeout(r);const n=()=>{l.removeListener(c,e),l.removeListener("killed",t),a--,0===a&&M(),t()};return l.on(c,e),l.once("killed",n),n}});I.push(t),e.inputArguments=["-f",i,"-i","tcp://127.0.0.1:"+n]}const d=(0,n.createServer)(async e=>{d.close(),w(e);try{const n=i+"-data";for await(const i of s.parse(e,parseInt(null===(t=S)||void 0===t?void 0:t[2]),parseInt(null===(r=S)||void 0===r?void 0:r[3]))){var t,r;l.emit(n,i)}}catch(e){g.error("rebroadcast parse error",e),b()}});I.push(d);const u=await(0,o.listenZeroCluster)(d);O.push(...s.outputArguments,"tcp://127.0.0.1:"+u)}O.unshift("-hide_banner"),g.log(O);const D=i.default.spawn(await u.getFFmpegPath(),O);return(0,c.ffmpegLogInitialOutput)(g,D),D.on("exit",b),f(D).then(e=>v=e),m(D).then(e=>y=e),p(D).then(e=>S=e),await x,{inputAudioCodec:v,inputVideoCodec:y,inputVideoResolution:S,events:l,resetActivityTimer:M,isActive:()=>d,kill:b,servers:I,cp:D,ffmpegInputs:P}},t.createRebroadcaster=h;var n=r(7),i=d(r(8)),o=r(9),s=r(1),a=d(r(0)),c=r(3);function d(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;async function p(e){return new Promise(t=>{const r=n=>{const i=n.toString(),o=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(i);o&&(e.stdout.removeListener("data",r),e.stderr.removeListener("data",r),t(o))};e.stdout.on("data",r),e.stderr.on("data",r)})}async function l(e,t){return new Promise(r=>{const n=i=>{const o=i.toString(),s=o.indexOf(t+": ");if(-1!==s){const i=o.substring(s+t.length+1).trim();let a=i.indexOf(" ");const c=i.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",n),e.stderr.removeListener("data",n),r(i.substring(0,a)))}};e.stdout.on("data",n),e.stderr.on("data",n)})}async function m(e){return l(e,"Video")}async function f(e){return l(e,"Audio")}async function h(e){const t=(0,n.createServer)(t=>{let r=!0;const n=()=>{t.removeAllListeners(),t.destroy();const e=i;i=void 0,null==e||e()};let i=null==e?void 0:e.connect(e=>{r&&(r=!1,e.startStream&&t.write(e.startStream));for(const r of e.chunks)t.write(r)},n);t.on("end",n),t.on("close",n),t.on("error",n)});return{server:t,port:await(0,o.listenZeroCluster)(t)}}},function(e,t){e.exports=require("net")},function(e,t){e.exports=require("child_process")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.listenZeroCluster=async function(e){for(;;){const t=1e4+Math.round(3e4*Math.random());e.listen(t);try{return await(0,n.once)(e,"listening"),e.address().port}catch(e){}}};var n=r(1)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,r){var i,o;function s(e){const i=o=>{const s=o.toString();for(const e of n)if(-1!==s.indexOf(e))return;if(!r&&(-1!==s.indexOf("frame=")||-1!==s.indexOf("size=")))return e(s),e("video/audio detected, discarding further input"),t.stdout.removeListener("data",i),void t.stderr.removeListener("data",i);e(s)};return i}null===(i=t.stdout)||void 0===i||i.on("data",s(e.log)),null===(o=t.stderr)||void 0===o||o.on("data",s(e.error)),t.on("exit",()=>e.log("ffmpeg exited"))},t.probeVideoCamera=async function(e){let t;try{t=await e.getVideoStreamOptions()||[]}catch(e){}const r=t&&t.length&&null===t[0].audio;return{options:t,noAudio:r}};const n=["decode_slice_header error","no frame!","non-existing PPS"]},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createPCMParser=function(){return{container:"s16le",outputArguments:["-vn","-acodec","pcm_s16le","-f","s16le"],parse:s(512),findSyncFrame:o}},t.createMpegTsParser=function(e){return{container:"mpegts",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-f","mpegts"],parse:s(188,e=>{if(71!=e[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.")}),findSyncFrame(e){for(let t=0;t<e.length;t++){const r=e[t];for(let n=0;n<r.chunks.length;n++){const i=r.chunks[n];let o=0;for(;o+188<i.length;){const r=i.subarray(o,o+188);if(256==((31&r[1])<<8|r[2])&&32&r[3]&&r[4]>0&&64&r[5])return e.slice(t);o+=188}}}return e}}},t.parseFragmentedMP4=a,t.createFragmentedMp4Parser=function(e){return{container:"mp4",outputArguments:[...(null==e?void 0:e.vcodec)||[],...(null==e?void 0:e.acodec)||[],"-movflags","frag_keyframe+empty_moov+default_base_moof","-f","mp4"],async*parse(e){const t=a(e);let r,n,i;for await(const e of t)r?n||(n=e):r=e,yield{startStream:i,chunks:[e.header,e.data],type:e.type},r&&n&&!i&&(i=Buffer.concat([r.header,r.data,n.header,n.data]))},findSyncFrame:o}},t.createRawVideoParser=function(e){let t;e=e||{};const{size:r,everyNFrames:n}=e;r&&(t=`scale=${r.width}:${r.height}`);n&&n>1&&(t?t+=",":t="",t+=`select=not(mod(n\\,${n}))`);return{container:"rawvideo",outputArguments:[...t?["-vf",t]:[],"-an","-vcodec","rawvideo","-pix_fmt","yuv420p","-f","rawvideo"],async*parse(e,t,n){if(!t||!n)throw new Error("error parsing rawvideo, unknown width and height");const o=(t=(null==r?void 0:r.width)||t)*(n=(null==r?void 0:r.height)||n)*1.5;for(;;){const r=await(0,i.readLength)(e,o);yield{chunks:[r],width:t,height:n}}},findSyncFrame:o}};var n=r(1),i=r(12);function o(e){return e}function s(e,t){return async function*(r){let i=[],o=0;for(;;){const s=r.read();if(!s){await(0,n.once)(r,"readable");continue}if(i.push(s),o+=s.length,o<e)continue;const a=Buffer.concat(i);null==t||t(a);const c=a.length%e,d=a.slice(0,a.length-c),u=a.slice(a.length-c);i=[u],o=u.length,yield{chunks:[d]}}}}async function*a(e){for(;;){const t=await(0,i.readLength)(e,8),r=t.readInt32BE(0)-8,n=t.slice(4).toString(),o=await(0,i.readLength)(e,r);yield{header:t,length:r,type:n,data:o}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.readLength=async function(e,t){if(!t)return Buffer.alloc(0);{const r=e.read(t);if(r)return r}return new Promise((r,n)=>{const i=()=>{const n=e.read(t);n&&(s(),r(n))},o=()=>{s(),n(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",i),e.removeListener("end",o)};e.on("readable",i),e.on("end",o)})}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AutoenableMixinProvider=void 0;var n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=i();if(t&&t.has(e))return t.get(e);var r={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if(Object.prototype.hasOwnProperty.call(e,o)){var s=n?Object.getOwnPropertyDescriptor(e,o):null;s&&(s.get||s.set)?Object.defineProperty(r,o,s):r[o]=e[o]}r.default=e,t&&t.set(e,r);return r}(r(0));function i(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return i=function(){return e},e}const{systemManager:o}=n.default;class s extends n.ScryptedDeviceBase{constructor(e){var t,r,i;super(e),i={},(r="hasEnabledMixin")in(t=this)?Object.defineProperty(t,r,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[r]=i;try{this.hasEnabledMixin=JSON.parse(this.storage.getItem("hasEnabledMixin"))}catch(e){this.hasEnabledMixin={}}this.pluginsComponent=o.getComponent("plugins"),o.listen(async(e,t,r)=>{t.eventInterface!==n.ScryptedInterface.ScryptedDevice||t.property||this.maybeEnableMixin(e)});for(const e of Object.keys(o.getSystemState())){const t=o.getDeviceById(e);this.maybeEnableMixin(t)}}async maybeEnableMixin(e){var t;if(!e||null!==(t=e.mixins)&&void 0!==t&&t.includes(this.id))return;if(this.hasEnabledMixin[e.id])return;if(!await this.canMixin(e.type,e.interfaces))return;this.log.i("auto enabling mixin for "+e.name);const r=e.mixins||[];r.push(this.id);const n=await this.pluginsComponent;await n.setMixins(e.id,r)}setHasEnabledMixin(e){this.hasEnabledMixin[e]||(this.hasEnabledMixin[e]=!0,this.storage.setItem("hasEnabledMixin",JSON.stringify(this.hasEnabledMixin)))}}t.AutoenableMixinProvider=s}]));
2
2
  //# sourceMappingURL=main.nodejs.js.map
package/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrypted/prebuffer-mixin",
3
- "version": "0.1.61",
3
+ "version": "0.1.65",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -10,10 +10,10 @@ import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-p
10
10
 
11
11
  const { mediaManager, log, systemManager, deviceManager } = sdk;
12
12
 
13
- const defaultPrebufferDuration = 15000;
13
+ const defaultPrebufferDuration = 10000;
14
14
  const PREBUFFER_DURATION_MS = 'prebufferDuration';
15
15
  const SEND_KEYFRAME = 'sendKeyframe';
16
- const AUDIO_CONFIGURATION = 'audioConfiguration';
16
+ const AUDIO_CONFIGURATION_TEMPLATE = 'audioConfiguration';
17
17
  const COMPATIBLE_AUDIO = 'MPEG-TS/MP4 Compatible or No Audio (Copy)';
18
18
  const OTHER_AUDIO = 'Other Audio';
19
19
  const OTHER_AUDIO_DESCRIPTION = `${OTHER_AUDIO} (Transcode)`;
@@ -26,10 +26,17 @@ interface PrebufferStreamChunk {
26
26
  time: number;
27
27
  }
28
28
 
29
- class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements VideoCamera, Settings {
30
- prebufferSession: Promise<FFMpegRebroadcastSession>;
31
- session: FFMpegRebroadcastSession;
32
- prebuffers = {
29
+ interface Prebuffers {
30
+ mp4: PrebufferStreamChunk[];
31
+ mpegts: PrebufferStreamChunk[];
32
+ s16le: PrebufferStreamChunk[];
33
+ }
34
+
35
+ class PrebufferSession {
36
+
37
+ parserSessionPromise: Promise<FFMpegRebroadcastSession>;
38
+ parserSession: FFMpegRebroadcastSession;
39
+ prebuffers: Prebuffers = {
33
40
  mp4: [],
34
41
  mpegts: [],
35
42
  s16le: [],
@@ -37,65 +44,73 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
37
44
  parsers: { [container: string]: StreamParser };
38
45
 
39
46
  events = new EventEmitter();
40
- released = false;
41
47
  detectedIdrInterval = 0;
42
48
  prevIdr = 0;
43
49
  incompatibleDetected = false;
44
50
  allowImmediateRestart = false;
45
51
 
46
- constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
47
- super(mixinDevice, mixinDeviceState, {
48
- providerNativeId,
49
- mixinDeviceInterfaces,
50
- group: "Rebroadcast and Prebuffer Settings",
51
- groupKey: "prebuffer",
52
- });
52
+ mixinDevice: VideoCamera;
53
+ console: Console;
54
+ storage: Storage;
53
55
 
54
- // to prevent noisy startup/reload/shutdown, delay the prebuffer starting.
55
- this.console.log(`prebuffer session starting in 10 seconds`);
56
- setTimeout(() => this.ensurePrebufferSession(), 10000);
56
+ AUDIO_CONFIGURATION = AUDIO_CONFIGURATION_TEMPLATE + '-' + this.streamId;
57
+
58
+ constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string) {
59
+ this.storage = mixin.storage;
60
+ this.console = mixin.console;
61
+ this.mixinDevice = mixin.mixinDevice;
62
+ }
63
+
64
+ ensurePrebufferSession() {
65
+ if (this.parserSessionPromise || this.mixin.released)
66
+ return;
67
+ this.console.log('prebuffer session started', this.streamId);
68
+ this.parserSessionPromise = this.startPrebufferSession();
69
+ }
70
+
71
+ getAudioConfig(): {
72
+ audioConfig: string,
73
+ pcmAudio: boolean,
74
+ reencodeAudio: boolean,
75
+ } {
76
+ const audioConfig = this.storage.getItem(this.AUDIO_CONFIGURATION) || '';
77
+ // pcm audio only used when explicitly set.
78
+ const pcmAudio = audioConfig.indexOf(PCM_AUDIO) !== -1;
79
+ // reencode audio will be used if explicitly set, OR an incompatible codec was detected, PCM audio was not explicitly set
80
+ const reencodeAudio = audioConfig.indexOf(OTHER_AUDIO) !== -1 || (!pcmAudio && this.incompatibleDetected);
81
+ return {
82
+ audioConfig,
83
+ pcmAudio,
84
+ reencodeAudio,
85
+ }
57
86
  }
58
87
 
59
88
  async getMixinSettings(): Promise<Setting[]> {
60
89
  const settings: Setting[] = [];
61
90
 
62
- const msos = await this.mixinDevice.getVideoStreamOptions();
63
- const enabledStream = this.getEnabledMediaStreamOption(msos);
64
- if (enabledStream) {
65
- settings.push(
66
- {
67
- title: 'Prebuffered Stream',
68
- description: 'The stream to prebuffer for recordings.',
69
- key: 'enabledStream',
70
- value: enabledStream.name,
71
- choices: msos.map(mso => mso.name),
72
- },
73
- )
91
+ const session = this.parserSession;
92
+
93
+ let total = 0;
94
+ let start = 0;
95
+ for (const prebuffer of this.prebuffers.mp4) {
96
+ start = start || prebuffer.time;
97
+ for (const chunk of prebuffer.chunk.chunks) {
98
+ total += chunk.byteLength;
99
+ }
74
100
  }
101
+ const elapsed = Date.now() - start;
102
+ const bitrate = Math.round(total / elapsed * 8);
75
103
 
76
- const session = this.session;
104
+ const group = this.streamName ? `Rebroadcast: ${this.streamName}` : 'Rebroadcast';
77
105
 
78
106
  settings.push(
79
- {
80
- title: 'Prebuffer Duration',
81
- description: 'Duration of the prebuffer in milliseconds.',
82
- type: 'number',
83
- key: PREBUFFER_DURATION_MS,
84
- value: this.storage.getItem(PREBUFFER_DURATION_MS) || defaultPrebufferDuration.toString(),
85
- },
86
- {
87
- title: 'Start at Previous Keyframe',
88
- description: 'Start live streams from the previous key frame. Improves startup time.',
89
- type: 'boolean',
90
- key: SEND_KEYFRAME,
91
- value: (this.storage.getItem(SEND_KEYFRAME) !== 'false').toString(),
92
- },
93
107
  {
94
108
  title: 'Audio Codec Transcoding',
109
+ group,
95
110
  description: 'Configuring your camera to output AAC, MP3, or MP2 is recommended. PCM/G711 cameras should set this to Reencode.',
96
111
  type: 'string',
97
- key: AUDIO_CONFIGURATION,
98
- value: this.storage.getItem(AUDIO_CONFIGURATION) || COMPATIBLE_AUDIO,
112
+ key: this.AUDIO_CONFIGURATION,
113
+ value: this.storage.getItem(this.AUDIO_CONFIGURATION) || COMPATIBLE_AUDIO,
99
114
  choices: [
100
115
  COMPATIBLE_AUDIO,
101
116
  OTHER_AUDIO_DESCRIPTION,
@@ -103,73 +118,43 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
103
118
  ],
104
119
  },
105
120
  {
106
- group: 'Media Information',
107
- title: 'Detected Resolution',
121
+ key: 'detectedResolution',
122
+ group,
123
+ title: 'Detected Resolution and Bitrate',
108
124
  readonly: true,
109
- key: 'detectedAcodec',
110
- value: `${session?.inputVideoResolution?.[0] || "unknown"}`,
125
+ value: `${session?.inputVideoResolution?.[0] || "unknown"} @ ${bitrate || "unknown"} Kb/s`,
111
126
  description: 'Configuring your camera to 1920x1080 is recommended.',
112
127
  },
113
128
  {
114
- group: 'Media Information',
129
+ key: 'detectedCodec',
130
+ group,
115
131
  title: 'Detected Video/Audio Codecs',
116
132
  readonly: true,
117
- key: 'detectedVcodec',
118
133
  value: (session?.inputVideoCodec?.toString() || 'unknown') + '/' + (session?.inputAudioCodec?.toString() || 'unknown'),
119
134
  description: 'Configuring your camera to H264 video (2000Kb/s) and AAC/MP3/MP2 audio is recommended.'
120
135
  },
121
136
  {
122
- group: 'Media Information',
137
+ key: 'detectedKeyframe',
138
+ group,
123
139
  title: 'Detected Keyframe Interval',
124
140
  description: "Configuring your camera to 4 seconds is recommended (IDR = FPS * 4 seconds).",
125
141
  readonly: true,
126
- key: 'detectedIdr',
127
142
  value: ((this.detectedIdrInterval || 0) / 1000).toString() || 'none',
128
143
  },
129
144
  );
130
145
  return settings;
131
146
  }
132
147
 
133
- async putMixinSetting(key: string, value: string | number | boolean): Promise<void> {
134
- this.storage.setItem(key, value.toString());
135
- this.prebufferSession?.then(session => session.kill());
136
- }
137
-
138
- ensurePrebufferSession() {
139
- if (this.prebufferSession || this.released)
140
- return;
141
- console.log(`prebuffer session started`);
142
- this.prebufferSession = this.startPrebufferSession();
143
- }
144
-
145
- getAudioConfig(): {
146
- audioConfig: string,
147
- pcmAudio: boolean,
148
- reencodeAudio: boolean,
149
- } {
150
- const audioConfig = this.storage.getItem(AUDIO_CONFIGURATION) || '';
151
- // pcm audio only used when explicitly set.
152
- const pcmAudio = audioConfig.indexOf(PCM_AUDIO) !== -1;
153
- // reencode audio will be used if explicitly set, OR an incompatible codec was detected, PCM audio was not explicitly set
154
- const reencodeAudio = audioConfig.indexOf(OTHER_AUDIO) !== -1 || (!pcmAudio && this.incompatibleDetected);
155
- return {
156
- audioConfig,
157
- pcmAudio,
158
- reencodeAudio,
159
- }
160
- }
161
-
162
148
  async startPrebufferSession() {
163
149
  this.prebuffers.mp4 = [];
164
150
  this.prebuffers.mpegts = [];
165
151
  this.prebuffers.s16le = [];
166
152
  const prebufferDurationMs = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
167
153
 
168
- const enabledStream = this.storage.getItem('enabledStream');
169
154
  const probe = await probeVideoCamera(this.mixinDevice);
170
155
  let mso: MediaStreamOptions;
171
156
  if (probe.options) {
172
- mso = probe.options.find(mso => mso.name === enabledStream);
157
+ mso = probe.options.find(mso => mso.id === this.streamId);
173
158
  }
174
159
  const probeAudioCodec = probe?.options?.[0].audio?.codec;
175
160
  this.incompatibleDetected = this.incompatibleDetected || (probeAudioCodec && !compatibleAudio.includes(probeAudioCodec));
@@ -224,7 +209,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
224
209
  this.parsers = rbo.parsers;
225
210
 
226
211
  const session = await startRebroadcastSession(ffmpegInput, rbo);
227
- this.session = session;
212
+ this.parserSession = session;
228
213
 
229
214
  let watchdog: NodeJS.Timeout;
230
215
  const restartWatchdog = () => {
@@ -237,7 +222,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
237
222
  session.events.on('mp4-data', restartWatchdog);
238
223
 
239
224
  session.events.once('killed', () => {
240
- this.prebufferSession = undefined;
225
+ this.parserSessionPromise = undefined;
241
226
  session.events.removeListener('mp4-data', restartWatchdog);
242
227
  clearTimeout(watchdog);
243
228
  });
@@ -251,7 +236,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
251
236
  this.console.error('Detected audio codec was not AAC.', session.inputAudioCodec);
252
237
  // show an alert if no audio config was explicitly specified. Force the user to choose/experiment.
253
238
  if (!audioConfig) {
254
- log.a(`${this.name} is using ${session.inputAudioCodec} audio. Enable Reencode Audio in Rebroadcast Settings Audio Configuration to disable this alert.`);
239
+ log.a(`${this.mixin.name} is using ${session.inputAudioCodec} audio. Enable Reencode Audio in Rebroadcast Settings Audio Configuration to disable this alert.`);
255
240
  this.incompatibleDetected = true;
256
241
  this.allowImmediateRestart = true;
257
242
  // this will probably crash ffmpeg due to mp4/mpegts not being a valid container for pcm,
@@ -303,13 +288,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
303
288
  async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
304
289
  this.ensurePrebufferSession();
305
290
 
306
- const session = await this.prebufferSession;
307
-
308
- // if a specific stream is requested, and it's not what we're streaming, just fall through to source.
309
- if (options?.id !== undefined && options.id !== session.ffmpegInputs['mpegts'].mediaStreamOptions?.id) {
310
- this.console.log('rebroadcast session cant be used here', options);
311
- return this.mixinDevice.getVideoStream(options);
312
- }
291
+ const session = await this.parserSessionPromise;
313
292
 
314
293
  const sendKeyframe = this.storage.getItem(SEND_KEYFRAME) !== 'false';
315
294
  const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? Math.max(4000, (this.detectedIdrInterval || 4000)) * 1.5 : 0);
@@ -319,7 +298,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
319
298
  return mo;
320
299
  }
321
300
 
322
- this.console.log('prebuffer request started');
301
+ this.console.log('prebuffer request started', this.streamId);
323
302
 
324
303
  const createContainerServer = async (container: string) => {
325
304
  const eventName = container + '-data';
@@ -404,33 +383,166 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
404
383
  const mo = mediaManager.createFFmpegMediaObject(ffmpegInput);
405
384
  return mo;
406
385
  }
386
+ }
387
+
388
+ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements VideoCamera, Settings {
389
+ released = false;
390
+ sessions = new Map<string, PrebufferSession>();
391
+
392
+ constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
393
+ super(mixinDevice, mixinDeviceState, {
394
+ providerNativeId,
395
+ mixinDeviceInterfaces,
396
+ group: "Prebuffer Settings",
397
+ groupKey: "prebuffer",
398
+ });
399
+
400
+ this.delayStart();
401
+ }
402
+
403
+ delayStart() {
404
+ this.console.log('prebuffer sessions starting in 5 seconds');
405
+ // to prevent noisy startup/reload/shutdown, delay the prebuffer starting.
406
+ setTimeout(() => this.ensurePrebufferSessions(), 5000);
407
+ }
408
+
409
+ async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
410
+ await this.ensurePrebufferSessions();
411
+
412
+ let id = options?.id;
413
+ if (!id && !this.sessions.has(id)) {
414
+ const stream = await this.mixinDevice.getVideoStream(options);
415
+ const ffmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(stream, ScryptedMimeTypes.FFmpegInput)).toString()) as FFMpegInput;
416
+ id = ffmpegInput?.mediaStreamOptions?.id;
417
+ this.sessions.set(options?.id, this.sessions.get(id));
418
+ }
419
+ let session = this.sessions.get(id);
420
+ if (!session)
421
+ return this.mixinDevice.getVideoStream(options);
422
+ session.ensurePrebufferSession();
423
+ await session.parserSessionPromise;
424
+ session = this.sessions.get(id);
425
+ if (!session)
426
+ return this.mixinDevice.getVideoStream(options);
427
+ return session.getVideoStream(options);
428
+ }
429
+
430
+ async ensurePrebufferSessions() {
431
+ const msos = await this.mixinDevice.getVideoStreamOptions();
432
+ const enabled = this.getEnabledMediaStreamOptions(msos);
433
+ const ids = enabled ? enabled.map(mso => mso.id) : [undefined];
434
+ for (const id of ids) {
435
+ let session = this.sessions.get(id);
436
+ if (!session) {
437
+ const name = msos.find(mso => mso.id === id)?.name;
438
+ session = new PrebufferSession(this, name, id);
439
+ session.ensurePrebufferSession();
440
+ this.sessions.set(id, session);
441
+ }
442
+ }
443
+ deviceManager.onMixinEvent(this.id, this.mixinProviderNativeId, ScryptedInterface.Settings, undefined);
444
+ }
445
+
446
+ async getMixinSettings(): Promise<Setting[]> {
447
+ const settings: Setting[] = [];
448
+
449
+ const msos = await this.mixinDevice.getVideoStreamOptions();
450
+ const enabledStreams = this.getEnabledMediaStreamOptions(msos);
451
+ if (enabledStreams && msos?.length > 1) {
452
+ settings.push(
453
+ {
454
+ title: 'Prebuffered Streams',
455
+ description: 'The streams to prebuffer. Enable only as necessary to reduce traffic.',
456
+ key: 'enabledStreams',
457
+ value: enabledStreams.map(mso => mso.name || ''),
458
+ choices: msos.map(mso => mso.name),
459
+ multiple: true,
460
+ },
461
+ )
462
+ }
463
+
464
+ settings.push(
465
+ {
466
+ title: 'Prebuffer Duration',
467
+ description: 'Duration of the prebuffer in milliseconds.',
468
+ type: 'number',
469
+ key: PREBUFFER_DURATION_MS,
470
+ value: this.storage.getItem(PREBUFFER_DURATION_MS) || defaultPrebufferDuration.toString(),
471
+ },
472
+ {
473
+ title: 'Start at Previous Keyframe',
474
+ description: 'Start live streams from the previous key frame. Improves startup time.',
475
+ type: 'boolean',
476
+ key: SEND_KEYFRAME,
477
+ value: (this.storage.getItem(SEND_KEYFRAME) !== 'false').toString(),
478
+ },
479
+ );
480
+
481
+
482
+ for (const session of new Set([...this.sessions.values()])) {
483
+ settings.push(...await session.getMixinSettings());
484
+ }
485
+
486
+ return settings;
487
+ }
488
+
489
+ async putMixinSetting(key: string, value: string | number | boolean): Promise<void> {
490
+ const sessions = this.sessions;
491
+ this.sessions = new Map();
492
+ if (key === 'enabledStreams') {
493
+ this.storage.setItem(key, JSON.stringify(value));
494
+ }
495
+ else {
496
+ this.storage.setItem(key, value.toString());
497
+ }
498
+ for (const session of sessions.values()) {
499
+ session.parserSessionPromise?.then(session => session.kill());
500
+ }
501
+ this.ensurePrebufferSessions();
502
+ }
503
+
504
+ getEnabledMediaStreamOptions(msos?: MediaStreamOptions[]) {
505
+ if (!msos || !msos.length)
506
+ return;
407
507
 
408
- getEnabledMediaStreamOption(msos?: MediaStreamOptions[]) {
409
- if (msos?.length > 1) {
410
- const enabledStream = this.storage.getItem('enabledStream');
411
- return msos.find(mso => mso.name === enabledStream) || msos[0];
508
+ try {
509
+ const parsed: any[] = JSON.parse(this.storage.getItem('enabledStreams'));
510
+ const filtered = msos.filter(mso => parsed.includes(mso.name));
511
+ return filtered;
412
512
  }
513
+ catch (e) {
514
+ }
515
+ return [msos[0]];
413
516
  }
414
517
 
415
518
  async getVideoStreamOptions(): Promise<MediaStreamOptions[]> {
416
519
  const ret: MediaStreamOptions[] = await this.mixinDevice.getVideoStreamOptions() || [];
417
- let enabledStream = this.getEnabledMediaStreamOption(ret);
520
+ let enabledStreams = this.getEnabledMediaStreamOptions(ret);
521
+
522
+ const prebuffer = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
418
523
 
419
- if (!enabledStream) {
420
- enabledStream = {};
421
- ret.push(enabledStream);
524
+ if (!enabledStreams) {
525
+ ret.push({
526
+ prebuffer,
527
+ });
528
+ }
529
+ else {
530
+ for (const enabledStream of enabledStreams) {
531
+ enabledStream.prebuffer = prebuffer;
532
+ }
422
533
  }
423
- enabledStream.prebuffer = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
424
534
  return ret;
425
535
  }
426
536
 
427
537
  release() {
428
538
  this.console.log('prebuffer releasing if started');
429
539
  this.released = true;
430
- this.prebufferSession?.then(start => {
431
- this.console.log('prebuffer released');
432
- start.kill();
433
- });
540
+ for (const session of this.sessions.values()) {
541
+ session.parserSessionPromise?.then(session => {
542
+ this.console.log('prebuffer released');
543
+ session.kill();
544
+ });
545
+ }
434
546
  }
435
547
  }
436
548