@scrypted/prebuffer-mixin 0.1.40 → 0.1.44

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 +1 @@
1
- !function(e,t){for(var r in t)e[r]=t[r]}(window,function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},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 o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},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=5)}([function(e,t,r){"use strict";var o=r(6);try{o=Object.assign(o,{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)}e.exports=o,e.exports.default=o},function(e,t){e.exports=require("net")},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,o.once)(e,"listening"),e.address().port}catch(e){}}};var o=r(3)},function(e,t){e.exports=require("events")},function(e,t){e.exports=require("child_process")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o,n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=d();if(t&&t.has(e))return t.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var s=o?Object.getOwnPropertyDescriptor(e,n):null;s&&(s.get||s.set)?Object.defineProperty(r,n,s):r[n]=e[n]}r.default=e,t&&t.set(e,r);return r}(r(0)),s=r(1),i=r(2),a=(o=r(3))&&o.__esModule?o:{default:o},c=r(8),p=r(9),u=r(10);function d(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return d=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}=n.default;class f extends c.SettingsMixinDeviceBase{constructor(e,t,r,o){super(e,r,{providerNativeId:o,mixinDeviceInterfaces:t,group:"Rebroadcast and Prebuffer Settings",groupKey:"prebuffer"}),l(this,"prebufferMpegTs",[]),l(this,"prebufferFmp4",[]),l(this,"events",new a.default),l(this,"released",!1),l(this,"idrInterval",0),l(this,"prevIdr",0),console.log(this.name+" prebuffer session starting in 10 seconds"),setTimeout(()=>this.ensurePrebufferSession(),1e4)}async getMixinSettings(){const e=[];return e.push({title:"Prebuffer Duration",description:"Duration of the prebuffer in milliseconds.",type:"number",key:"prebufferDuration",value:this.storage.getItem("prebufferDuration")||15e3.toString()},{title:"Detected Keyframe Interval",description:"Currently detected keyframe interval. This value may vary based on the stream behavior.",readonly:!0,key:"detectedIdr",value:this.idrInterval.toString()},{title:"Start at Previous Keyframe",description:"Start live streams from the previous key frame. Improves startup time.",type:"boolean",key:"sendKeyframe",value:("true"===this.storage.getItem("sendKeyframe")).toString()},{title:"Reencode Audio",description:"Reencode the audio (necessary if camera outputs PCM).",type:"boolean",key:"reencodeAudio",value:("true"===this.storage.getItem("reencodeAudio")).toString()}),e}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(this.name+" prebuffer session started"),this.prebufferSession=this.startPrebufferSession())}async startPrebufferSession(){this.prebufferMpegTs=[];const e=parseInt(this.storage.getItem("prebufferDuration"))||15e3,t=JSON.parse((await m.convertMediaObjectToBuffer(await this.mixinDevice.getVideoStream(),n.ScryptedMimeTypes.FFmpegInput)).toString()),r="true"===this.storage.getItem("reencodeAudio")?[]:["-acodec","copy"],o=["-vcodec","copy"],a=async t=>{c.close();const r=(0,u.parseFragmentedMP4)(t);for await(const t of r){const r=Date.now();for(this.ftyp?this.moov?("mdat"===t.type&&(this.prevIdr&&(this.idrInterval=r-this.prevIdr),this.prevIdr=r),this.prebufferFmp4.push({atom:t,time:r})):this.moov=t:this.ftyp=t;this.prebufferFmp4.length&&this.prebufferFmp4[0].time<r-e;)this.prebufferFmp4.shift();this.events.emit("atom",t)}},c=(0,s.createServer)(e=>{a(e).catch(e=>console.log("fragmented mp4 session ended",e))}),d=await(0,i.listenZeroCluster)(c),l=["-f","mp4",...r,...o,"-movflags","frag_keyframe+empty_moov+default_base_moof","tcp://127.0.0.1:"+d],f=await(0,p.startRebroadcastSession)(t,{additionalOutputs:l,vcodec:o,acodec:r});return f.events.on("killed",()=>{c.close(),this.prebufferSession=void 0}),f.events.on("data",t=>{const r=Date.now();for(this.prebufferMpegTs.push({time:r,buffer:t});this.prebufferMpegTs.length&&this.prebufferMpegTs[0].time<r-e;)this.prebufferMpegTs.shift();this.events.emit("mpegts-data",t)}),f}async getVideoStream(e){this.ensurePrebufferSession();const t="true"===this.storage.getItem("sendKeyframe");if(!(null!=e&&e.prebuffer||t)){const e=await this.prebufferSession;return m.createFFmpegMediaObject(e.ffmpegInput)}const r=(null==e?void 0:e.prebuffer)||(t?(this.idrInterval||4e3)+1e3:0);console.log(this.name,"prebuffer request started");const o=new s.Server(t=>{o.close();const n=Date.now();let s;if("mp4"===(null==e?void 0:e.container)){const e=e=>{t.write(Buffer.concat([e.header,e.data]))};this.ftyp&&e(this.ftyp),this.moov&&e(this.moov);const o=Date.now();let n=!0;for(const t of this.prebufferFmp4)t.time<o-r||n&&"moof"!==t.atom.type||(n=!1,e(t.atom));this.events.on("atom",e),s=()=>{console.log(this.name,"prebuffer request ended"),this.events.removeListener("atom",e),this.events.removeListener("killed",s),t.removeAllListeners(),t.destroy()}}else{const e=e=>{t.write(e)};for(const t of this.prebufferMpegTs)t.time<n-r||e(t.buffer);this.events.on("mpegts-data",e),s=()=>{console.log(this.name,"prebuffer request ended"),this.events.removeListener("mpegts-data",e),this.events.removeListener("killed",s),t.removeAllListeners(),t.destroy()}}this.events.once("killed",s),t.once("end",s),t.once("close",s),t.once("error",s)});setTimeout(()=>o.close(),3e4);const n=await(0,i.listenZeroCluster)(o),a={inputArguments:["-f","mp4"===(null==e?void 0:e.container)?"mp4":"mpegts","-i","tcp://127.0.0.1:"+n]};console.log(this.name,"prebuffer ffmpeg input",a.inputArguments[3]);return m.createFFmpegMediaObject(a)}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=e[0];return t||(t={},e.push(t)),t.prebuffer=parseInt(this.storage.getItem("prebufferDuration"))||15e3,e}release(){var e;console.log(this.name,"prebuffer releasing if started"),this.released=!0,null===(e=this.prebufferSession)||void 0===e||e.then(e=>{console.log(this.name,"prebuffer released"),e.kill()})}}class g extends n.ScryptedDeviceBase{async canMixin(e,t){return t.includes(n.ScryptedInterface.VideoCamera)?[n.ScryptedInterface.VideoCamera,n.ScryptedInterface.Settings]:null}async getMixin(e,t,r){return new f(e,t,r,this.nativeId)}async releaseMixin(e,t){t.release()}}var h=new g;t.default=h},function(e,t,r){"use strict";const o=r(7);class n{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())}}class s{constructor(e,t,r,o){this.mixinDevice=e,this.mixinDevice=e,this.mixinDeviceInterfaces=t,this._deviceState=r,this.providerNativeId=o}get storage(){return this._storage||(this._storage=deviceManager.getMixinStorage(this.id,this.providerNativeId)),this._storage}_lazyLoadDeviceState(){}release(){}}!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(n.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(s.prototype,r,{set:t(r),get:e(r)})}();const i={ScryptedDeviceBase:n,MixinDeviceBase:s};Object.assign(i,o),e.exports=i,e.exports.default=i},function(e,t,r){"use strict";const o={};e.exports=o,e.exports.default=o,e.exports.ScryptedDeviceType={Builtin:"Builtin",Camera:"Camera",Fan:"Fan",Light:"Light",Switch:"Switch",Outlet:"Outlet",Sensor:"Sensor",Scene:"Scene",Program:"Program",Automation:"Automation",Vacuum:"Vacuum",Notifier:"Notifier",Thermostat:"Thermostat",Lock:"Lock",PasswordControl:"PasswordControl",Display:"Display",Speaker:"Speaker",Event:"Event",Entry:"Entry",Garage:"Garage",DeviceProvider:"DeviceProvider",DataSource:"DataSource",API:"API",Doorbell:"Doorbell",Irrigation:"Irrigation",Valve:"Valve",Unknown:"Unknown"},e.exports.TemperatureUnit={C:"C",F:"F"},e.exports.ThermostatMode={Off:"Off",Cool:"Cool",Heat:"Heat",HeatCool:"HeatCool",Auto:"Auto",FanOnly:"FanOnly",Purifier:"Purifier",Eco:"Eco",Dry:"Dry",On:"On"},e.exports.LockState={Locked:"Locked",Unlocked:"Unlocked",Jammed:"Jammed"},e.exports.MediaPlayerState={Idle:"Idle",Playing:"Playing",Paused:"Paused",Buffering:"Buffering"},e.exports.ScryptedInterface={ScryptedDevice:"ScryptedDevice",OnOff:"OnOff",Brightness:"Brightness",ColorSettingTemperature:"ColorSettingTemperature",ColorSettingRgb:"ColorSettingRgb",ColorSettingHsv:"ColorSettingHsv",Notifier:"Notifier",StartStop:"StartStop",Pause:"Pause",Dock:"Dock",TemperatureSetting:"TemperatureSetting",Thermometer:"Thermometer",HumiditySensor:"HumiditySensor",Camera:"Camera",VideoCamera:"VideoCamera",Intercom:"Intercom",Lock:"Lock",PasswordStore:"PasswordStore",Authenticator:"Authenticator",Scene:"Scene",Entry:"Entry",EntrySensor:"EntrySensor",DeviceProvider:"DeviceProvider",Battery:"Battery",Refresh:"Refresh",MediaPlayer:"MediaPlayer",Online:"Online",SoftwareUpdate:"SoftwareUpdate",BufferConverter:"BufferConverter",Settings:"Settings",BinarySensor:"BinarySensor",IntrusionSensor:"IntrusionSensor",PowerSensor:"PowerSensor",AudioSensor:"AudioSensor",MotionSensor:"MotionSensor",OccupancySensor:"OccupancySensor",FloodSensor:"FloodSensor",UltravioletSensor:"UltravioletSensor",LuminanceSensor:"LuminanceSensor",PositionSensor:"PositionSensor",MediaSource:"MediaSource",MessagingEndpoint:"MessagingEndpoint",OauthClient:"OauthClient",MixinProvider:"MixinProvider",HttpRequestHandler:"HttpRequestHandler",EngineIOHandler:"EngineIOHandler",PushHandler:"PushHandler",Program:"Program",Javascript:"Javascript"},e.exports.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"]},Javascript:{name:"Javascript",properties:[],methods:["eval"]}},e.exports.ScryptedInterfaceProperty={},Object.values(e.exports.ScryptedInterfaceDescriptors).map(e=>e.properties).flat().forEach(t=>e.exports.ScryptedInterfaceProperty[t]=t),e.exports.ScryptedMimeTypes={AcceptUrlParameter:"accept-url",Url:"text/x-uri",InsecureLocalUrl:"text/x-insecure-local-uri",LocalUrl:"text/x-local-uri",PushEndpoint:"text/x-push-endpoint",FFmpegInput:"x-scrypted/x-ffmpeg-input",RTCAVOffer:"x-scrypted/x-rtc-av-offer",RTCAVAnswer:"x-scrypted/x-rtc-av-answer"}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var o=r(0);class n extends o.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(o.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){const r=this.settingsGroupKey+":";return null!=e&&e.startsWith(r)?this.putMixinSetting(e.substring(r.length),t):this.mixinDevice.putSetting(e,t)}}t.SettingsMixinDeviceBase=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.startRebroadcastSession=async function(e,t){return new Promise(async r=>{let a,c=0,u=!0;const d=new i.EventEmitter;function l(){u&&d.emit("killed"),u=!1,null==y||y.kill(),null==h||h.close(),null==f||f.close()}function m(){t.timeout&&(clearTimeout(a),a=setTimeout(l,t.timeout))}m();const f=(0,o.createServer)(e=>{c++,console.log("rebroadcast client",c),clearTimeout(a);const t=t=>{e.write(t)},r=()=>{e.removeAllListeners(),d.removeListener("data",t),c--,0===c&&m(),e.destroy()};d.on("data",t),e.on("end",r),e.on("close",r),e.on("error",r)}),g=await(0,s.listenZeroCluster)(f),h=(0,o.createServer)(e=>{h.close(),(async()=>{let t=[],r=0;for(;;){const o=e.read();if(!o){await(0,i.once)(e,"readable");continue}if(t.push(o),r+=o.length,r<188)continue;const n=Buffer.concat(t),s=n.length%188,a=n.slice(0,n.length-s),c=n.slice(n.length-s);t=[c],r=c.length,d.emit("data",a)}})().catch(e=>{console.error("rebroadcast source ended",e),l()}),r({events:d,resetActivityTimer:m,isActive:()=>u,kill:l,server:f,cp:y,ffmpegInput:{inputArguments:["-f","mpegts","-i","tcp://127.0.0.1:"+g]}})}),v=await(0,s.listenZeroCluster)(h),S=e.inputArguments.slice();S.push(...t.additionalOutputs||[],"-f","mpegts",...t.vcodec||[],...t.acodec||[],"tcp://127.0.0.1:"+v),console.log(S);const y=n.default.spawn(await p.getFFmpegPath(),S,{stdio:"ignore"});y.on("exit",l)})};var o=r(1),n=c(r(4)),s=r(2),i=r(3),a=c(r(0));function c(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:p}=a.default},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseFragmentedMP4=u,t.startFFMPegFragmetedMP4Session=async function(e,t,r){return new Promise(async i=>{const a=(0,o.createServer)(e=>{a.close(),i({socket:e,cp:l,generator:u(e)})}),c=await(0,s.listenZeroCluster)(a),d=e.inputArguments.slice();d.push("-f","mp4"),d.push(...r),d.push(...t),d.push("-movflags","frag_keyframe+empty_moov+default_base_moof","tcp://127.0.0.1:"+c),console.log(d);const l=n.default.spawn(await p.getFFmpegPath(),d,{stdio:"ignore"})})};var o=r(1),n=c(r(4)),s=r(2),i=r(11),a=c(r(0));function c(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:p}=a.default;async function*u(e){for(;;){const t=await(0,i.readLength)(e,8),r=t.readInt32BE(0)-8,o=t.slice(4).toString(),n=await(0,i.readLength)(e,r);yield{header:t,length:r,type:o,data:n}}}},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,o)=>{const n=()=>{const o=e.read(t);o&&(i(),r(o))},s=()=>{i(),o(new Error(`stream ended during read for minimum ${t} bytes`))},i=()=>{e.removeListener("readable",n),e.removeListener("end",s)};e.on("readable",n),e.on("end",s)})}}]));
1
+ !function(e,t){for(var r in t)e[r]=t[r]}(window,function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},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 o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},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=6)}([function(e,t,r){"use strict";var o=r(7);try{o=Object.assign(o,{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)}e.exports=o,e.exports.default=o},function(e,t){e.exports=require("net")},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,o.once)(e,"listening"),e.address().port}catch(e){}}};var o=r(3)},function(e,t){e.exports=require("events")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=r(11);Object.keys(o).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===o[e]||Object.defineProperty(t,e,{enumerable:!0,get:function(){return o[e]}}))}))},function(e,t){e.exports=require("child_process")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o,n=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=l();if(t&&t.has(e))return t.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var i=o?Object.getOwnPropertyDescriptor(e,n):null;i&&(i.get||i.set)?Object.defineProperty(r,n,i):r[n]=e[n]}r.default=e,t&&t.set(e,r);return r}(r(0)),i=r(1),s=r(2),a=(o=r(3))&&o.__esModule?o:{default:o},c=r(9),d=r(10),u=r(4),p=r(12);function l(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return l=function(){return e},e}function m(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:f,log:g}=n.default;class h extends c.SettingsMixinDeviceBase{constructor(e,t,r,o){super(e,r,{providerNativeId:o,mixinDeviceInterfaces:t,group:"Rebroadcast and Prebuffer Settings",groupKey:"prebuffer"}),m(this,"prebufferMpegTs",[]),m(this,"prebufferFmp4",[]),m(this,"events",new a.default),m(this,"released",!1),m(this,"detectedIdrInterval",0),m(this,"detectedVcodec",""),m(this,"detectedAcodec",""),m(this,"prevIdr",0),console.log(this.name+" prebuffer session starting in 10 seconds"),setTimeout(()=>this.ensurePrebufferSession(),1e4)}async getMixinSettings(){const e=[];return e.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:("true"===this.storage.getItem("sendKeyframe")).toString()},{title:"Reencode Audio",description:"Reencode the audio (necessary if camera outputs PCM).",type:"boolean",key:"reencodeAudio",value:("true"===this.storage.getItem("reencodeAudio")).toString()},{group:"Media Information",title:"Detected Video Codec",readonly:!0,key:"detectedVcodec",value:this.detectedVcodec.toString()||"none"},{group:"Media Information",title:"Detected Audio Codec",readonly:!0,key:"detectedAcodec",value:this.detectedAcodec.toString()||"none"},{group:"Media Information",title:"Detected Keyframe Interval",description:"Currently detected keyframe interval. This value may vary based on the stream behavior.",readonly:!0,key:"detectedIdr",value:this.detectedIdrInterval.toString()||"none"}),e}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(this.name+" prebuffer session started"),this.prebufferSession=this.startPrebufferSession())}async startPrebufferSession(){this.prebufferMpegTs=[];const e=parseInt(this.storage.getItem("prebufferDuration"))||15e3,t=JSON.parse((await f.convertMediaObjectToBuffer(await this.mixinDevice.getVideoStream(),n.ScryptedMimeTypes.FFmpegInput)).toString()),r="true"===this.storage.getItem("reencodeAudio");let o;o=(await(0,u.probeVideoCamera)(this.mixinDevice)).noAudio?["-an"]:r?[]:["-acodec","copy"];const a=["-vcodec","copy"],c=async t=>{l.close();const r=(0,p.parseFragmentedMP4)(t);for await(const t of r){const r=Date.now();for(this.ftyp?this.moov?("mdat"===t.type&&(this.prevIdr&&(this.detectedIdrInterval=r-this.prevIdr),this.prevIdr=r),this.prebufferFmp4.push({atom:t,time:r})):this.moov=t:this.ftyp=t;this.prebufferFmp4.length&&this.prebufferFmp4[0].time<r-e;)this.prebufferFmp4.shift();this.events.emit("atom",t)}},l=(0,i.createServer)(e=>{c(e).catch(e=>console.log(this.name,"fragmented mp4 session ended",e))}),m=await(0,s.listenZeroCluster)(l),h=["-f","mp4",...o,...a,"-movflags","frag_keyframe+empty_moov+default_base_moof","tcp://127.0.0.1:"+m],v=await(0,d.startRebroadcastSession)(t,{additionalOutputs:h,vcodec:a,acodec:o});if(this.detectedAcodec=v.inputAudioCodec||"",this.detectedVcodec=v.inputVideoCodec||"",this.detectedAcodec){if("aac"!==this.detectedAcodec){var S;console.error(this.name,"Detected audio codec was not AAC."),-1===(null===(S=this.name)||void 0===S?void 0:S.indexOf("pcm"))||r||g.a(this.name+" is using PCM audio. You will need to enable Reencode Audio in Rebroadcast Settings for this stream.")}}else console.warn(this.name,"no audio detected.");return"h264"!==this.detectedVcodec&&console.error(this.name+" video codec is not h264. If there are errors, try changing your camera's encoder output."),v.events.on("killed",()=>{l.close(),this.prebufferSession=void 0}),v.events.on("data",t=>{const r=Date.now();for(this.prebufferMpegTs.push({time:r,buffer:t});this.prebufferMpegTs.length&&this.prebufferMpegTs[0].time<r-e;)this.prebufferMpegTs.shift();this.events.emit("mpegts-data",t)}),v}async getVideoStream(e){var t;this.ensurePrebufferSession();const r=await this.prebufferSession;if(null!=e&&e.id&&e.id!==(null===(t=r.ffmpegInput.mediaStreamOptions)||void 0===t?void 0:t.id))return console.log(this.name,"rebroadcast session cant be used here",e),this.mixinDevice.getVideoStream(e);const o="true"===this.storage.getItem("sendKeyframe");if(!(null!=e&&e.prebuffer||o)){return f.createFFmpegMediaObject(r.ffmpegInput)}console.log(this.name,"prebuffer request started");const n=new i.Server(t=>{n.close();const r=(null==e?void 0:e.prebuffer)||(o?1.5*Math.max(4e3,this.detectedIdrInterval||4e3):0),i=Date.now();let s;if("mp4"===(null==e?void 0:e.container)){const e=e=>{t.write(Buffer.concat([e.header,e.data]))};this.ftyp&&e(this.ftyp),this.moov&&e(this.moov);const o=Date.now();let n=!0;for(const t of this.prebufferFmp4)t.time<o-r||n&&"moof"!==t.atom.type||(n=!1,e(t.atom));this.events.on("atom",e),s=()=>{console.log(this.name,"prebuffer request ended"),this.events.removeListener("atom",e),this.events.removeListener("killed",s),t.removeAllListeners(),t.destroy()}}else{const e=e=>{t.write(e)};for(const t of this.prebufferMpegTs)t.time<i-r||e(t.buffer);this.events.on("mpegts-data",e),s=()=>{console.log(this.name,"prebuffer request ended"),this.events.removeListener("mpegts-data",e),this.events.removeListener("killed",s),t.removeAllListeners(),t.destroy()}}this.events.once("killed",s),t.once("end",s),t.once("close",s),t.once("error",s)});setTimeout(()=>n.close(),3e4);const a=await(0,s.listenZeroCluster)(n),c=r.ffmpegInput.mediaStreamOptions?Object.assign({},r.ffmpegInput.mediaStreamOptions):void 0;if(c&&c.audio){"true"===this.storage.getItem("reencodeAudio")&&(c.audio={codec:"aac"})}const d={inputArguments:["-f","mp4"===(null==e?void 0:e.container)?"mp4":"mpegts","-i","tcp://127.0.0.1:"+a],mediaStreamOptions:c};console.log(this.name,"prebuffer ffmpeg input",d.inputArguments[3]);return f.createFFmpegMediaObject(d)}async getVideoStreamOptions(){const e=await this.mixinDevice.getVideoStreamOptions()||[];let t=e[0];return t||(t={},e.push(t)),t.prebuffer=parseInt(this.storage.getItem("prebufferDuration"))||15e3,e}release(){var e;console.log(this.name,"prebuffer releasing if started"),this.released=!0,null===(e=this.prebufferSession)||void 0===e||e.then(e=>{console.log(this.name,"prebuffer released"),e.kill()})}}class v extends n.ScryptedDeviceBase{async canMixin(e,t){return t.includes(n.ScryptedInterface.VideoCamera)?[n.ScryptedInterface.VideoCamera,n.ScryptedInterface.Settings]:null}async getMixin(e,t,r){return new h(e,t,r,this.nativeId)}async releaseMixin(e,t){t.release()}}var S=new v;t.default=S},function(e,t,r){"use strict";const o=r(8);class n{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())}}class i{constructor(e,t,r,o){this.mixinDevice=e,this.mixinDevice=e,this.mixinDeviceInterfaces=t,this._deviceState=r,this.mixinProviderNativeId=o}get storage(){return this._storage||(this._storage=deviceManager.getMixinStorage(this.id,this.mixinProviderNativeId)),this._storage}_lazyLoadDeviceState(){}release(){}}!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(n.prototype,r,{set:t(r),get:e(r)}),Object.defineProperty(i.prototype,r,{set:t(r),get:e(r)})}();const s={ScryptedDeviceBase:n,MixinDeviceBase:i};Object.assign(s,o),e.exports=s,e.exports.default=s},function(e,t,r){"use strict";const o={};e.exports=o,e.exports.default=o,e.exports.ScryptedDeviceType={Builtin:"Builtin",Camera:"Camera",Fan:"Fan",Light:"Light",Switch:"Switch",Outlet:"Outlet",Sensor:"Sensor",Scene:"Scene",Program:"Program",Automation:"Automation",Vacuum:"Vacuum",Notifier:"Notifier",Thermostat:"Thermostat",Lock:"Lock",PasswordControl:"PasswordControl",Display:"Display",Speaker:"Speaker",Event:"Event",Entry:"Entry",Garage:"Garage",DeviceProvider:"DeviceProvider",DataSource:"DataSource",API:"API",Doorbell:"Doorbell",Irrigation:"Irrigation",Valve:"Valve",Unknown:"Unknown"},e.exports.TemperatureUnit={C:"C",F:"F"},e.exports.ThermostatMode={Off:"Off",Cool:"Cool",Heat:"Heat",HeatCool:"HeatCool",Auto:"Auto",FanOnly:"FanOnly",Purifier:"Purifier",Eco:"Eco",Dry:"Dry",On:"On"},e.exports.LockState={Locked:"Locked",Unlocked:"Unlocked",Jammed:"Jammed"},e.exports.MediaPlayerState={Idle:"Idle",Playing:"Playing",Paused:"Paused",Buffering:"Buffering"},e.exports.ScryptedInterface={ScryptedDevice:"ScryptedDevice",OnOff:"OnOff",Brightness:"Brightness",ColorSettingTemperature:"ColorSettingTemperature",ColorSettingRgb:"ColorSettingRgb",ColorSettingHsv:"ColorSettingHsv",Notifier:"Notifier",StartStop:"StartStop",Pause:"Pause",Dock:"Dock",TemperatureSetting:"TemperatureSetting",Thermometer:"Thermometer",HumiditySensor:"HumiditySensor",Camera:"Camera",VideoCamera:"VideoCamera",Intercom:"Intercom",Lock:"Lock",PasswordStore:"PasswordStore",Authenticator:"Authenticator",Scene:"Scene",Entry:"Entry",EntrySensor:"EntrySensor",DeviceProvider:"DeviceProvider",Battery:"Battery",Refresh:"Refresh",MediaPlayer:"MediaPlayer",Online:"Online",SoftwareUpdate:"SoftwareUpdate",BufferConverter:"BufferConverter",Settings:"Settings",BinarySensor:"BinarySensor",IntrusionSensor:"IntrusionSensor",PowerSensor:"PowerSensor",AudioSensor:"AudioSensor",MotionSensor:"MotionSensor",OccupancySensor:"OccupancySensor",FloodSensor:"FloodSensor",UltravioletSensor:"UltravioletSensor",LuminanceSensor:"LuminanceSensor",PositionSensor:"PositionSensor",MediaSource:"MediaSource",MessagingEndpoint:"MessagingEndpoint",OauthClient:"OauthClient",MixinProvider:"MixinProvider",HttpRequestHandler:"HttpRequestHandler",EngineIOHandler:"EngineIOHandler",PushHandler:"PushHandler",Program:"Program",Javascript:"Javascript"},e.exports.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"]},Javascript:{name:"Javascript",properties:[],methods:["eval"]}},e.exports.ScryptedInterfaceProperty={},Object.values(e.exports.ScryptedInterfaceDescriptors).map(e=>e.properties).flat().forEach(t=>e.exports.ScryptedInterfaceProperty[t]=t),e.exports.ScryptedMimeTypes={AcceptUrlParameter:"accept-url",Url:"text/x-uri",InsecureLocalUrl:"text/x-insecure-local-uri",LocalUrl:"text/x-local-uri",PushEndpoint:"text/x-push-endpoint",FFmpegInput:"x-scrypted/x-ffmpeg-input",RTCAVOffer:"x-scrypted/x-rtc-av-offer",RTCAVAnswer:"x-scrypted/x-rtc-av-answer"}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SettingsMixinDeviceBase=void 0;var o=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=n();if(t&&t.has(e))return t.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(Object.prototype.hasOwnProperty.call(e,i)){var s=o?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 n(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return n=function(){return e},e}const{deviceManager:i}=o.default;class s extends o.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(o.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 n=this.settingsGroupKey+":";if(null==e||!e.startsWith(n))return this.mixinDevice.putSetting(e,t);await this.putMixinSetting(e.substring(n.length),t),null===(r=i.onMixinEvent)||void 0===r||r.call(i,this.id,this.mixinProviderNativeId,o.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){return new Promise(async r=>{let a,d=0,l=!0;const g=new s.EventEmitter;let h,v,S;function y(){l&&g.emit("killed"),l=!1,null==w||w.kill(),null==O||O.close(),null==P||P.close()}function b(){t.timeout&&(clearTimeout(a),a=setTimeout(y,t.timeout))}b();const P=(0,o.createServer)(e=>{d++,console.log("rebroadcast client",d),clearTimeout(a);const t=t=>{e.write(t)},r=()=>{e.removeAllListeners(),g.removeListener("data",t),d--,0===d&&b(),e.destroy()};g.on("data",t),e.on("end",r),e.on("close",r),e.on("error",r)}),M=await(0,i.listenZeroCluster)(P),O=(0,o.createServer)(t=>{O.close(),(async()=>{let e=[],r=0;for(;;){const o=t.read();if(!o){await(0,s.once)(t,"readable");continue}if(e.push(o),r+=o.length,r<188)continue;const n=Buffer.concat(e);if(71!=n[0])throw new Error("Invalid sync byte in mpeg-ts packet. Terminating stream.");const i=n.length%188,a=n.slice(0,n.length-i),c=n.slice(n.length-i);e=[c],r=c.length,g.emit("data",a)}})().catch(e=>{console.error("rebroadcast source ended",e),y()}),r({inputAudioCodec:h,inputVideoCodec:v,inputVideoResolution:S,events:g,resetActivityTimer:b,isActive:()=>l,kill:y,server:P,cp:w,ffmpegInput:{inputArguments:["-f","mpegts","-i","tcp://127.0.0.1:"+M],mediaStreamOptions:e.mediaStreamOptions}})}),I=await(0,i.listenZeroCluster)(O),x=e.inputArguments.slice();x.push(...t.additionalOutputs||[],"-f",t.outputFormat||"mpegts",...t.vcodec||[],...t.acodec||[],"tcp://127.0.0.1:"+I),console.log(x);const w=n.default.spawn(await u.getFFmpegPath(),x,{});(0,c.ffmpegLogInitialOutput)(console,w),w.on("exit",y),f(w).then(e=>h=e),m(w).then(e=>v=e),p(w).then(e=>S=e)})};var o=r(1),n=d(r(5)),i=r(2),s=r(3),a=d(r(0)),c=r(4);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=o=>{const n=o.toString(),i=/(([0-9]{2,5})x([0-9]{2,5}))/.exec(n);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 o=n=>{const i=n.toString(),s=i.indexOf(t+": ");if(-1!==s){const n=i.substring(s+t.length+1).trim();let a=n.indexOf(" ");const c=n.indexOf(",");-1!==a&&c<a&&(a=c),-1!==a&&(e.stdout.removeListener("data",o),e.stderr.removeListener("data",o),r(n.substring(0,a)))}};e.stdout.on("data",o),e.stderr.on("data",o)})}async function m(e){return l(e,"Video")}async function f(e){return l(e,"Audio")}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t,r){var n,i;function s(e){const n=i=>{const s=i.toString();for(const e of o)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",n),void t.stderr.removeListener("data",n);e(s)};return n}null===(n=t.stdout)||void 0===n||n.on("data",s(e.log)),null===(i=t.stderr)||void 0===i||i.on("data",s(e.error))},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 o=["decode_slice_header error","no frame!","non-existing PPS"]},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseFragmentedMP4=p,t.startFFMPegFragmetedMP4Session=async function(e,t,r){return new Promise(async s=>{const a=(0,o.createServer)(e=>{a.close(),s({socket:e,cp:m,generator:p(e)})}),d=await(0,i.listenZeroCluster)(a),l=e.inputArguments.slice();l.push("-f","mp4"),l.push(...r),l.push(...t),l.push("-movflags","frag_keyframe+empty_moov+default_base_moof","tcp://127.0.0.1:"+d),console.log(l);const m=n.default.spawn(await u.getFFmpegPath(),l,{});(0,c.ffmpegLogInitialOutput)(console,m)})};var o=r(1),n=d(r(5)),i=r(2),s=r(13),a=d(r(0)),c=r(4);function d(e){return e&&e.__esModule?e:{default:e}}const{mediaManager:u}=a.default;async function*p(e){for(;;){const t=await(0,s.readLength)(e,8),r=t.readInt32BE(0)-8,o=t.slice(4).toString(),n=await(0,s.readLength)(e,r);yield{header:t,length:r,type:o,data:n}}}},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,o)=>{const n=()=>{const o=e.read(t);o&&(s(),r(o))},i=()=>{s(),o(new Error(`stream ended during read for minimum ${t} bytes`))},s=()=>{e.removeListener("readable",n),e.removeListener("end",i)};e.on("readable",n),e.on("end",i)})}}]));
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.40",
3
+ "version": "0.1.44",
4
4
  "description": "Rebroadcast and Prebuffer for VideoCameras.",
5
5
  "author": "Scrypted",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -1,14 +1,15 @@
1
1
 
2
- import { MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, VideoStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput } from '@scrypted/sdk';
2
+ import { MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput } from '@scrypted/sdk';
3
3
  import sdk from '@scrypted/sdk';
4
4
  import { createServer, Server, Socket } from 'net';
5
5
  import { listenZeroCluster } from '@scrypted/common/src/listen-cluster';
6
6
  import EventEmitter from 'events';
7
7
  import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
8
8
  import { FFMpegRebroadcastSession, startRebroadcastSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
9
+ import { probeVideoCamera } from '@scrypted/common/src/media-helpers';
9
10
  import { MP4Atom, parseFragmentedMP4 } from '@scrypted/common/src/ffmpeg-mp4-parser-session';
10
11
 
11
- const { mediaManager } = sdk;
12
+ const { mediaManager, log } = sdk;
12
13
 
13
14
  const defaultPrebufferDuration = 15000;
14
15
  const PREBUFFER_DURATION_MS = 'prebufferDuration';
@@ -35,7 +36,9 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
35
36
  released = false;
36
37
  ftyp: MP4Atom;
37
38
  moov: MP4Atom;
38
- idrInterval = 0;
39
+ detectedIdrInterval = 0;
40
+ detectedVcodec = '';
41
+ detectedAcodec = '';
39
42
  prevIdr = 0;
40
43
 
41
44
  constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
@@ -62,13 +65,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
62
65
  key: PREBUFFER_DURATION_MS,
63
66
  value: this.storage.getItem(PREBUFFER_DURATION_MS) || defaultPrebufferDuration.toString(),
64
67
  },
65
- {
66
- title: 'Detected Keyframe Interval',
67
- description: "Currently detected keyframe interval. This value may vary based on the stream behavior.",
68
- readonly: true,
69
- key: 'detectedIdr',
70
- value: this.idrInterval.toString(),
71
- },
72
68
  {
73
69
  title: 'Start at Previous Keyframe',
74
70
  description: 'Start live streams from the previous key frame. Improves startup time.',
@@ -82,7 +78,29 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
82
78
  type: 'boolean',
83
79
  key: REENCODE_AUDIO,
84
80
  value: (this.storage.getItem(REENCODE_AUDIO) === 'true').toString(),
85
- }
81
+ },
82
+ {
83
+ group: 'Media Information',
84
+ title: 'Detected Video Codec',
85
+ readonly: true,
86
+ key: 'detectedVcodec',
87
+ value: this.detectedVcodec.toString() || 'none',
88
+ },
89
+ {
90
+ group: 'Media Information',
91
+ title: 'Detected Audio Codec',
92
+ readonly: true,
93
+ key: 'detectedAcodec',
94
+ value: this.detectedAcodec.toString() || 'none',
95
+ },
96
+ {
97
+ group: 'Media Information',
98
+ title: 'Detected Keyframe Interval',
99
+ description: "Currently detected keyframe interval. This value may vary based on the stream behavior.",
100
+ readonly: true,
101
+ key: 'detectedIdr',
102
+ value: this.detectedIdrInterval.toString() || 'none',
103
+ },
86
104
  );
87
105
  return settings;
88
106
  }
@@ -106,10 +124,19 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
106
124
 
107
125
  const reencodeAudio = this.storage.getItem(REENCODE_AUDIO) === 'true';
108
126
 
109
- const acodec = reencodeAudio ? [] : [
110
- '-acodec',
111
- 'copy',
112
- ];
127
+ const probe = await probeVideoCamera(this.mixinDevice);
128
+
129
+ let acodec: string[];
130
+ // no audio? explicitly disable it.
131
+ if (probe.noAudio) {
132
+ acodec = ['-an'];
133
+ }
134
+ else {
135
+ acodec = reencodeAudio ? [] : [
136
+ '-acodec',
137
+ 'copy',
138
+ ];
139
+ }
113
140
 
114
141
  const vcodec = [
115
142
  '-vcodec',
@@ -130,7 +157,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
130
157
  else {
131
158
  if (atom.type === 'mdat') {
132
159
  if (this.prevIdr)
133
- this.idrInterval = now - this.prevIdr;
160
+ this.detectedIdrInterval = now - this.prevIdr;
134
161
  this.prevIdr = now;
135
162
  }
136
163
 
@@ -149,7 +176,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
149
176
  }
150
177
 
151
178
  const fmp4OutputServer = createServer(socket => {
152
- fragmentClientHandler(socket).catch(e => console.log('fragmented mp4 session ended', e));
179
+ fragmentClientHandler(socket).catch(e => console.log(this.name, 'fragmented mp4 session ended', e));
153
180
  });
154
181
  const fmp4Port = await listenZeroCluster(fmp4OutputServer);
155
182
 
@@ -166,6 +193,24 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
166
193
  vcodec,
167
194
  acodec,
168
195
  });
196
+
197
+ this.detectedAcodec = session.inputAudioCodec || '';
198
+ this.detectedVcodec = session.inputVideoCodec || '';
199
+
200
+ if (!this.detectedAcodec) {
201
+ console.warn(this.name, 'no audio detected.');
202
+ }
203
+ else if (this.detectedAcodec !== 'aac') {
204
+ console.error(this.name, 'Detected audio codec was not AAC.');
205
+ if (this.name?.indexOf('pcm') !== -1 && !reencodeAudio) {
206
+ log.a(`${this.name} is using PCM audio. You will need to enable Reencode Audio in Rebroadcast Settings for this stream.`);
207
+ }
208
+ }
209
+
210
+ if (this.detectedVcodec !== 'h264') {
211
+ console.error(`${this.name} video codec is not h264. If there are errors, try changing your camera's encoder output.`);
212
+ }
213
+
169
214
  session.events.on('killed', () => {
170
215
  fmp4OutputServer.close();
171
216
  this.prebufferSession = undefined;
@@ -186,23 +231,28 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
186
231
  return session;
187
232
  }
188
233
 
189
- async getVideoStream(options?: VideoStreamOptions): Promise<MediaObject> {
234
+ async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
190
235
  this.ensurePrebufferSession();
191
236
 
192
- const sendKeyframe = this.storage.getItem(SEND_KEYFRAME) === 'true';
237
+ const session = await this.prebufferSession;
193
238
 
239
+ // if a specific stream is requested, and it's not what we're streaming, just fall through to source.
240
+ if (options?.id && options.id !== session.ffmpegInput.mediaStreamOptions?.id) {
241
+ console.log(this.name, 'rebroadcast session cant be used here', options);
242
+ return this.mixinDevice.getVideoStream(options);
243
+ }
244
+
245
+ const sendKeyframe = this.storage.getItem(SEND_KEYFRAME) === 'true';
194
246
  if (!options?.prebuffer && !sendKeyframe) {
195
- const session = await this.prebufferSession;
196
247
  const mo = mediaManager.createFFmpegMediaObject(session.ffmpegInput);
197
248
  return mo;
198
249
  }
199
250
 
200
- const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? (this.idrInterval || 4000) + 1000 : 0);
201
-
202
251
  console.log(this.name, 'prebuffer request started');
203
252
 
204
253
  const server = new Server(socket => {
205
254
  server.close();
255
+ const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? Math.max(4000, (this.detectedIdrInterval || 4000)) * 1.5 : 0);
206
256
 
207
257
  const now = Date.now();
208
258
 
@@ -272,11 +322,24 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
272
322
 
273
323
  const port = await listenZeroCluster(server);
274
324
 
325
+ const mediaStreamOptions = session.ffmpegInput.mediaStreamOptions
326
+ ? Object.assign({}, session.ffmpegInput.mediaStreamOptions)
327
+ : undefined;
328
+
329
+ if (mediaStreamOptions && mediaStreamOptions.audio) {
330
+ const reencodeAudio = this.storage.getItem(REENCODE_AUDIO) === 'true';
331
+ if (reencodeAudio)
332
+ mediaStreamOptions.audio = {
333
+ codec: 'aac',
334
+ }
335
+ }
336
+
275
337
  const ffmpegInput: FFMpegInput = {
276
338
  inputArguments: [
277
339
  '-f', options?.container === 'mp4' ? 'mp4' : 'mpegts',
278
340
  '-i', `tcp://127.0.0.1:${port}`,
279
341
  ],
342
+ mediaStreamOptions,
280
343
  }
281
344
 
282
345
  console.log(this.name, 'prebuffer ffmpeg input', ffmpegInput.inputArguments[3]);
@@ -284,8 +347,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
284
347
  return mo;
285
348
  }
286
349
 
287
- async getVideoStreamOptions(): Promise<void | VideoStreamOptions[]> {
288
- const ret: VideoStreamOptions[] = await this.mixinDevice.getVideoStreamOptions() || [];
350
+ async getVideoStreamOptions(): Promise<void | MediaStreamOptions[]> {
351
+ const ret: MediaStreamOptions[] = await this.mixinDevice.getVideoStreamOptions() || [];
289
352
  let first = ret[0];
290
353
  if (!first) {
291
354
  first = {};