UNPKG

onvif-nvt-ts

Version:

Wrapper for ONVIF spec to control NVT (Network Video Transitter) devices. Forked and added TypeScript support from onvif-nvt.

555 lines 23.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const url_parse_1 = __importDefault(require("url-parse")); const Util = __importStar(require("./utils/util")); const MODULE_MAP = { analytics: './modules/analytics', core: './modules/core', events: './modules/events', media: './modules/media', media2: './modules/media2', ptz: './modules/ptz' }; const MODULE_MAP_AFTER = { core: function () { this.core.init(this.serviceAddress, this.username, this.password); }, media: function () { this.media.init(this.timeDiff, this.serviceAddress, this.username, this.password); } // snapshot: function (this: Camera) { // const defaultProfile = this.getDefaultProfile() // if (defaultProfile) { // const snapshotUri = defaultProfile.SnapshotUri.Uri // this.snapshot.init(snapshotUri, this.username, this.password) // } // } }; /** * Wrapper class for all onvif modules to manage an Onvif device (camera). */ class Camera { constructor() { this.rootPath = null; this.serviceAddress = null; this.timeDiff = 0; this.address = null; this.port = null; this.username = null; this.password = null; this.deviceInformation = null; this.profileList = []; this.defaultProfile = null; } /** * Add a module to Camera. The available modules are: * <ul> * <li>analytics - automatically added based on capabilities</li> * <li>core - automatically added</li> * <li>events - automatically added based on capabilities</li> * <li>media - automatically added based on capabilities</li> * <li>media2</li> * <li>ptz - automatically added based on capabilities</li> * </ul> * @param {string} name The name of the module. */ async add(name) { const mod = MODULE_MAP[name]; if (!MODULE_MAP[name]) { throw new Error(`Module '${name}' does not exist. Cannot add to Camera.`); } if (this[name]) { return; } const Inst = await Promise.resolve().then(() => __importStar(require(mod))); const after = MODULE_MAP_AFTER[name] || (() => { }); this[name] = new Inst(); after.call(this); } /** * Connect to the specified camera. * @param address The camera's address * @param port Optional port (80 used if this is null) * @param username The username for the account on the camera. This is optional if your camera does not require a username. * @param password The password for the account on the camera. This is optional if your camera does not require a password. * @param servicePath The service path for the camera. If null or 'undefined' the default path according to the ONVIF spec will be used. */ connect(address, port, username, password, servicePath) { return new Promise((resolve, reject) => { // check for valid address let errMsg = ''; if ((errMsg = Util.isInvalidValue(address, 'string'))) { reject(new Error('The "address" argument for connect is invalid: ' + errMsg)); return; } // provide defaults if not provided port = port || 80; username = username || null; password = password || null; servicePath = servicePath || '/onvif/device_service'; this.address = address; this.port = port; this.setAuth(username, password); // set up the service address let serviceAddress = 'http://' + address; if (port && port !== 80) { serviceAddress = serviceAddress + ':' + port; } this.rootPath = serviceAddress; serviceAddress = serviceAddress + servicePath; this.serviceAddress = new url_parse_1.default(serviceAddress); // add core module this.add('core'); return this.coreGetSystemDateAndTime() .then(() => this.coreGetServices()) .then(() => this.coreGetCapabilities()) .then(() => this.coreGetDeviceInformation()) .then(() => this.mediaGetProfiles()) .then(() => this.mediaGetStreamURI()) .then(() => this.mediaGetSnapshotUri()) .then(() => this.coreGetScopes()) .then(() => resolve(this.getInformation())) .catch(error => { reject(error); }); }); } /** * Change or remove authentication. * @param username The username for the account on the camera. This is optional if your camera does not require a username. * @param password The password for the account on the camera. This is optional if your camera does not require a password. */ setAuth(username, password) { this.username = username ?? null; this.password = password ?? null; } /** * Returns the ONVIF device's informaton. Available only after connection. */ getInformation() { const info = this.deviceInformation; if (info) { return JSON.parse(JSON.stringify(info)); } return null; } /** * Returns the default profile that will be used when one is not supplied to functions that require it. Available after connection. */ getDefaultProfile() { return this.defaultProfile; } coreGetSystemDateAndTime() { return new Promise((resolve, reject) => { this.core .getSystemDateAndTime() .then(results => { this.timeDiff = this.core.getTimeDiff(); resolve(); }) .catch(error => { console.error(error); reject(error); }); }); } coreGetServices() { return new Promise((resolve, reject) => { this.core .getServices(true) .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in core.getServices')); } const response = results.data.GetServicesResponse; const services = response.Service; // the appropriate modules will be automatically added // to camera based on the onvif device's services. // if GetServics is not supported, the GetCapabilities // fallback will be used. services.forEach(service => { this.checkForProxy(service); const namespace = service.Namespace; if (namespace === 'http://www.onvif.org/ver10/device/wsdl') { this.core.version = service.Version; } else if (namespace === 'http://www.onvif.org/ver10/media/wsdl') { this.add('media'); if (this.media) { this.media.init(this.timeDiff, new url_parse_1.default(service.XAddr), this.username, this.password); this.media.version = service.Version; } } else if (namespace === 'http://www.onvif.org/ver10/events/wsdl') { this.add('events'); if (this.events) { this.events.init(this.timeDiff, new url_parse_1.default(service.XAddr), this.username, this.password); this.events.version = service.Version; } } else if (namespace === 'http://www.onvif.org/ver20/ptz/wsdl') { this.add('ptz'); if (this.ptz) { this.ptz.init(this.timeDiff, new url_parse_1.default(service.XAddr), this.username, this.password); this.ptz.version = service.Version; } } else if (namespace === 'http://www.onvif.org/ver20/analytics/wsdl') { this.add('analytics'); if (this.analytics) { this.analytics.init(this.timeDiff, new url_parse_1.default(service.XAddr), this.username, this.password); this.analytics.version = service.Version; } } }); resolve(); }) .catch(error => { console.error(error); // don't fail because this isn't supported by the camera // spec says to use fallback of GetCapabilities method. resolve(); }); }); } // make sure the serviceAddress matches // if not, then we may be behind a proxy and it needs // do be dealt with checkForProxy(service) { const xaddrPath = new url_parse_1.default(service.XAddr); if (xaddrPath.hostname === this.serviceAddress.hostname) { // no proxy return; } // build new path service.XAddr = this.rootPath + xaddrPath.pathname; } coreGetCapabilities() { return new Promise((resolve, reject) => { this.core .getCapabilities() .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in core.getCapabilities')); } const c = results.data.GetCapabilitiesResponse.Capabilities; if (!c) { reject(new Error('Failed to initialize the device: No capabilities were found.')); return; } // the appropriate modules will be automatically added // to camera based on the onvif device's capabilities. if ('Analytics' in c) { const analytics = c.Analytics; this.checkForProxy(analytics); if (analytics && 'XAddr' in analytics) { if (!this.analytics) { this.add('analytics'); if (this.analytics) { const serviceAddress = new url_parse_1.default(analytics.XAddr); this.analytics.init(this.timeDiff, serviceAddress, this.username, this.password); } } if (this.analytics) { if ('RuleSupport' in analytics && analytics.RuleSupport === 'true') { this.analytics.ruleSupport = true; } if ('AnalyticsModuleSupport' in analytics && analytics.AnalyticsModuleSupport === 'true') { this.analytics.analyticsModuleSupport = true; } } } } if ('Events' in c) { const events = c.Events; this.checkForProxy(events); if (events && 'XAddr' in events) { if (!this.events) { this.add('events'); if (this.events) { const serviceAddress = new url_parse_1.default(events.XAddr); this.events.init(this.timeDiff, serviceAddress, this.username, this.password); } } if (this.events && this.analytics) { if ('WSPullPointSupport' in events && events.WSPullPointSupport === 'true') { this.analytics.wsPullPointSupport = true; } if ('WSSubscriptionPolicySupport' in events && events.WSSubscriptionPolicySupport === 'true') { this.analytics.wsSubscriptionPolicySupport = true; } } } } if ('Media' in c) { const media = c.Media; this.checkForProxy(media); if (media && 'XAddr' in media) { if (!this.media) { this.add('media'); if (this.media) { const serviceAddress = new url_parse_1.default(media.XAddr); this.media.init(this.timeDiff, serviceAddress, this.username, this.password); } } } } if ('PTZ' in c) { const ptz = c.PTZ; this.checkForProxy(ptz); if (ptz && 'XAddr' in ptz) { if (!this.ptz) { this.add('ptz'); if (this.ptz) { const serviceAddress = new url_parse_1.default(ptz.XAddr); this.ptz.init(this.timeDiff, serviceAddress, this.username, this.password); } } } } resolve(); }) .catch(error => { console.error(error); reject(error); }); }); } coreGetDeviceInformation() { return new Promise((resolve, reject) => { this.core .getDeviceInformation() .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in core.getDeviceInformation')); } this.deviceInformation = results.data.GetDeviceInformationResponse; resolve(); }) .catch(error => { console.error(error); reject(error); }); }); } coreGetScopes() { return new Promise((resolve, reject) => { this.core .getScopes() .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in core.getScopes')); } const scopes = typeof results.data.GetScopesResponse.Scopes === 'undefined' || !Array.isArray(results.data.GetScopesResponse.Scopes) ? [] : results.data.GetScopesResponse.Scopes; this.deviceInformation.Ptz = false; scopes.forEach(scope => { const s = scope.ScopeItem; if (s.indexOf('onvif://www.onvif.org/hardware/') === 0) { const hardware = s.split('/').pop(); this.deviceInformation.Hardware = hardware; } else if (s.indexOf('onvif://www.onvif.org/type/Streaming') === 0) { this.deviceInformation.Streaming = true; } else if (s.indexOf('onvif://www.onvif.org/type/video_encoder') === 0) { this.deviceInformation.VideoEncoder = true; } else if (s.indexOf('onvif://www.onvif.org/type/audio_encoder') === 0) { this.deviceInformation.AudiooEncoder = true; } else if (s.indexOf('onvif://www.onvif.org/type/ptz') === 0) { this.deviceInformation.Ptz = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/S') === 0) { this.deviceInformation.ProfileS = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/C') === 0) { this.deviceInformation.ProfileC = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/G') === 0) { this.deviceInformation.ProfileG = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/Q') === 0) { this.deviceInformation.ProfileQ = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/A') === 0) { this.deviceInformation.ProfileA = true; } else if (s.indexOf('onvif://www.onvif.org/Profile/T') === 0) { this.deviceInformation.ProfileT = true; } else if (s.indexOf('onvif://www.onvif.org/location/country/') === 0) { const country = s.split('/').pop(); this.deviceInformation.Country = country; } else if (s.indexOf('onvif://www.onvif.org/location/city/') === 0) { const city = s.split('/').pop(); this.deviceInformation.City = city; } else if (s.indexOf('onvif://www.onvif.org/name/') === 0) { let name = s.split('/').pop(); name = name.replace(/_/g, ' '); this.deviceInformation.Name = name; } }); resolve(); }) .catch(error => { console.error(error); reject(error); }); }); } mediaGetProfiles() { return new Promise((resolve, reject) => { this.media .getProfiles() .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in media.getProfiles')); } const profiles = results.data.GetProfilesResponse.Profiles; if (!profiles) { reject(new Error('Failed to initialize the device: The targeted device does not any media profiles.')); return; } const profileList = this.parseProfiles(profiles); this.profileList = this.profileList.concat(profileList); resolve(); }) .catch(error => { console.error(error); reject(error); }); }); } parseProfiles(profiles) { const profileList = []; // When a single profile is given 'profiles' is the single profile if (!Array.isArray(profiles)) { profiles = [profiles]; } profiles.forEach(profile => { profileList.push(profile); if (!this.defaultProfile) { this.defaultProfile = profile; if (this.ptz) { this.ptz.setDefaultProfileToken(profile.$.token); } } }); return profileList; } /** * Returns an array of profiles. Available after connection. * The profiles will contain media stream URIs and snapshot URIs for each profile. */ getProfiles() { return this.profileList; } mediaGetStreamURI() { return new Promise((resolve, reject) => { const protocols = ['UDP', 'HTTP', 'RTSP']; let profileIndex = 0; let protocolIndex = 0; const getStreamUri = () => { const profile = this.profileList[profileIndex]; if (profile) { const protocol = protocols[protocolIndex]; if (protocol) { const token = profile.$.token; this.media .getStreamUri('RTP-Unicast', protocol, token) .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in media.getStreamUri')); } profile.StreamUri = results.data.GetStreamUriResponse.MediaUri; ++protocolIndex; getStreamUri(); }) .catch(error => { console.error(error); ++protocolIndex; getStreamUri(); }); } else { ++profileIndex; protocolIndex = 0; getStreamUri(); } } else { resolve(); } }; getStreamUri(); }); } mediaGetSnapshotUri() { return new Promise((resolve, reject) => { let profileIndex = 0; const getSnapshotUri = () => { const profile = this.profileList[profileIndex]; if (profile) { // this.media.getSnapshotUri(profile['token']) this.media .getSnapshotUri(profile.$.token) .then(results => { if (!('data' in results)) { return reject(new Error('No "data" field in media.getSnapshotUri')); } try { const service = { XAddr: results.data.GetSnapshotUriResponse.MediaUri.Uri }; this.checkForProxy(service); profile.SnapshotUri = results.data.GetSnapshotUriResponse.MediaUri; profile.SnapshotUri.Uri = service.XAddr; } catch (e) { } ++profileIndex; getSnapshotUri(); }) .catch(error => { console.error(error); ++profileIndex; getSnapshotUri(); }); } else { resolve(); } }; getSnapshotUri(); }); } } exports.default = Camera; //# sourceMappingURL=camera.js.map