@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.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +87 -24
package/dist/main.nodejs.js
CHANGED
|
@@ -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
package/src/main.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
|
|
2
|
-
import { MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, MediaObject, VideoCamera,
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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.
|
|
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?:
|
|
234
|
+
async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
|
|
190
235
|
this.ensurePrebufferSession();
|
|
191
236
|
|
|
192
|
-
const
|
|
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 |
|
|
288
|
-
const ret:
|
|
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 = {};
|