@scrypted/prebuffer-mixin 0.1.43 → 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=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){e.exports=require("child_process")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ffmpegLogInitialOutput=function(e,t){var r,o;function n(e){const r=o=>{if(-1!==o.toString().indexOf("frame="))return e("frames detected, discarding further input"),t.stdout.removeListener("data",r),void t.stderr.removeListener("data",r);e(o.toString())};return r}null===(r=t.stdout)||void 0===r||r.on("data",n(e.log)),null===(o=t.stderr)||void 0===o||o.on("data",n(e.error))}},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=p();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(11);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}=n.default;class g 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,"detectedIdrInterval",0),l(this,"detectedVcodec",""),l(this,"detectedAcodec",""),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:"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 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.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)}},c=(0,i.createServer)(e=>{a(e).catch(e=>console.log("fragmented mp4 session ended",e))}),p=await(0,s.listenZeroCluster)(c),l=["-f","mp4",...r,...o,"-movflags","frag_keyframe+empty_moov+default_base_moof","tcp://127.0.0.1:"+p],f=await(0,d.startRebroadcastSession)(t,{additionalOutputs:l,vcodec:o,acodec:r});return this.detectedAcodec=f.inputAudioCodec||"",this.detectedVcodec=f.inputVideoCodec||"","aac"!==this.detectedAcodec&&console.error("Detected audio codec was not AAC."),f.inputAudioCodec&&"h264"!==f.inputAudioCodec&&console.error(this.name+" video codec is not AAC. Enable Reencode Audio if there are errors."),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)}console.log(this.name,"prebuffer request started");const r=new i.Server(o=>{r.close();const n=(null==e?void 0:e.prebuffer)||(t?1.5*(this.detectedIdrInterval||4e3):0),i=Date.now();let s;if("mp4"===(null==e?void 0:e.container)){const e=e=>{o.write(Buffer.concat([e.header,e.data]))};this.ftyp&&e(this.ftyp),this.moov&&e(this.moov);const t=Date.now();let r=!0;for(const o of this.prebufferFmp4)o.time<t-n||r&&"moof"!==o.atom.type||(r=!1,e(o.atom));this.events.on("atom",e),s=()=>{console.log(this.name,"prebuffer request ended"),this.events.removeListener("atom",e),this.events.removeListener("killed",s),o.removeAllListeners(),o.destroy()}}else{const e=e=>{o.write(e)};for(const t of this.prebufferMpegTs)t.time<i-n||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),o.removeAllListeners(),o.destroy()}}this.events.once("killed",s),o.once("end",s),o.once("close",s),o.once("error",s)});setTimeout(()=>r.close(),3e4);const o=await(0,s.listenZeroCluster)(r),n={inputArguments:["-f","mp4"===(null==e?void 0:e.container)?"mp4":"mpegts","-i","tcp://127.0.0.1:"+o]};console.log(this.name,"prebuffer ffmpeg input",n.inputArguments[3]);return m.createFFmpegMediaObject(n)}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 h 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 g(e,t,r,this.nativeId)}async releaseMixin(e,t){t.release()}}var v=new h;t.default=v},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.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(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=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.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==I||I.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),I=(0,o.createServer)(e=>{I.close(),(async()=>{let t=[],r=0;for(;;){const o=e.read();if(!o){await(0,s.once)(e,"readable");continue}if(t.push(o),r+=o.length,r<188)continue;const n=Buffer.concat(t),i=n.length%188,a=n.slice(0,n.length-i),c=n.slice(n.length-i);t=[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]}})}),O=await(0,i.listenZeroCluster)(I),x=e.inputArguments.slice();x.push(...t.additionalOutputs||[],"-f","mpegts",...t.vcodec||[],...t.acodec||[],"tcp://127.0.0.1:"+O),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(4)),i=r(2),s=r(3),a=d(r(0)),c=r(5);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.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(4)),i=r(2),s=r(12),a=d(r(0)),c=r(5);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)})}}]));
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.43",
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,11 +1,12 @@
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
12
  const { mediaManager, log } = sdk;
@@ -123,10 +124,19 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
123
124
 
124
125
  const reencodeAudio = this.storage.getItem(REENCODE_AUDIO) === 'true';
125
126
 
126
- const acodec = reencodeAudio ? [] : [
127
- '-acodec',
128
- 'copy',
129
- ];
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
+ }
130
140
 
131
141
  const vcodec = [
132
142
  '-vcodec',
@@ -166,7 +176,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
166
176
  }
167
177
 
168
178
  const fmp4OutputServer = createServer(socket => {
169
- 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));
170
180
  });
171
181
  const fmp4Port = await listenZeroCluster(fmp4OutputServer);
172
182
 
@@ -187,12 +197,18 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
187
197
  this.detectedAcodec = session.inputAudioCodec || '';
188
198
  this.detectedVcodec = session.inputVideoCodec || '';
189
199
 
190
- if (this.detectedAcodec !== 'aac') {
191
- console.error('Detected audio codec was not AAC.')
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
+ }
192
208
  }
193
209
 
194
- if (session.inputAudioCodec && session.inputAudioCodec !== 'h264') {
195
- console.error(`${this.name} video codec is not AAC. Enable Reencode Audio if there are errors.`);
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.`);
196
212
  }
197
213
 
198
214
  session.events.on('killed', () => {
@@ -215,13 +231,19 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
215
231
  return session;
216
232
  }
217
233
 
218
- async getVideoStream(options?: VideoStreamOptions): Promise<MediaObject> {
234
+ async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
219
235
  this.ensurePrebufferSession();
220
236
 
221
- const sendKeyframe = this.storage.getItem(SEND_KEYFRAME) === 'true';
237
+ const session = await this.prebufferSession;
222
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';
223
246
  if (!options?.prebuffer && !sendKeyframe) {
224
- const session = await this.prebufferSession;
225
247
  const mo = mediaManager.createFFmpegMediaObject(session.ffmpegInput);
226
248
  return mo;
227
249
  }
@@ -230,7 +252,7 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
230
252
 
231
253
  const server = new Server(socket => {
232
254
  server.close();
233
- const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? (this.detectedIdrInterval || 4000) * 1.5 : 0);
255
+ const requestedPrebuffer = options?.prebuffer || (sendKeyframe ? Math.max(4000, (this.detectedIdrInterval || 4000)) * 1.5 : 0);
234
256
 
235
257
  const now = Date.now();
236
258
 
@@ -300,11 +322,24 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
300
322
 
301
323
  const port = await listenZeroCluster(server);
302
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
+
303
337
  const ffmpegInput: FFMpegInput = {
304
338
  inputArguments: [
305
339
  '-f', options?.container === 'mp4' ? 'mp4' : 'mpegts',
306
340
  '-i', `tcp://127.0.0.1:${port}`,
307
341
  ],
342
+ mediaStreamOptions,
308
343
  }
309
344
 
310
345
  console.log(this.name, 'prebuffer ffmpeg input', ffmpegInput.inputArguments[3]);
@@ -312,8 +347,8 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
312
347
  return mo;
313
348
  }
314
349
 
315
- async getVideoStreamOptions(): Promise<void | VideoStreamOptions[]> {
316
- const ret: VideoStreamOptions[] = await this.mixinDevice.getVideoStreamOptions() || [];
350
+ async getVideoStreamOptions(): Promise<void | MediaStreamOptions[]> {
351
+ const ret: MediaStreamOptions[] = await this.mixinDevice.getVideoStreamOptions() || [];
317
352
  let first = ret[0];
318
353
  if (!first) {
319
354
  first = {};