@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.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +51 -16
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=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
package/src/main.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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
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
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
191
|
-
console.
|
|
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 (
|
|
195
|
-
console.error(`${this.name} video codec is not
|
|
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?:
|
|
234
|
+
async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
|
|
219
235
|
this.ensurePrebufferSession();
|
|
220
236
|
|
|
221
|
-
const
|
|
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 |
|
|
316
|
-
const ret:
|
|
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 = {};
|