CVH-Camera
The CVH-Camera project provides a way to share one or multiple camera views using the Janus WebRTC gateway. It is designed to be used with the PULT project available at https://gitlab.cvh-server.de/pgerwinski/pult.
Note that the project and its documentation are still in development.
In this documentation clients that publish a feed will be referred to as senders and clients who receive a feed will be referred to as receivers or viewers.
Sender Web Page
CVH-Camera provides a web page with which users can transmit camera feeds.
Query Parameters
The behavior of the sender page can be controlled using its query parameters.
Providing query parameters can be done by appending a question mark to the url, followed by key-value pairs which are divided by and symbols.
Example: http://www.mywebpage.com/somePage.html?param1=value1¶m2=value2
. In the example the parameters param1
and param2
are provided
with the values value1
and value2
.
The following list explains the usage of the parameters:
-
room
: The number of the Janus room. Defaults to1000
. -
slot
: The camera slot used in the room. Defaults to0
. -
token
: The token required for authentication on the camera server (see below). Defaults to an empty string, which will yield to the user not being able to transmit his camera feed. -
pin
optional: The pin for the Janus room. If the Janus room has no pin, provide the valuenone
. If this parameter is not provided, an input field for the pin is shown. -
customNameAllowed
optional: If this parameter is present (even when holding no value), an input field for a custom name is shown. If a value is provided for this field, it will be used as initial value of the input field. The user can also update his name after starting a transmission. The names are escaped on the server to prevent Cross-Site-Scripting (XSS) attacks.
Camera Server
CVH-Camera uses a Nodejs server with the socket.io library to send events regarding the camera feeds out to the receivers in realtime. The socket connection is also used to verify feeds that are published in the Janus rooms and only send those to the receivers. This way no unwanted feeds can be shown to the receivers while still having a public password for the Janus room itself.
One instance of the camera server is meant to manage one Janus room. This is done by creating a new Janus room on startup and destroying it on shutdown of the server.
The room is managed by defining slots for cameras which are all disabled by default. To activate a slot, one has to provide a token for that slot. This token will be required to be able to send a feed on that slot. As mentioned above, only feeds that are verified in that way are shown to the receivers. One can also refresh the token for a given slot or simply deactivate it.
Compiling and Running the Server
The camera server is written in typescript, a superset of javascript. That means that is has to be compiled before being able to start.
To compile the code, install all dependencies, including the typescript compiler.
This can be done by running npm install
in the camera-server
folder.
Then run npm run build
in the same folder. This will compile the files into a newly created folder dist
.
Once the code is compiled, the server can be started with node server.js
in the dist
folder.
Config
The camera server can read a path to a config file from the CONFIG_PATH
environment variable.
This config file has to be in the json format and should have this structure.
Below is a description of the config file's properties:
-
port
: The port on which the server will listen. Defaults to5000
. -
cameraSlots
: The camera slots available for this room. Defaults to4
. -
notifyPath
: A path to a file which the camera server will append messages to. This is used to notify the controller (usually PULT). The path can either be absolute or relative to the config file but must not start with a tilde.If this property is emitted or an empty string is provided, the notify feature will not be used.
-
janusURL
: The url of the janus server. Defaults tohttp://localhost:8088/janus
. Note that by default/janus
has to be appended to the url. -
janusRoom
: The janus room which will be used. Make sure that the room is unique and not used for anything else, as the server will destroy it on startup to create a new room. Defaults to1000
. -
janusRoomSecret
: The secret used by the janus room. This is required to make severe request regarding the room like destroying it. Make sure that this is a long arbitrary string. Defaults todefault
. -
janusRoomPin
: The pin required to join the room as a viewer or sender. Defaults to no pin (empty string). -
janusBitrate
: The default bitrate with which a camera feed is transmitted by Janus. Defaults to128000
.
Stdin-Interface
The camera server is controlled by PULT via its stdin. One could also implement the interface in any other program to manage the CVH-Camera.
For the syntax of the commands the following convention is used in this documentation:
-
<param>
is a parameter that is required. -
<a | b>
is a parameter that is required and has to hold either the valuea
orb
. -
[param]
is a parameter that is optional
This is the list of the available commands:
Slot Control Commands
Command | Description |
---|---|
activate_slot |
Activates a slot and sets its token. To set a new token use refresh_token . Usage: activate_slot <slot> <token> [annotation] See set_annotation for an explanation of the annotation. |
refresh_token |
Sets a new token for a slot. Usage: refresh_token <slot> <new_token>
|
deactivate_slot |
Deactivates a slot. Also ensures that the feed is removed for the receivers. Usage: deactivate_slot <slot>
|
set_annotation |
Sets the annotation of a slot. A simple use case would be displaying the user's name below his feed. Usage: set_annotation <slot> <annotation> annotation can be any HTML snippet which will annotate the camera feed of the slot. This is done by appending the snippet to the container which contains the video element of the feed. If this example snippet is provided, the text Hello noVNC will be displayed at the bottom of the feed container: <div style="box-sizing: border-box; position: absolute; bottom: 0; width: 100%; background: rgba(0, 0, 0, 0.75); color: white; text-align: center; padding: 4px;">Hello noVNC</div> . Important: The HTML snippet has to have only one parent element. The container uses the CSS declaration position: fixed . Thus, working with position: absolute is possible and definitely advised. |
remove_annotation |
Removes the annotation of a slot. Usage: remove_annotation <slot>
|
set_bitrate_limit |
Sets the bitrate limit for the camera feed transmission of the provided slot. This can be useful to save traffic when the camera feed is only displayed relatively small on the receiver side. Setting a limit of 0 removes the limit. Note that the initial controller bitrate will be equal to the one mentioned in the config file. When deactivating a slot, the controller bitrate is set to its initial value. Usage: set_bitrate_limit <slot> <bitrate>
|
Camera Control Commands
These commands are designed to work well with the PULT project and thus with noVNC, a VNC web client.
The current state of the camera feeds is saved in the server's memory and will be applied to new viewers.
Command | Description |
---|---|
set_geometry_relative_to_canvas |
Sets the geometry of the feed on the slot relative to the noVNC canvas. Note that the pixel values should be provided relative to those of the transmitted VNC feed, not those of the canvas or window. This might change the absolute size of the camera feed depending on the screen size of the viewer. Usage: set_geometry_relative_to_canvas <slot> <l | r><t | b> <x_offset> <y_offset> <width> <height> [z-index] . The positioning is handled like the CSS position attribute. In case you are not familiar with it: l stand for left, r for right, t for top and b for bottom. The x and y offset is then relative to the provided sides. For example the position rt 21 42 will position the camera feed with a 21px space to the right side and a 42px space to the top side of the canvas. The z-index defines how the camera feeds are layered, when they overlap. To make sure no undefined behavior occurs, one should make sure that every feed has a different z-index when overlapping the feeds. |
set_geometry_relative_to_window |
Sets the geometry of the feed on the slot relative to the window of the viewer. Note that this can cause unwanted behavior. The feeds might look positioned well on your screen, but poorly positioned on a screen with a different size. set_geometry_relative_to_canvas should be used in most cases, because its pixel values are relative to the size of the transmitted VNC feed. Usage: set_geometry_relative_to_window <slot> <l | r><t | b> <x_offset> <y_offset> <width> <height> [z-index] . The parameters work the same way as described in the description of set_geometry_relative_to_canvas . |
hide |
Hides the feed of the provided slot. Note that the feed will still be transmitted to the viewer but is just hidden. By doing that, the feed can be shown again with a very low latency. Usage: hide <slot>
|
show |
Shows the feed of the provided slot in case it was hidden. Usage: show <slot>
|
Notify-Interface
The camera server can notify its controller (usually PULT) by writing to a file which is provided in the config. The controller can then read the file and process the messages.
In the case of PULT this file is a named pipe (mkfifo), which works perfectly fine.
This is a list of all sent messages. Note that a newline character \n
is appended to every message.
Message | Description |
---|---|
new_feed <slot> |
Sent after a sender on a slot has started transmitting a feed. |
remove_feed <slot> |
Sent after a sender on a slot has stopped transmitting a feed or the slot is deactivated (which also removes the feed). |
custom_name <slot> <custom_name> |
Sent after a sender on a slot has started transmitting a feed and has set a custom name. The name is a string which is guaranteed to be escaped to prevent Cross-Site-Scripting (XSS) attacks. Note that the name can contain spaces but will never be an empty string. The controller should wrap the name into a HTML snippet and send it back to the camera server using the set_annotation command. |
Socket Traffic
This section describes the socket traffic and the socket.io events that are used.
Sender
When the sender side expects an answer to a request, a callback can be passed to the emit function. The server can then take that function as a parameter of the handler and call it with the response. Below you can see an abstract example of the described technique.
// Client
socket.emit('sender_init', 'Some data', function(responseData) {
console.log(responseData); // Should log 'Some answer'
});
// Server
socket.on('sender_init', function(data, fn) {
console.log(data); // Should log 'Some data'
fn('Some answer');
});
Server responses to request will always include the following fields:
-
success
: A boolean that indicates, whether the request was successful or not. -
message
: A string that holds a user-friendly text to display. Holds the message of the error if one occurred.
In order to authenticate itself, the sender has to provide a slot and a token by emitting a sender_init
event. These values are provided through the query string of the sender web page. When the server receives the sender_init
event it validates the slot and the token and sends a response.
After a successful initialisation the connectin to Janus is established. When the camera is shared, the id of the transmitted Janus feed is determined and then sent to the server using the set_feed_id
event. On the server the corresponding slot will save that feed id. This will then be used to tell all receivers which feed id to attach to.
When the sender socket disconnects, an event will be emitted, notifying the receivers to remove the corresponding feed.