ONVIF Client protocol Profile S (Live Streaming) and Profile G (Replay) Node.js implementation.
This is a wrapper to ONVIF protocol which allows you to get information about your NVT (network video transmitter) device, its media sources, control PTZ (pan-tilt-zoom) movements and manage presets, detect devices in your network and control its events. It will also allow you to get information about your NVR (network video recorder) Profile G device and obtain a list of recordings.
The library uses NodeJS. And works on the server-side.
The library is tested on a test bed with 5 x Axis, 2 x Bosch, 1 x Canon, 2 x Hanwha, 4 x HikVision, 1 x Panasonic, 2 x Sony and 2 x unknown vendor cameras. There is a mix of PTZ and Fixed cameras and a mix of Pre-Profile, Profile S, Profile G and Profile T devices.
It is also tested with some Analogue Encoders from Avigilon, Axis, Bosch and HikVision including testing the RS485 output.
We welcome any donations or long term loans cameras from other vendors to test compatibility especially when testing the Media2 API, ONVIF Events and OSD.
npm install onvif
- install latest stable version
npm install agsh/onvif
- install latest version from GitHub
npm install agsh/onvif#v1
- install latest development version
git clone https://github.com/agsh/onvif.git
In the library directory run npm test
By default the tests use a mockup server to generate ONVIF replies.
To test with the real device, set appropriate environment variables HOSTNAME
, USERNAME
, PASSWORD
, PORT
and run
tests.
To build jsdoc for the library with default theme run npm run jsdoc
. Otherwise use jsdoc
with sources from
./lib/*.js
Special teasing example how to create little funny video server (http://localhost:6147) with 1 ffmpeg and 3 node.js libraries:
sudo apt install ffmpeg
npm install onvif socket.io rtsp-ffmpeg
const server = require('http').createServer((req, res) =>
res.end(`
<!DOCTYPE html><body>
<canvas width='640' height='480' />
<script src="https://github.com/agsh/onvif/raw/master/socket.io/socket.io.js"></script><script>
const socket = io(), ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
socket.on('data', (data) => {
const img = new Image;
const url = URL.createObjectURL(new Blob([new Uint8Array(data)], {type: 'application/octet-binary'}));
img.onload = () => {
URL.revokeObjectURL(url, {type: 'application/octet-binary'});
ctx.drawImage(img, 100, 100);
};
img.src = url;
});
</script></body></html>`));
const { Cam } = require('onvif/promises'), io = require('socket.io')(server), rtsp = require('rtsp-ffmpeg');
server.listen(6147);
const cam = new Cam({username: 'username', password: 'password', hostname: '192.168.0.116', port: 2020});
(async() => {
await cam.connect();
const input = (await cam.getStreamUri({protocol:'RTSP'})).uri.replace('://', `://${cam.username}:${cam.password}@`);
const stream = new rtsp.FFMpeg({input, resolution: '320x240', quality: 3});
io.on('connection', (socket) => {
const pipeStream = socket.emit.bind(socket, 'data');
stream.on('disconnect', () => stream.removeListener('data', pipeStream)).on('data', pipeStream);
});
setInterval(() => cam.absoluteMove({
x: Math.random() * 2 - 1,
y: Math.random() * 2 - 1,
zoom: Math.random()
}), 3000);
})().catch(console.error);
Short description of library possibilities is below.
Since 0.2.7 version library supports WS-Discovery of NVT devices. Currently it uses only Probe
SOAP method that just works well.
You can find devices in your subnetwork using probe
method of the Discovery singleton.
Discovery is an EventEmitter inheritor, so you can wait until discovery timeout, or subscribe on device
event.
You must subscribe to the error
event as a device on your network could reply with bad XML
Here some examples:
var onvif = require('onvif');
onvif.Discovery.on('device', function(cam){
// function will be called as soon as NVT responds
cam.username = <USERNAME>;
cam.password = <PASSWORD>;
cam.connect(console.log);
})
// Must have an error handler to catch bad replies from the network
onvif.Discovery.on('error', function (err,xml) {
// function called as soon as NVT responds, but this library could not parse the response
console.log('Discovery error ' + err);
});
onvif.Discovery.probe();
var onvif = require('onvif');
// Must have an error handler to catch bad replies from the network
onvif.Discovery.on('error', function (err,xml) {
console.log('Discovery error ' + err);
});
onvif.Discovery.probe(function(err, cams) {
// function will be called only after timeout (5 sec by default)
if (err) {
// There is a device on the network returning bad discovery data
// Probe results will be incomplete
throw err;
}
cams.forEach(function(cam) {
cam.username = <USERNAME>;
cam.password = <PASSWORD>;
cam.connect(console.log);
});
});
In all of that cases you've got disconnected cameras. To access each camera (and issue ONVIF commands) you normally need
the tuple username:password
. So, as shown in the examples, you can assign these properties and call connect
method to
get full functionality.
Options
timeout
, number. Time the probe method will wait NVT responses in msresolve
, boolean. If this argument is false, all discovered NVTs would be presented as data object instead of Cam instancedevice(cam, remoteInfo, responseXML)
fires on device discover. cam
is a Cam instance, remoteInfo is an object with network information
and responseXML is a body of SOAP responseerror(error)
fires on some UDP error or on bad SOAP response from NVTSince version 0.7.2 this library have a onvif/promises
namespace. It have promisified version of Cam constructor which returns an object with
the same methods as described below or in documentation but returns promises instead of callback function. Short example of common
usage is here:
const onvif = require('onvif/promises');
onvif.Discovery.on('device', async (cam) => {
// Set credentials to connect
cam.username = 'username';
cam.password = 'password';
await cam.connect();
cam.on('event', (event)=> console.log(JSON.stringify(event.message, null, '\t')));
cam.on('eventsError', console.error);
console.log(cam.username, cam.password);
console.log((await cam.getStreamUri({protocol:'RTSP'})).uri);
const date = await cam.getSystemDateAndTime();
console.log(date);
await cam.absoluteMove({
x: Math.random() * 2 - 1,
y: Math.random() * 2 - 1,
zoom: Math.random()
});
});
onvif.Discovery.on('error', console.error);
onvif.Discovery.probe();
const Cam = require('onvif').Cam;
Options are:
The library calls connect() automatically which executes the getSystemDateAndTime
, getCapabilities
and other methods.
Note on username and password:
getSystemDateAndTime
method.Callback (optional) executes when the cam is initialised. Single argument for this function is possible error.
When the cam object is created it automatically sends a getCapabilities
command to the ONVIF device. If the device is a camera or encoder (NVT) it sends two commands to the ONVIF device:
getVideoSources
and getProfiles
. It fills corresponding properties of an object:
After that it runs getActiveSources
method. It iterates over all video sources and tries to find out proper configuration
for profile and videosource. First matching profile becomes a member of defaultProfiles array and video source configuration
with ptz configuration becomes a member of activeSources array.
Configuration for the first or the only one video source becomes defaultProfile and activeSource properties. All methods without passing options object use it. You can change it manually at any time.
Connect to the camera and fill device information properties with getSystemDateAndTime
, getCapabilities
, getVideoSources
, getProfiles
methods
See more detailed information at http://www.onvif.org/ver10/media/wsdl/media.wsdl
After cam initialisation we can run several ONVIF commands.
There are several common methods that work without credentials. Here are they: getSystemDateAndTime
.
Returns a Date object with current camera datetime in the callback.
The ONVIF Standard says this would work without credentials (passed username
and password
arguments) so that the timeShift difference between the local clock and the NVT's onboard clock can be calculated for SOAP Authentication. However some devices claiming ONVIF support require a password and the library will re-try the connection if a username and password are available.
Device. Returns a device information, such as manufacturer, model and firmware version in the callback
Device. Returns in callback and assigns to #services
property an array consists of objects with properties: namespace
, XAddr
, version
Device. Returns in callback and assigns to #serviceCapabilities
property the capabilities of the device service (not media):
network, security and system. If your device supports some auxiliary capabilities they will be there too.
Media. Returns a URI that can be used to initiate a live media stream using RTSP as the control protocol The options are:
stream
(optional) - defines if a multicast or unicast stream is requested. Possible values are: 'RTP-Unicast' (default), 'RTP-Multicast'protocol
(optional) - defines the network protocol for streaming. Possible values are: 'UDP', 'TCP', 'RTSP' (default), 'HTTP'profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
Media. Obtain a JPEG snapshot URI from the device.
Returns the saved presets as an a key-value object where the key is the name of a preset and a value is a preset token.
This method also stores the presets information in a #presets
property of an object.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
PTZ. Operation to go to a saved preset position for the PTZ node in the selected profile.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
preset
- the name of preset. List of presets you can get by #getPresets
method or in #presets
property.PTZ. Operation to set the current position as a preset for the PTZ node in the selected profile. If presetToken
is passed as an option, then the preset for which that token is attached will be replaced. After success, you should re-fetch the presets with #getPresets
method.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
presetName
- the name to give to the preset. (optional) is this is a preset update.PTZ. Operation to remove a preset specified by the preset token. After success, you should re-fetch the presets with #getPresets
method.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
presetToken
- the preset token to use for preset removal (this will be the value
of a preset object found in #presets
after calling the #getPresets
method.PTZ. Operation to go to the saved home
position for the PTZ node in the selected profile. If no home
position has been saved, the ONVIF camera will do nothing.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
speed
An object with properties
x
Pan speed, float within 0 to 1y
Tilt speed, float within 0 to 1zoom
Zoom speed, float within 0 to 1If the speed option is omitted, the default speed set by the PTZConfiguration will be used.
PTZ. Operation to set the current position as the home
position for the PTZ node in the selected profile.
The options are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
PTZ. Returns the properties of the current PTZ node, if it exists.
Use this function to get maximum number of presets, ranges of admitted values for x, y, zoom, iris, focus.
Sets all information into #nodes
property.
PTZ. This is a relative pan-tilt-zoom method. Options for this method is a delta between desired and current position of the camera.
The options are:
x
Pan, number or a string within -1 to 1, optionaly
Tilt, number or a string within -1 to 1, optionalzoom
Zoom, number or a string within 0 to 1, optionalspeed
An object with properties
x
Pan speed, float within 0 to 1y
Tilt speed, float within 0 to 1zoom
Zoom speed, float within 0 to 1If the speed option is omitted, the default speed set by the PTZConfiguration will be used.
Callback is optional and means essentially nothing
PTZ. This is an absolute pan-tilt-zoom method. Options for this method is an absolute position of the camera.
The options are:
x
Pan, number or a string within -1 to 1, optionaly
Tilt, number or a string within -1 to 1, optionalzoom
Zoom, number or a string within 0 to 1, optionalspeed
An object with properties
x
Pan speed, float within 0 to 1y
Tilt speed, float within 0 to 1zoom
Zoom speed, float within 0 to 1If the speed option is omitted, the default speed set by the PTZConfiguration will be used.
Callback is optional and means essentially nothing
PTZ. Operation for continuous Pan/Tilt and Zoom movements
The options are:
x
Pan velocity, number or a string within -1 to 1, optionaly
Tilt velocity, number or a string within -1 to 1, optionalzoom
Zoom velocity, number or a string within -1 to 1, optionaltimeout
Timeout in milliseconds, number. If timeout is omitted, movement will continue until stop
commandPTZ. Stop ongoing pan, tilt and zoom movements of absolute, relative and continuous type
Options and callback are optional. The options properties are:
profileToken
(optional) - defines media profile to use and will define the configuration of the content of the stream. Default is #activeSource.profileToken
panTilt
(optional) - set true when we want to stop ongoing pan and tilt movements. If panTilt
arguments are not present, this command stops these movements.zoom
(optional) - set true when we want to stop ongoing zoom movement. If zoom
arguments are not present, this command stops ongoing zoom movement.PTZ. Returns an object with the current PTZ values.
{
position: {
x: 'pan position',
y: 'tilt position',
zoom: 'zoom'
}
, moveStatus: {} // camera moving
, utcTime: 'current camera datetime'
}
PTZ. Get all the existing PTZConfigurations from the device. Configurations saved into #configurations
property
PTZ. Get supported coordinate systems including their range limitations for selected configuration. Extends corresponding configuration object
Recordings. Get all the recordings tracks available on the device. Note: Only Onvif Profile G devices provide this features.
Recordings. Get the replay stream or streams (if using a NVR) - usually RTSP - for the provided recording token/s.
Recordings. Get the information of a recording token. Needed in order to match a recordingToken with a sourceToken. Used with both GetRecordings and GetReplayUri will allow to retreive recordings from an Onvif Profile G device. Note: not all devices are 100% Onvif G compliant.
gotoHomePosition
, ptz.setHomePosition
. Fixed exceptions in ptz.getConfigurations
and utils.parseSOAPString
. Added tests for ptz.setPreset
, ptz.removePreset
, ptz.gotoHomePosition
, and ptz.setHomePosition
.preserveAddress
property for NAT devices, discovery with multiple network interfaces (@Climax777)WSDL schemes and docs: