@survilla/public-video-player
v1.0.3
Published
This is public javascript ESM library to play video (liveview or recordings) of Survilla.
Downloads
283
Maintainers
Readme
Public video player
This is public javascript ESM library to play video (liveview or recordings) of Survilla.
- Typescript is supported
Requirements
- To play video you need to have account in some survilla resseler (eg. Netrex https://www.netrex.cz/ or Camstreamer https://camstreamer.com/)
- Create an API token and use it for authorization
- It uses Web codec (https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API) and canvas to render the video. See minimal browser supported versions (https://caniuse.com/webcodecs).
Installation
There are two ways
Add library through script tag
Copy the file dist/survilla-player.js to your server and add to you html. Than you can use it as:
<script src="./survilla-player.js" type="module"></script>
<script type="module">
import { Liveview } from './survilla-player.js';
const liveview = new Liveview({
...
});
</script>Note: dont forget to add type="module" because the library is built as ES module.
Install via package manager
Use nodejs package manager (eg. npm or yarn)
yarn add @survilla/public-video-playerDocumentation
Note: If you have multiple players on the page its recomended to use Looper (to have one webworker that is running all players at once)
Liveview
- Liveview player
- Its automatically restarted if the stream crashes / ends
- Video is started by default
- Audio is turned off by default (browser cant play audio without user action)
- Can be stopped and resumed
- Connects with a websocket url + alias (temporary token), so no API token is needed in the browser - you provide a
getLiveViewAliasWscallback that returns them - The browser opens only the websocket, which is not restricted by CORS (unlike the API request)
- Dont forget to call destroy() method to stop all timmers
how to get the websocket url and the alias (APIv3)
Ask the public API (v3) for the rtspwss live view of the camera. Do it on your own server, so the API token stays there, and send only the result to the browser. See api management how to create the token.
curl 'https://<your portal>/api/graphql.php' \
-X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <apiToken>' \
--data-raw '{"query":"query { camera(node_id: 269974) { live_view(stream_profile_id: 209546) { remote { rtspwss { alias_name ws { url } } } } } }"}'{
"data": {
"camera": {
"live_view": {
"remote": {
"rtspwss": {
"alias_name": "0678ebeb...",
"ws": {
"url": "wss://nvhs11.survilla.net:443/sms1/wss"
}
}
}
}
}
}
}ws.url is the wsUrl and alias_name is the aliasName.
The alias is valid only for a few seconds, so getLiveViewAliasWs is called again for every (re)connect - always return a freshly requested alias, dont cache it.
constructor
{
canvas: HTMLCanvasElement;
getLiveViewAliasWs: (size: { width: number; height: number }) => Promise<{
wsUrl: string;
aliasName: string;
isLocal?: boolean;
}>;
clbs?: {
onConnected?: () => void;
onError?: (e: unknown) => void;
onConnectionFailed?: () => void;
onSmartBufferLoad?: () => void;
};
looper?: Looper;
}getLiveViewAliasWsreceives the current canvas size (eg. to pick a stream profile) and returns thewsUrl+aliasName(see above). It is called on every (re)connect.clbs.onErroris called when the stream cant be started; it receives aTooManyViewersErrorwhen the camera has too many viewers, so you can show a proper message.
methods
you can controll it via livePlayback
const liveview = new Liveview({ ... });
liveview.livePlayback.start(); // starts the liveview
liveview.livePlayback.stop(); // stops the liveview
liveview.livePlayback.setMute(true); // controll audio
liveview.livePlayback.setPaintMode('cover') // change paint mode: 'cover' | 'contain' | 'fill'
liveview.livePlayback.state; // you can read the player state: 'IDLE' | 'LOADING' | 'CONNECTING' | 'CONNECTED' | 'PAUSE' | 'RESTARTING' | 'DESTROYED'
liveview.destroy(); // stops liveview and destroy itNote: dont forget to call destroy() method to stop all timmers
example
LocalLiveview
- Liveview player that connects to the camera via ip address (port)
- It requires to have on camera EdgeRecording Acap installed
- automatically tries to reconnect if it fails
- Note: every LocalLiveview is opening new connection to the camera, if you open too many streams to one camera (multiple users will open it separetly) => you can overload the camera. If you use Liveview, there is only one connection to the camera and the streams are multiplied on our cloud.
constructor
{
canvas: HTMLCanvasElement;
camera: {
ip: string;
user: string;
pass: string;
rtspParam: string;
reduceLatency?: boolean;
};
clbs?: {
onConnected?: () => void;
onError?: (e: unknown) => void;
onConnectionFailed?: () => void;
onSmartBufferLoad?: () => void;
};
looper?: Looper;
}camera.ipis ip address of the camera (can include port eg.0.0.0.0:5000)camera.user,camera.passare username and password to the cameracamera.rtspParamare parameters of the stream (eg.camera=1&resolution=640x480&fps=30)reduceLatencydecreases latency from 300ms to 150ms
methods
same as in Liveview
example
Recording
- Recording player
- you are passing div as container to the constructor, where canvases will be created
- dont forget about css (see below)
constructor
{
container: HTMLDivElement;
connect: {
containerId: number;
apiToken: string;
dns?: string;
};
clbs?: {
onNoMoreRecording?: () => void;
onTimeChange?: (time: NrTime) => void;
};
range: {
start: NrTime;
end: NrTime;
};
looper?: Looper;
}methods
some of the setting methods are exposed: but there is a lot of more, just investigate methods of RecordingPlayerStore
const recording = new Recording({ ... });
recording.seek(time: NrTime); // seek to time (cant seek outside of defined range passed in constructor)
recording.play()
recording.pause();
recording.getTime(); // retuns current time as NrTime
recording.setMute(value: boolean); // mute and unmute audio
recording.downloadSnapshot(); // download current frame
recording.setPlaybackSpeed(speed: number); // change play speed
// look to recording.store. for more
recording.destroy(); // stops recording and destroys itcss
if you want to have video scaled to container, you have to add these css, where #container is the container you are passing to the Recording constructor
#container canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
opacity: 1;
transition-property: opacity;
transition-timing-function: linear;
transition-duration: 200ms;
}
#container canvas:not(.active) {
opacity: 0;
}example
Utils
NrTime
class used for time
const time = NrTime.fromS(1748604899); // absolute time in s
time.s // get seconds
time.ms // get miliseconds
time.us // get microsecondsLooper
- registers a webworker that is moving the video forward
- use this if you have multiple players on the page
constructor
takes number, how often should be triggered in miliseconds
const looper = new Looper(5);
for(let i = 0; i < 4; i++){
new Liveview({ looper, .... });
}Note: Dont forget to destroy it
Examples
Simple liveview
<html>
<head>
<script src="./survilla-player.js" type="module"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module">
import { Liveview } from './survilla-player.js';
/**
* Returns the websocket url and the alias (temporary token) of the stream.
*
* In production do this on your own server, so the API token never reaches the browser.
* Ask https://<your portal>/api/graphql.php with the `Authorization: Bearer <apiToken>`
* header for the rtspwss live view of the camera and return its ws url + alias name:
*
* query {
* camera(node_id: 269974) {
* live_view(stream_profile_id: 209546) {
* remote { rtspwss { alias_name ws { url } } }
* }
* }
* }
*
* const res = await fetch('/api/liveview-alias');
* const { alias_name, ws } = await res.json();
* return { wsUrl: ws.url, aliasName: alias_name };
*
* The alias is valid only for a few seconds. Liveview calls this again for every
* (re)connect, so always return a freshly requested alias. The browser opens just
* the websocket, which is not restricted by CORS.
*/
const getLiveViewAliasWs = async (size) => {
const res = await fetch('/api/liveview-alias');
const { alias_name, ws } = await res.json();
return { wsUrl: ws.url, aliasName: alias_name };
};
const liveview = new Liveview({
canvas: document.getElementById('canvas'),
getLiveViewAliasWs,
clbs: {
onConnected: () => console.log('playing'),
onError: (e) => console.log('cant start the stream', e),
},
});
// liveview.destroy(); // dont forget to destroy it when done
</script>
</body>
</html>Multiple liveviews
<html>
<head>
<script src="./survilla-player.js" type="module"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
display: flex;
flex-wrap: wrap;
}
canvas {
width: 50%;
height: 50%;
box-sizing: border-box;
}
</style>
</head>
<body>
<canvas id="canvas0"></canvas>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
<canvas id="canvas3"></canvas>
<script type="module">
import { Liveview, Looper } from './survilla-player.js';
const looper = new Looper();
// fetch a fresh ws url + alias for the camera from your own server
const getLiveViewAliasWs = async (size) => {
const res = await fetch('/api/liveview-alias');
const { alias_name, ws } = await res.json();
return { wsUrl: ws.url, aliasName: alias_name };
};
for (let i = 0; i < 4; i++) {
const canvas = document.getElementById(`canvas${i}`);
const liveview = new Liveview({
canvas,
getLiveViewAliasWs,
looper,
});
}
</script>
</body>
</html>Recording
<html>
<head>
<script src="./survilla-player.js" type="module"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
display: flex;
flex-wrap: wrap;
}
#container {
width: 100%;
height: 100%;
box-sizing: border-box;
}
#container canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
opacity: 1;
transition-property: opacity;
transition-timing-function: linear;
transition-duration: 200ms;
}
#container canvas:not(.active) {
opacity: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="module">
import { Recording, NrTime } from './survilla-player.js';
const recording = new Recording({
container: document.getElementById('container'),
connect: {
containerId: 92203,
apiToken: 'token',
},
range: {
start: NrTime.fromS(1748598757),
end: NrTime.fromS(1748599000),
},
});
recording.play();
</script>
</body>
</html>