@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.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +221 -109
package/dist/main.nodejs.js
CHANGED
|
@@ -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
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 =
|
|
13
|
+
const defaultPrebufferDuration = 10000;
|
|
14
14
|
const PREBUFFER_DURATION_MS = 'prebufferDuration';
|
|
15
15
|
const SEND_KEYFRAME = 'sendKeyframe';
|
|
16
|
-
const
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
mixinDeviceInterfaces,
|
|
50
|
-
group: "Rebroadcast and Prebuffer Settings",
|
|
51
|
-
groupKey: "prebuffer",
|
|
52
|
-
});
|
|
52
|
+
mixinDevice: VideoCamera;
|
|
53
|
+
console: Console;
|
|
54
|
+
storage: Storage;
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
107
|
-
|
|
121
|
+
key: 'detectedResolution',
|
|
122
|
+
group,
|
|
123
|
+
title: 'Detected Resolution and Bitrate',
|
|
108
124
|
readonly: true,
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
return
|
|
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
|
|
520
|
+
let enabledStreams = this.getEnabledMediaStreamOptions(ret);
|
|
521
|
+
|
|
522
|
+
const prebuffer = parseInt(this.storage.getItem(PREBUFFER_DURATION_MS)) || defaultPrebufferDuration;
|
|
418
523
|
|
|
419
|
-
if (!
|
|
420
|
-
|
|
421
|
-
|
|
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.
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|