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
JavaScript
"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