2021-04-01 15:10:43 -04:00
|
|
|
|
# Moonfire NVR API <!-- omit in toc -->
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2018-03-24 23:51:30 -04:00
|
|
|
|
Status: **current**.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
* [Summary](#summary)
|
|
|
|
|
* [Endpoints](#endpoints)
|
2022-12-23 15:43:00 -05:00
|
|
|
|
* [Authentication](#authentication)
|
|
|
|
|
* [`POST /api/login`](#post-apilogin)
|
|
|
|
|
* [`POST /api/logout`](#post-apilogout)
|
2021-04-01 15:10:43 -04:00
|
|
|
|
* [`GET /api/`](#get-api)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/`](#get-apicamerasuuid)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/recordings`](#get-apicamerasuuidstreamrecordings)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/view.mp4`](#get-apicamerasuuidstreamviewmp4)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/view.mp4.txt`](#get-apicamerasuuidstreamviewmp4txt)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/view.m4s`](#get-apicamerasuuidstreamviewm4s)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/view.m4s.txt`](#get-apicamerasuuidstreamviewm4stxt)
|
|
|
|
|
* [`GET /api/cameras/<uuid>/<stream>/live.m4s`](#get-apicamerasuuidstreamlivem4s)
|
|
|
|
|
* [`GET /api/init/<id>.mp4`](#get-apiinitidmp4)
|
|
|
|
|
* [`GET /api/init/<id>.mp4.txt`](#get-apiinitidmp4txt)
|
|
|
|
|
* [`GET /api/signals`](#get-apisignals)
|
|
|
|
|
* [`POST /api/signals`](#post-apisignals)
|
|
|
|
|
* [Request 1](#request-1)
|
|
|
|
|
* [Request 2](#request-2)
|
|
|
|
|
* [Request 3](#request-3)
|
2022-12-24 13:09:05 -05:00
|
|
|
|
* [User management](#user-management)
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* [`GET /api/users/`](#get-apiusers)
|
2023-01-08 04:14:03 -05:00
|
|
|
|
* [`POST /api/users/`](#post-apiusers)
|
2022-12-24 13:09:05 -05:00
|
|
|
|
* [`GET /api/users/<id>`](#get-apiusersid)
|
2023-01-08 04:14:03 -05:00
|
|
|
|
* [`PATCH /api/users/<id>`](#patch-apiusersid)
|
2022-12-24 15:21:06 -05:00
|
|
|
|
* [`DELETE /api/users/<id>`](#delete-apiusersid)
|
2022-12-31 12:08:26 -05:00
|
|
|
|
* [Types](#types)
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* [UserSubset](#usersubset)
|
2022-12-31 12:08:26 -05:00
|
|
|
|
* [Permissions](#permissions)
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* [Cross-site request forgery (CSRF) protection](#cross-site-request-forgery-csrf-protection)
|
2021-04-01 15:10:43 -04:00
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
## Summary
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
A JavaScript-based web interface to list cameras and view recordings.
|
|
|
|
|
Supports external analytics.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
|
|
|
|
In the future, this is likely to be expanded:
|
|
|
|
|
|
|
|
|
|
* configuration support
|
|
|
|
|
* commandline tool over a UNIX-domain socket
|
|
|
|
|
(at least for bootstrapping web authentication)
|
|
|
|
|
|
2020-08-05 00:44:01 -04:00
|
|
|
|
*Note:* italicized terms in this document are defined in the [glossary](glossary.md).
|
|
|
|
|
|
2021-11-23 16:01:20 -05:00
|
|
|
|
Currently the API is considered an internal contract between the server and the
|
|
|
|
|
UI which are bundled together. Thus, breaking changes in the API may happen in
|
|
|
|
|
any release of Moonfire NVR, even a "minor" or "patch" release. From version
|
|
|
|
|
0.7.0 onward, API changes should be described in the
|
|
|
|
|
[changelog](../CHANGELOG.md).
|
|
|
|
|
|
|
|
|
|
Future work may introduce versioning to improve compatibility with externally
|
|
|
|
|
developed tools.
|
|
|
|
|
|
2018-11-26 00:31:50 -05:00
|
|
|
|
All requests for JSON data should be sent with the header
|
|
|
|
|
`Accept: application/json` (exactly).
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
## Endpoints
|
|
|
|
|
|
2022-12-23 15:43:00 -05:00
|
|
|
|
### Authentication
|
|
|
|
|
|
|
|
|
|
#### `POST /api/login`
|
2018-11-26 00:31:50 -05:00
|
|
|
|
|
2021-09-01 18:01:42 -04:00
|
|
|
|
The request should have an `application/json` body containing a JSON object with
|
2020-01-09 02:23:58 -05:00
|
|
|
|
`username` and `password` keys.
|
2018-11-26 00:31:50 -05:00
|
|
|
|
|
|
|
|
|
On successful authentication, the server will return an HTTP 204 (no content)
|
2021-03-31 13:44:08 -04:00
|
|
|
|
with a `Set-Cookie` header for the `s` cookie, which is an opaque, `HttpOnly`
|
2018-11-26 00:31:50 -05:00
|
|
|
|
(unavailable to Javascript) session identifier.
|
|
|
|
|
|
|
|
|
|
If authentication or authorization fails, the server will return a HTTP 403
|
|
|
|
|
(forbidden) response. Currently the body will be a `text/plain` error message;
|
|
|
|
|
future versions will likely be more sophisticated.
|
|
|
|
|
|
2022-12-23 15:43:00 -05:00
|
|
|
|
#### `POST /api/logout`
|
2018-11-26 00:31:50 -05:00
|
|
|
|
|
2020-01-09 02:23:58 -05:00
|
|
|
|
The request should have an `application/json` body containing
|
2019-06-07 13:19:38 -04:00
|
|
|
|
a `csrf` parameter copied from the `session.csrf` of the
|
2018-11-26 00:31:50 -05:00
|
|
|
|
top-level API request.
|
|
|
|
|
|
|
|
|
|
On success, returns an HTTP 204 (no content) responses. On failure, returns a
|
|
|
|
|
4xx response with `text/plain` error message.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/`
|
2017-10-22 00:54:27 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns basic information about the server, including all cameras. Valid
|
|
|
|
|
request parameters:
|
2017-10-22 00:54:27 -04:00
|
|
|
|
|
|
|
|
|
* `days`: a boolean indicating if the days parameter described below
|
|
|
|
|
should be included.
|
2020-06-22 03:38:23 -04:00
|
|
|
|
* `cameraConfigs`: a boolean indicating if the `camera.config` and
|
|
|
|
|
`camera.stream[].config` parameters described below should be included.
|
2023-01-05 13:11:28 -05:00
|
|
|
|
This requires the `readCameraConfigs` permission.
|
2017-10-22 00:54:27 -04:00
|
|
|
|
|
2019-06-19 18:17:50 -04:00
|
|
|
|
Example request URI (with added whitespace between parameters):
|
2017-10-22 00:54:27 -04:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/api/?days=true
|
2019-06-19 18:17:50 -04:00
|
|
|
|
&cameraConfigs=true
|
2017-10-22 00:54:27 -04:00
|
|
|
|
```
|
|
|
|
|
|
2021-09-01 18:01:42 -04:00
|
|
|
|
The `application/json` response will have a JSON object as follows:
|
2017-10-22 00:54:27 -04:00
|
|
|
|
|
|
|
|
|
* `timeZoneName`: the name of the IANA time zone the server is using
|
|
|
|
|
to divide recordings into days as described further below.
|
2021-10-27 16:09:20 -04:00
|
|
|
|
* `serverVersion`: the version of the server in use, eg `0.7.0`.
|
2021-09-01 18:01:42 -04:00
|
|
|
|
* `cameras`: a list of cameras. Each is a JSON object as follows:
|
2017-10-22 00:54:27 -04:00
|
|
|
|
* `uuid`: in text format
|
2021-08-31 15:05:03 -04:00
|
|
|
|
* `id`: an integer. The client doesn't ever need to send the id
|
|
|
|
|
back in API requests, but camera ids are helpful to know when debugging
|
|
|
|
|
by reading logs or directly examining the filesystem/database.
|
2017-10-22 00:54:27 -04:00
|
|
|
|
* `shortName`: a short name (typically one or two words)
|
|
|
|
|
* `description`: a longer description (typically a phrase or paragraph)
|
2021-11-23 16:01:20 -05:00
|
|
|
|
* `config`: (only included if request parameter `cameraConfigs` is
|
|
|
|
|
true) a JSON object describing the configuration of the camera.
|
|
|
|
|
See doc comments on the `CameraConfig` type in
|
|
|
|
|
[`server/db/json.rs`](../server/db.json.rs).
|
|
|
|
|
* `streams`: a JSON object. Maps each configured stream type (valid types
|
|
|
|
|
are `main`, `sub`, and `ext`), a JSON object describing the stream:
|
2021-08-31 15:05:03 -04:00
|
|
|
|
* `id`: an integer. The client doesn't ever need to send the id
|
|
|
|
|
back in API requests, but stream ids are helpful to know when
|
|
|
|
|
debugging by reading logs or directly examining the
|
|
|
|
|
filesystem/database.
|
2018-01-23 14:05:07 -05:00
|
|
|
|
* `retainBytes`: the configured total number of bytes of completed
|
2021-11-23 16:01:20 -05:00
|
|
|
|
recordings to retain. This is copied from the `config` to make it
|
|
|
|
|
available when the client doesn't have permission to view
|
|
|
|
|
the full configuration.
|
2018-01-23 14:05:07 -05:00
|
|
|
|
* `minStartTime90k`: the start time of the earliest recording for
|
|
|
|
|
this camera, in 90kHz units since 1970-01-01 00:00:00 UTC.
|
|
|
|
|
* `maxEndTime90k`: the end time of the latest recording for this
|
|
|
|
|
camera, in 90kHz units since 1970-01-01 00:00:00 UTC.
|
|
|
|
|
* `totalDuration90k`: the total duration recorded, in 90 kHz units.
|
|
|
|
|
This is no greater than `maxEndTime90k - maxStartTime90k`; it will
|
|
|
|
|
be lesser if there are gaps in the recorded data.
|
|
|
|
|
* `totalSampleFileBytes`: the total number of bytes of sample data
|
|
|
|
|
(the `mdat` portion of a `.mp4` file).
|
2020-07-12 19:51:39 -04:00
|
|
|
|
* `fsBytes`: the total number of bytes on the filesystem used by
|
|
|
|
|
this stream. This is slightly more than `totalSampleFileBytes`
|
|
|
|
|
because it also includes the wasted portion of the final
|
|
|
|
|
filesystem block allocated to each file.
|
2021-03-23 23:22:29 -04:00
|
|
|
|
* `days`: (only included if request parameter `days` is true)
|
2021-09-01 18:01:42 -04:00
|
|
|
|
JSON object representing calendar days (in the server's time zone)
|
2020-07-18 14:57:17 -04:00
|
|
|
|
with non-zero total duration of recordings for that day. Currently
|
|
|
|
|
this includes uncommitted and growing recordings. This is likely
|
|
|
|
|
to change in a future release for
|
|
|
|
|
[#40](https://github.com/scottlamb/moonfire-nvr/issues/40). The
|
|
|
|
|
keys are of the form `YYYY-mm-dd`; the values are objects with the
|
2018-01-23 14:05:07 -05:00
|
|
|
|
following attributes:
|
|
|
|
|
* `totalDuration90k` is the total duration recorded during that
|
|
|
|
|
day. If a recording spans a day boundary, some portion of it
|
|
|
|
|
is accounted to each day.
|
|
|
|
|
* `startTime90k` is the start of that calendar day in the
|
|
|
|
|
server's time zone.
|
|
|
|
|
* `endTime90k` is the end of that calendar day in the server's
|
|
|
|
|
time zone. It is usually 24 hours after the start time. It
|
|
|
|
|
might be 23 hours or 25 hours during spring forward or fall
|
|
|
|
|
back, respectively.
|
2020-06-22 03:38:23 -04:00
|
|
|
|
* `config`: (only included if request parameter `cameraConfigs` is
|
2021-11-23 16:01:20 -05:00
|
|
|
|
true) a JSON object describing the configuration of the stream.
|
|
|
|
|
See doc comments on the `StreamConfig` type in
|
|
|
|
|
[`server/db/json.rs`](../server/db.json.rs).
|
2021-09-01 18:01:42 -04:00
|
|
|
|
* `signals`: a list of all *signals* known to the server. Each is a JSON
|
|
|
|
|
object with the following properties:
|
2021-02-22 16:46:51 -05:00
|
|
|
|
* `id`: an integer identifier.
|
2021-10-26 13:12:19 -04:00
|
|
|
|
* `uuid`: a UUID identifier.
|
2021-02-22 16:46:51 -05:00
|
|
|
|
* `shortName`: a unique, human-readable description of the signal
|
|
|
|
|
* `cameras`: a map of associated cameras' UUIDs to the type of association:
|
|
|
|
|
`direct` or `indirect`. See `db/schema.sql` for more description.
|
|
|
|
|
* `type`: a UUID, expected to match one of `signalTypes`.
|
2021-03-23 23:22:29 -04:00
|
|
|
|
* `days`: (only included if request parameter `days` is true) similar to
|
|
|
|
|
`cameras.days` above. Values are objects with the following attributes:
|
2021-11-23 15:14:56 -05:00
|
|
|
|
* `states`: an array of the total time (in 90,000ths of a second) the
|
|
|
|
|
signal was state 1, state 2, and so on during the day. These may not
|
|
|
|
|
sum to the entire day; if so, the rest of the day is in state 0
|
|
|
|
|
(`unknown`).
|
2019-06-06 19:18:13 -04:00
|
|
|
|
* `signalTypes`: a list of all known signal types.
|
2021-02-22 16:46:51 -05:00
|
|
|
|
* `uuid`: in text format.
|
2021-11-23 15:14:56 -05:00
|
|
|
|
* `states`: an array of all possible states of the enumeration to more
|
|
|
|
|
information about them. Each holds a JSON object:
|
|
|
|
|
* `value`: an integer used to refer to this state, 1 or higher.
|
|
|
|
|
Value 0 always means the `unknown` state.
|
|
|
|
|
* `name`: a human-readable name of this state.
|
2021-02-22 16:46:51 -05:00
|
|
|
|
* `motion`: if present and true, directly associated cameras will be
|
|
|
|
|
considered to have motion when this signal is in this state.
|
2021-11-23 15:14:56 -05:00
|
|
|
|
* `color` (optional): a recommended color to use in UIs to represent
|
|
|
|
|
this state, as in the [HTML specification](https://html.spec.whatwg.org/#colours).
|
2022-12-31 12:08:26 -05:00
|
|
|
|
* `permissions`: the caller's current `Permissions` object (defined below).
|
2021-11-23 16:01:20 -05:00
|
|
|
|
* `user`: an object, present only when authenticated:
|
2021-09-01 18:01:42 -04:00
|
|
|
|
* `name`: a human-readable name
|
|
|
|
|
* `id`: an integer
|
|
|
|
|
* `preferences`: a JSON object
|
|
|
|
|
* `session`: an object, present only if authenticated via session cookie.
|
|
|
|
|
* `csrf`: a cross-site request forgery token for use in `POST` requests.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
|
|
|
|
Example response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2017-10-22 00:54:27 -04:00
|
|
|
|
"timeZoneName": "America/Los_Angeles",
|
2016-04-23 16:55:36 -04:00
|
|
|
|
"cameras": [
|
|
|
|
|
{
|
|
|
|
|
"uuid": "fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe",
|
2017-10-10 00:58:44 -04:00
|
|
|
|
"shortName": "driveway",
|
2016-04-23 16:55:36 -04:00
|
|
|
|
"description": "Hikvision DS-2CD2032 overlooking the driveway from east",
|
2019-06-19 18:17:50 -04:00
|
|
|
|
"config": {
|
2019-07-01 00:54:52 -04:00
|
|
|
|
"onvif_host": "192.168.1.100",
|
2019-06-19 18:17:50 -04:00
|
|
|
|
"user": "admin",
|
|
|
|
|
"password": "12345",
|
|
|
|
|
},
|
2018-01-23 14:05:07 -05:00
|
|
|
|
"streams": {
|
|
|
|
|
"main": {
|
|
|
|
|
"retainBytes": 536870912000,
|
|
|
|
|
"minStartTime90k": 130888729442361,
|
|
|
|
|
"maxEndTime90k": 130985466591817,
|
|
|
|
|
"totalDuration90k": 96736169725,
|
|
|
|
|
"totalSampleFileBytes": 446774393937,
|
2021-08-13 15:02:42 -04:00
|
|
|
|
"record": true,
|
2018-01-23 14:05:07 -05:00
|
|
|
|
"days": {
|
|
|
|
|
"2016-05-01": {
|
|
|
|
|
"endTime90k": 131595516000000,
|
|
|
|
|
"startTime90k": 131587740000000,
|
|
|
|
|
"totalDuration90k": 52617609
|
|
|
|
|
},
|
|
|
|
|
"2016-05-02": {
|
|
|
|
|
"endTime90k": 131603292000000,
|
|
|
|
|
"startTime90k": 131595516000000,
|
|
|
|
|
"totalDuration90k": 20946022
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-22 00:54:27 -04:00
|
|
|
|
}
|
2018-01-23 14:05:07 -05:00
|
|
|
|
}
|
2016-04-23 16:55:36 -04:00
|
|
|
|
},
|
|
|
|
|
...
|
|
|
|
|
],
|
2019-06-20 15:10:23 -04:00
|
|
|
|
"signals": [
|
|
|
|
|
{
|
|
|
|
|
"id": 1,
|
2019-06-06 19:18:13 -04:00
|
|
|
|
"shortName": "driveway motion",
|
|
|
|
|
"cameras": {
|
|
|
|
|
"fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe": "direct"
|
|
|
|
|
},
|
|
|
|
|
"type": "ee66270f-d9c6-4819-8b33-9720d4cbca6b",
|
|
|
|
|
"days": {
|
|
|
|
|
"2016-05-01": {
|
|
|
|
|
"endTime90k": 131595516000000,
|
|
|
|
|
"startTime90k": 131587740000000,
|
2021-03-23 23:22:29 -04:00
|
|
|
|
"states": [5400000]
|
2019-06-06 19:18:13 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"signalTypes": [
|
|
|
|
|
{
|
|
|
|
|
"uuid": "ee66270f-d9c6-4819-8b33-9720d4cbca6b",
|
|
|
|
|
"states": {
|
|
|
|
|
0: {
|
|
|
|
|
"name": "unknown",
|
|
|
|
|
"color": "#000000"
|
|
|
|
|
},
|
|
|
|
|
1: {
|
|
|
|
|
"name": "off",
|
|
|
|
|
"color": "#888888"
|
|
|
|
|
},
|
|
|
|
|
2: {
|
|
|
|
|
"name": "on",
|
|
|
|
|
"color": "#ff8888",
|
|
|
|
|
"motion": true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
2021-09-01 18:01:42 -04:00
|
|
|
|
"user": {
|
|
|
|
|
"id": 1,
|
|
|
|
|
"name": "slamb",
|
|
|
|
|
"session": {
|
|
|
|
|
"csrf": "2DivvlnKUQ9JD4ao6YACBJm8XK4bFmOc"
|
|
|
|
|
}
|
2018-11-26 00:31:50 -05:00
|
|
|
|
}
|
2016-04-23 16:55:36 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/`
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2019-06-19 18:17:50 -04:00
|
|
|
|
Returns information for the camera with the given URL. As in the like section
|
|
|
|
|
of `GET /api/` with the `days` parameter set and the `cameraConfigs` parameter
|
|
|
|
|
unset.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2016-05-02 11:38:52 -04:00
|
|
|
|
Example response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2018-01-23 14:05:07 -05:00
|
|
|
|
"description": "",
|
|
|
|
|
"streams": {
|
|
|
|
|
"main": {
|
|
|
|
|
"days": {
|
|
|
|
|
"2016-05-01": {
|
|
|
|
|
"endTime90k": 131595516000000,
|
|
|
|
|
"startTime90k": 131587740000000,
|
|
|
|
|
"totalDuration90k": 52617609
|
|
|
|
|
},
|
|
|
|
|
"2016-05-02": {
|
|
|
|
|
"endTime90k": 131603292000000,
|
|
|
|
|
"startTime90k": 131595516000000,
|
|
|
|
|
"totalDuration90k": 20946022
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"maxEndTime90k": 131598273666690,
|
|
|
|
|
"minStartTime90k": 131590386129355,
|
|
|
|
|
"retainBytes": 104857600,
|
|
|
|
|
"totalDuration90k": 73563631,
|
|
|
|
|
"totalSampleFileBytes": 98901406
|
2016-05-02 11:38:52 -04:00
|
|
|
|
}
|
|
|
|
|
},
|
2018-01-23 14:05:07 -05:00
|
|
|
|
"shortName": "driveway"
|
2016-05-02 11:38:52 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/recordings`
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2020-08-05 00:44:01 -04:00
|
|
|
|
Returns information about *recordings*. Valid request parameters:
|
2016-05-03 08:17:06 -04:00
|
|
|
|
|
2017-10-10 00:58:44 -04:00
|
|
|
|
* `startTime90k` and and `endTime90k` limit the data returned to only
|
2020-08-05 00:44:01 -04:00
|
|
|
|
recordings with wall times overlapping with the given half-open interval.
|
|
|
|
|
Either or both may be absent; they default to the beginning and end of time,
|
|
|
|
|
respectively.
|
2017-10-17 12:00:05 -04:00
|
|
|
|
* `split90k` causes long runs of recordings to be split at the next
|
|
|
|
|
convenient boundary after the given duration.
|
2016-05-03 08:17:06 -04:00
|
|
|
|
* TODO(slamb): `continue` to support paging. (If data is too large, the
|
|
|
|
|
server should return a `continue` key which is expected to be returned on
|
|
|
|
|
following requests.)
|
|
|
|
|
|
2020-03-14 00:20:51 -04:00
|
|
|
|
Returns a JSON object. Under the key `recordings` is an array of recordings in
|
|
|
|
|
arbitrary order. Each recording object has the following properties:
|
2016-12-21 01:08:18 -05:00
|
|
|
|
|
2017-10-10 00:58:44 -04:00
|
|
|
|
* `startId`. The id of this recording, which can be used with `/view.mp4`
|
2016-12-21 01:08:18 -05:00
|
|
|
|
to retrieve its content.
|
2017-10-10 00:58:44 -04:00
|
|
|
|
* `endId` (optional). If absent, this object describes a single recording.
|
|
|
|
|
If present, this indicates that recordings `startId-endId` (inclusive)
|
2016-12-21 01:08:18 -05:00
|
|
|
|
together are as described. Adjacent recordings from the same RTSP session
|
|
|
|
|
may be coalesced in this fashion to reduce the amount of redundant data
|
|
|
|
|
transferred.
|
2018-03-02 14:38:11 -05:00
|
|
|
|
* `firstUncommitted` (optional). If this range is not fully committed to the
|
|
|
|
|
database, the first id that is uncommitted. This is significant because
|
|
|
|
|
it's possible that after a crash and restart, this id will refer to a
|
|
|
|
|
completely different recording. That recording will have a different
|
|
|
|
|
`openId`.
|
2018-03-02 18:40:32 -05:00
|
|
|
|
* `growing` (optional). If this boolean is true, the recording `endId` is
|
|
|
|
|
still being written to. Accesses to this id (such as `view.mp4`) may
|
|
|
|
|
retrieve more data than described here if not bounded by duration.
|
|
|
|
|
Additionally, if `startId` == `endId`, the start time of the recording is
|
|
|
|
|
"unanchored" and may change in subsequent accesses.
|
2018-03-02 14:38:11 -05:00
|
|
|
|
* `openId`. Each time Moonfire NVR starts in read-write mode, it is assigned
|
|
|
|
|
an increasing "open id". This field is the open id as of when these
|
|
|
|
|
recordings were written. This can be used to disambiguate ids referring to
|
|
|
|
|
uncommitted recordings.
|
2020-08-05 00:44:01 -04:00
|
|
|
|
* `startTime90k`: the start time of the given recording, in the wall time
|
|
|
|
|
scale. Note this may be less than the requested `startTime90k` if this
|
|
|
|
|
recording was ongoing at the requested time.
|
|
|
|
|
* `endTime90k`: the end time of the given recording, in the wall time scale.
|
|
|
|
|
Note this may be greater than the requested `endTime90k` if this recording
|
|
|
|
|
was ongoing at the requested time.
|
2020-03-14 00:20:51 -04:00
|
|
|
|
* `videoSampleEntryId`: a reference to an entry in the `videoSampleEntries`
|
2021-03-14 01:25:05 -05:00
|
|
|
|
object.
|
2017-10-10 00:58:44 -04:00
|
|
|
|
* `videoSamples`: the number of samples (aka frames) of video in this
|
2016-05-10 20:37:53 -04:00
|
|
|
|
recording.
|
2021-02-22 16:46:51 -05:00
|
|
|
|
* `sampleFileBytes`: the number of bytes of video in this recording.
|
2021-10-10 19:13:57 -04:00
|
|
|
|
* `hasTrailingZero`: the final frame of the final recording id described by
|
|
|
|
|
this row (`endId` if present, otherwise `startId`) has a duration of 0.
|
|
|
|
|
A frame's duration is calculated by subtracting its timestamp from the
|
|
|
|
|
following frame's timestamp. When a run ends, there's no following frame
|
|
|
|
|
and Moonfire NVR fills in a duration of 0. When using `/view.mp4`, it's
|
|
|
|
|
not possible to append additional segments after such frames, as noted
|
|
|
|
|
below.
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2020-03-14 00:20:51 -04:00
|
|
|
|
Under the property `videoSampleEntries`, an object mapping ids to objects with
|
|
|
|
|
the following properties:
|
|
|
|
|
|
|
|
|
|
* `width`: the stored width in pixels.
|
|
|
|
|
* `height`: the stored height in pixels.
|
|
|
|
|
* `pixelHSpacing`: the relative width of a pixel, as in a ISO/IEC 14496-12
|
|
|
|
|
section 12.1.4.3 `PixelAspectRatioBox`. If absent, assumed to be 1.
|
|
|
|
|
* `pixelVSpacing`: the relative height of a pixel, as in a ISO/IEC 14496-12
|
|
|
|
|
section 12.1.4.3 `PixelAspectRatioBox`. If absent, assumed to be 1.
|
2021-08-12 16:32:01 -04:00
|
|
|
|
* `aspectWidth`: the width component of the aspect ratio. (The aspect ratio
|
|
|
|
|
can be computed from the dimensions and pixel spacing; it's included as a
|
|
|
|
|
convenience.)
|
|
|
|
|
* `aspectHeight`: the height component of the aspect ratio.
|
2020-03-14 00:20:51 -04:00
|
|
|
|
|
2020-03-20 23:52:30 -04:00
|
|
|
|
The full initialization segment data for a given video sample entry can be
|
|
|
|
|
retrieved at the URL `/api/init/<id>.mp4`.
|
|
|
|
|
|
2016-04-23 16:55:36 -04:00
|
|
|
|
Example request URI (with added whitespace between parameters):
|
|
|
|
|
|
|
|
|
|
```
|
2018-01-23 14:05:07 -05:00
|
|
|
|
/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/recordings
|
2017-10-10 00:58:44 -04:00
|
|
|
|
?startTime90k=130888729442361
|
|
|
|
|
&endTime90k=130985466591817
|
2016-04-23 16:55:36 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Example response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"recordings": [
|
|
|
|
|
{
|
2017-10-10 00:58:44 -04:00
|
|
|
|
"startId": 1,
|
|
|
|
|
"startTime90k": 130985461191810,
|
|
|
|
|
"endTime90k": 130985466591817,
|
|
|
|
|
"sampleFileBytes": 8405564,
|
2021-02-22 16:46:51 -05:00
|
|
|
|
"videoSampleEntryId": 1,
|
2016-04-23 16:55:36 -04:00
|
|
|
|
},
|
|
|
|
|
{
|
2017-10-10 00:58:44 -04:00
|
|
|
|
"endTime90k": 130985461191810,
|
2016-04-23 16:55:36 -04:00
|
|
|
|
...
|
|
|
|
|
},
|
|
|
|
|
...
|
|
|
|
|
],
|
2020-03-14 00:20:51 -04:00
|
|
|
|
"videoSampleEntries": {
|
|
|
|
|
"1": {
|
|
|
|
|
"width": 1280,
|
|
|
|
|
"height": 720
|
|
|
|
|
}
|
|
|
|
|
},
|
2016-04-23 16:55:36 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/view.mp4`
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Requires the `viewVideo` permission.
|
2019-06-19 18:17:50 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `.mp4` file, with an etag and support for range requests. The MIME
|
|
|
|
|
type will be `video/mp4`, with a `codecs` parameter as specified in
|
|
|
|
|
[RFC 6381][rfc-6381].
|
2016-04-23 16:55:36 -04:00
|
|
|
|
|
|
|
|
|
Expected query parameters:
|
|
|
|
|
|
2016-12-21 01:08:18 -05:00
|
|
|
|
* `s` (one or more): a string of the form
|
2018-03-02 14:38:11 -05:00
|
|
|
|
`START_ID[-END_ID][@OPEN_ID][.[REL_START_TIME]-[REL_END_TIME]]`. This
|
2020-08-05 00:44:01 -04:00
|
|
|
|
specifies *segments* to include. The produced `.mp4` file will be a
|
|
|
|
|
concatenation of the segments indicated by all `s` parameters. The ids to
|
2020-08-07 13:16:06 -04:00
|
|
|
|
retrieve are as returned by the `/recordings` URL. The *open id* is
|
|
|
|
|
optional and will be enforced if present; it's recommended for
|
|
|
|
|
disambiguation when the requested range includes uncommitted recordings.
|
|
|
|
|
The optional start and end times are in 90k units of wall time and relative
|
|
|
|
|
to the start of the first specified id. These can be used to clip the
|
|
|
|
|
returned segments. Note they can be used to skip over some ids entirely;
|
|
|
|
|
this is allowed so that the caller doesn't need to know the start time of
|
|
|
|
|
each interior id. If there is no key frame at the desired relative start
|
|
|
|
|
time, frames back to the last key frame will be included in the returned
|
|
|
|
|
data, and an edit list will instruct the viewer to skip to the desired
|
|
|
|
|
start time.
|
2016-12-21 01:08:18 -05:00
|
|
|
|
* `ts` (optional): should be set to `true` to request a subtitle track be
|
|
|
|
|
added with human-readable recording timestamps.
|
|
|
|
|
|
|
|
|
|
Example request URI to retrieve all of recording id 1 from the given camera:
|
|
|
|
|
|
|
|
|
|
```
|
2018-01-23 14:05:07 -05:00
|
|
|
|
/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.mp4?s=1
|
2016-12-21 01:08:18 -05:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Example request URI to retrieve all of recording ids 1–5 from the given camera,
|
|
|
|
|
with timestamp subtitles:
|
|
|
|
|
|
|
|
|
|
```
|
2018-01-23 14:05:07 -05:00
|
|
|
|
/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.mp4?s=1-5&ts=true
|
2016-12-21 01:08:18 -05:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Example request URI to retrieve recording id 1, skipping its first 26
|
|
|
|
|
90,000ths of a second:
|
|
|
|
|
|
|
|
|
|
```
|
2021-10-09 11:13:17 -04:00
|
|
|
|
/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.mp4?s=1.26-
|
2016-12-21 01:08:18 -05:00
|
|
|
|
```
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2020-08-05 00:44:01 -04:00
|
|
|
|
Note carefully the distinction between *wall duration* and *media duration*.
|
|
|
|
|
It's normal for `/view.mp4` to return a media presentation with a length
|
|
|
|
|
slightly different from the *wall duration* of the backing recording or
|
|
|
|
|
portion that was requested.
|
|
|
|
|
|
2021-10-10 18:58:55 -04:00
|
|
|
|
Bugs and limitations:
|
|
|
|
|
|
|
|
|
|
* If the `s=` parameter references a recording id that doesn't exist when the
|
|
|
|
|
server starts processing the `/view.mp4` request, the server will return a
|
|
|
|
|
`404` with a text error message. This commonly happens when the oldest
|
|
|
|
|
recording was deleted between the `/recordings` request and the `/view.mp4`
|
|
|
|
|
request. The server probably should return a structured JSON document
|
|
|
|
|
describing exactly which recordings have been deleted. For now, the client
|
|
|
|
|
will have to retry from `/recordings` and again race against deletion.
|
|
|
|
|
* If a recording is deleted after the server starts processing `/view.mp4`
|
|
|
|
|
but before the request advances to the recording's byte position, the server
|
|
|
|
|
will abruptly drop the HTTP connection. The client must then retry to see
|
|
|
|
|
a proper 404 error. It'd be better if the server would prevent recordings
|
|
|
|
|
from being deleted while there are `/view.mp4` requests in progress which
|
|
|
|
|
reference them.
|
|
|
|
|
* The final recording in every "run" ends with a frame that has duration 0.
|
|
|
|
|
It's not possible to append additional segments after such a frame;
|
2021-10-21 13:25:37 -04:00
|
|
|
|
the server will return a 400 error like `Invalid argument: unable to append
|
2021-10-10 19:13:57 -04:00
|
|
|
|
recording 2/16672 after recording 2/16671 with trailing zero`. See also
|
|
|
|
|
`hasTrailingZero` above, and
|
|
|
|
|
[#178](https://github.com/scottlamb/moonfire-nvr/issues/178).
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/view.mp4.txt`
|
2018-12-29 14:06:44 -05:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `text/plain` debugging string for the `.mp4` generated by the
|
2018-12-29 14:06:44 -05:00
|
|
|
|
same URL minus the `.txt` suffix.
|
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/view.m4s`
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions
|
2017-10-04 02:25:58 -04:00
|
|
|
|
media segment][media-segment]. The MIME type will be `video/mp4`, with a
|
2020-06-10 01:06:03 -04:00
|
|
|
|
`codecs` parameter as specified in [RFC 6381][rfc-6381]. Note that these
|
|
|
|
|
can't include edit lists, so (unlike `/view.mp4`) the caller must manually
|
|
|
|
|
trim undesired leading portions.
|
|
|
|
|
|
|
|
|
|
This response will include the following additional headers:
|
|
|
|
|
|
2020-08-05 00:44:01 -04:00
|
|
|
|
* `X-Prev-Media-Duration`: the total *media duration* (in 90 kHz units) of all
|
|
|
|
|
*recordings* before the first requested recording in the `s` parameter.
|
|
|
|
|
Browser-based callers may use this to place this at the correct position in
|
|
|
|
|
the source buffer via `SourceBuffer.timestampOffset`.
|
2020-06-10 01:06:03 -04:00
|
|
|
|
* `X-Runs`: the cumulative number of "runs" of recordings. If this recording
|
|
|
|
|
starts a new run, it is included in the count. Browser-based callers may
|
|
|
|
|
use this to force gaps in the source buffer timeline by adjusting the
|
|
|
|
|
timestamp offset if desired.
|
2020-08-05 00:44:01 -04:00
|
|
|
|
* `X-Leading-Media-Duration`: if present, the total duration (in 90 kHz
|
|
|
|
|
units) of additional leading video included before the caller's first
|
|
|
|
|
requested timestamp. This happens when the caller's requested timestamp
|
|
|
|
|
does not fall exactly on a key frame. Media segments can't include edit
|
|
|
|
|
lists, so unlike with the `/api/.../view.mp4` endpoint the caller is
|
|
|
|
|
responsible for trimming this portion. Browser-based callers may use
|
2020-06-10 01:06:03 -04:00
|
|
|
|
`SourceBuffer.appendWindowStart`.
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
|
|
|
|
Expected query parameters:
|
|
|
|
|
|
2020-06-10 01:06:03 -04:00
|
|
|
|
* `s` (one or more): as with the `.mp4` URL.
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
|
|
|
|
It's recommended that each `.m4s` retrieval be for at most one Moonfire NVR
|
2021-10-10 18:58:55 -04:00
|
|
|
|
recording. The fundamental reason is that the Media Source Extension API appears
|
|
|
|
|
structured for adding a complete segment at a time. Large media segments thus
|
|
|
|
|
impose significant latency on seeking. Additionally, because of this fundamental
|
|
|
|
|
reason Moonfire NVR makes no effort to make multiple-segment `.m4s` requests
|
|
|
|
|
practical:
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
|
|
|
|
* There is currently a hard limit of 4 GiB of data because the `.m4s` uses a
|
|
|
|
|
single `moof` followed by a single `mdat`; the former references the
|
|
|
|
|
latter with 32-bit offsets.
|
|
|
|
|
* There's currently no way to generate an initialization segment for more
|
|
|
|
|
than one video sample entry, so a `.m4s` that uses more than one video
|
|
|
|
|
sample entry can't be used.
|
2020-08-07 13:16:06 -04:00
|
|
|
|
* The `X-Prev-Media-Duration` and `X-Leading-Media-Duration` headers only
|
|
|
|
|
describe the first segment.
|
2020-08-05 00:44:01 -04:00
|
|
|
|
|
|
|
|
|
Timestamp tracks (see the `ts` parameter to `.mp4` URIs) aren't supported
|
|
|
|
|
today. Most likely browser clients will implement timestamp subtitles via
|
|
|
|
|
WebVTT API calls anyway.
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/view.m4s.txt`
|
2018-12-29 14:06:44 -05:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `text/plain` debugging string for the `.mp4` generated by the same
|
|
|
|
|
URL minus the `.txt` suffix.
|
2018-12-29 14:06:44 -05:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/cameras/<uuid>/<stream>/live.m4s`
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2020-02-29 00:41:31 -05:00
|
|
|
|
Initiate a WebSocket stream for chunks of video. Expects the standard
|
|
|
|
|
WebSocket headers as described in [RFC 6455][rfc-6455] and (if authentication
|
|
|
|
|
is required) the `s` cookie.
|
|
|
|
|
|
2023-02-15 10:04:50 -05:00
|
|
|
|
The server will send messages as follows:
|
|
|
|
|
|
|
|
|
|
* text: a plaintext error message, followed by the end of stream.
|
|
|
|
|
* binary: video data, repeatedly, as described below.
|
|
|
|
|
* ping: every 30 seconds.
|
|
|
|
|
|
|
|
|
|
Each binary message corresponds to one or more frames of video. The first
|
|
|
|
|
message is guaranteed to start with a "key" (IDR) frame; others may not. The
|
|
|
|
|
message will contain HTTP headers followed by by a `.mp4` media segment. The
|
|
|
|
|
following headers will be included:
|
2020-02-29 00:41:31 -05:00
|
|
|
|
|
2021-09-29 21:39:18 -04:00
|
|
|
|
* `X-Video-Sample-Entry-Id`: An id to use when fetching an initialization segment.
|
2020-02-29 00:41:31 -05:00
|
|
|
|
* `X-Recording-Id`: the open id, a period, and the recording id of the
|
|
|
|
|
recording these frames belong to.
|
|
|
|
|
* `X-Recording-Start`: the timestamp (in Moonfire NVR's usual 90,000ths
|
|
|
|
|
of a second) of the start of the recording. Note that if the recording
|
|
|
|
|
is "unanchored" (as described in `GET /api/.../recordings`), the
|
|
|
|
|
recording's start time may change before it is completed.
|
2020-08-07 13:16:06 -04:00
|
|
|
|
* `X-Prev-Media-Duration`: as in `/.../view.m4s`.
|
2020-06-10 01:06:03 -04:00
|
|
|
|
* `X-Runs`: as in `/.../view.m4s`.
|
2020-08-07 13:16:06 -04:00
|
|
|
|
* `X-Media-Time-Range`: the relative media start and end times of these
|
|
|
|
|
frames within the recording, as a half-open interval.
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2023-02-15 10:04:50 -05:00
|
|
|
|
The WebSocket will always open immediately but will receive messages only while
|
|
|
|
|
the backing RTSP stream is connected.
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
|
|
|
|
Example request URI:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/live.m4s
|
|
|
|
|
```
|
|
|
|
|
|
2020-02-29 00:41:31 -05:00
|
|
|
|
Example binary message sequence:
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Content-Type: video/mp4; codecs="avc1.640028"
|
2020-02-29 00:41:31 -05:00
|
|
|
|
X-Recording-Id: 42.5680
|
|
|
|
|
X-Recording-Start: 130985461191810
|
2020-08-07 13:16:06 -04:00
|
|
|
|
X-Prev-Media-Duration: 10000000
|
|
|
|
|
X-Media-Time-Range: 5220058-5400061
|
2020-03-20 23:52:30 -04:00
|
|
|
|
X-Video-Sample-Entry-Id: 4
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
|
|
|
|
binary mp4 data
|
2020-02-29 00:41:31 -05:00
|
|
|
|
```
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2020-02-29 00:41:31 -05:00
|
|
|
|
```
|
2019-01-21 18:58:52 -05:00
|
|
|
|
Content-Type: video/mp4; codecs="avc1.640028"
|
2020-02-29 00:41:31 -05:00
|
|
|
|
X-Recording-Id: 42.5681
|
|
|
|
|
X-Recording-Start: 130985461191822
|
2020-08-07 13:16:06 -04:00
|
|
|
|
X-Prev-Media-Duration: 10180003
|
|
|
|
|
X-Media-Time-Range: 0-180002
|
2020-03-20 23:52:30 -04:00
|
|
|
|
X-Video-Sample-Entry-Id: 4
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
|
|
|
|
binary mp4 data
|
2020-02-29 00:41:31 -05:00
|
|
|
|
```
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2020-02-29 00:41:31 -05:00
|
|
|
|
```
|
2019-01-21 18:58:52 -05:00
|
|
|
|
Content-Type: video/mp4; codecs="avc1.640028"
|
2020-02-29 00:41:31 -05:00
|
|
|
|
X-Recording-Id: 42.5681
|
|
|
|
|
X-Recording-Start: 130985461191822
|
2020-08-07 13:16:06 -04:00
|
|
|
|
X-Prev-Media-Duration: 10360005
|
|
|
|
|
X-Media-Time-Range: 180002-360004
|
2020-03-20 23:52:30 -04:00
|
|
|
|
X-Video-Sample-Entry-Id: 4
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
|
|
|
|
binary mp4 data
|
|
|
|
|
```
|
|
|
|
|
|
2020-08-07 18:30:22 -04:00
|
|
|
|
These roughly correspond to the `.m4s` files available at the following URLs:
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2020-08-07 13:16:06 -04:00
|
|
|
|
* `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5680@42.5220058-5400061`
|
|
|
|
|
* `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5681@42.0-180002`
|
|
|
|
|
* `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5681@42.180002-360004`
|
2019-01-21 18:58:52 -05:00
|
|
|
|
|
2020-08-07 18:30:22 -04:00
|
|
|
|
However, there are two important differences:
|
|
|
|
|
|
|
|
|
|
* The `/view.m4s` endpoint accepts offsets within a recording as wall durations;
|
|
|
|
|
the `/live.m4s` endpoint's `X-Media-Time-Range` header returns them as
|
|
|
|
|
media durations. Thus the URLs above are only exactly correct if the wall
|
|
|
|
|
and media durations of the recording are identical.
|
|
|
|
|
* The `/view.m4s` endpoint always returns a time range that starts with a key frame;
|
|
|
|
|
`/live.m4s` messages may not include a key frame.
|
|
|
|
|
|
2020-02-29 00:41:31 -05:00
|
|
|
|
Note: an earlier version of this API used a `multipart/mixed` segment instead,
|
|
|
|
|
compatible with the [multipart-stream-js][multipart-stream-js] library. The
|
|
|
|
|
problem with this approach is that browsers have low limits on the number of
|
|
|
|
|
active HTTP/1.1 connections: six in Chrome's case. The WebSocket limit is much
|
|
|
|
|
higher (256), allowing browser-side Javascript to stream all active camera
|
|
|
|
|
streams simultaneously as well as making other simultaneous HTTP requests.
|
|
|
|
|
|
2020-03-20 23:52:30 -04:00
|
|
|
|
### `GET /api/init/<id>.mp4`
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions
|
2017-10-04 02:25:58 -04:00
|
|
|
|
initialization segment][init-segment]. The MIME type will be `video/mp4`, with
|
2021-09-29 21:39:18 -04:00
|
|
|
|
a `codecs` parameter as specified in [RFC 6381][rfc-6381]. The `<id>` should be a value
|
|
|
|
|
previously extracted from the `X-Video-Sample-Entry-Id` header returned in a `.../live.m4s` response.
|
2017-10-01 18:29:22 -04:00
|
|
|
|
|
2021-08-12 16:32:01 -04:00
|
|
|
|
An `X-Aspect` HTTP header will include the aspect ratio as width:height,
|
|
|
|
|
eg `16:9` (most cameras) or `9:16` (rotated 90 degrees).
|
|
|
|
|
This is redundant with the returned `.mp4` but is far easier to parse from
|
|
|
|
|
Javascript.
|
|
|
|
|
|
2020-03-20 23:52:30 -04:00
|
|
|
|
### `GET /api/init/<id>.mp4.txt`
|
2018-12-29 14:06:44 -05:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns a `text/plain` debugging string for the `.mp4` generated by the
|
2018-12-29 14:06:44 -05:00
|
|
|
|
same URL minus the `.txt` suffix.
|
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `GET /api/signals`
|
2019-06-06 19:18:13 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Returns an `application/json` response with state of every signal for the
|
|
|
|
|
requested timespan.
|
2019-06-06 19:18:13 -04:00
|
|
|
|
|
|
|
|
|
Valid request parameters:
|
|
|
|
|
|
|
|
|
|
* `startTime90k` and and `endTime90k` limit the data returned to only
|
|
|
|
|
events relevant to the given half-open interval. Either or both
|
|
|
|
|
may be absent; they default to the beginning and end of time, respectively.
|
|
|
|
|
This will return the current state as of the latest change (to any signal)
|
|
|
|
|
before the start time (if any), then all changes in the interval. This
|
|
|
|
|
allows the caller to determine the state at every moment during the
|
2021-03-23 23:22:29 -04:00
|
|
|
|
selected timespan, as well as observe all events.
|
2019-06-06 19:18:13 -04:00
|
|
|
|
|
|
|
|
|
Responses are several parallel arrays for each observation:
|
|
|
|
|
|
|
|
|
|
* `times90k`: the time of each event. Events are given in ascending order.
|
|
|
|
|
* `signalIds`: the id of the relevant signal; expected to match one in the
|
|
|
|
|
`signals` field of the `/api/` response.
|
|
|
|
|
* `states`: the new state.
|
|
|
|
|
|
|
|
|
|
Example request URI (with added whitespace between parameters):
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/api/signals
|
|
|
|
|
?startTime90k=130888729442361
|
|
|
|
|
&endTime90k=130985466591817
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Example response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"signalIds": [1, 1, 1],
|
|
|
|
|
"states": [1, 2, 1],
|
|
|
|
|
"times90k": [130888729440000, 130985424000000, 130985418600000]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This represents the following observations:
|
|
|
|
|
|
|
|
|
|
1. time 130888729440000 was the last change before the requested start;
|
|
|
|
|
signal 1 (`driveway motion`) was in state 1 (`off`).
|
|
|
|
|
2. signal 1 entered state 2 (`on`) at time 130985424000000.
|
|
|
|
|
3. signal 1 entered state 1 (`off`) at time 130985418600000.
|
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
### `POST /api/signals`
|
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Requires the `updateSignals` permission.
|
2019-06-19 18:17:50 -04:00
|
|
|
|
|
2019-06-07 13:19:38 -04:00
|
|
|
|
Alters the state of a signal.
|
|
|
|
|
|
2019-06-14 00:55:15 -04:00
|
|
|
|
A typical client might be a subscriber of a camera's built-in motion
|
|
|
|
|
detection event stream or of a security system's zone status event stream.
|
|
|
|
|
It makes a request on every event or on every 30 second timeout, predicting
|
|
|
|
|
that the state will last for a minute. This prediction may be changed later.
|
|
|
|
|
Writing to the near future in this way ensures that the UI never displays
|
|
|
|
|
`unknown` when the client is actively managing the signal.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
|
|
|
|
Some requests may instead backfill earlier history, such as when a video
|
|
|
|
|
analytics client starts up and analyzes all video segments recorded since it
|
2019-06-14 00:55:15 -04:00
|
|
|
|
last ran. These will specify beginning and end times.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
|
|
|
|
The request should have an `application/json` body describing the change to
|
2021-09-01 18:01:42 -04:00
|
|
|
|
make. It should be a JSON object with these attributes:
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `csrf`: a CSRF token, required when using session authentication.
|
2019-06-14 00:55:15 -04:00
|
|
|
|
* `signalIds`: a list of signal ids to change. Must be sorted.
|
|
|
|
|
* `states`: a list (one per `signalIds` entry) of states to set.
|
2021-09-01 18:01:42 -04:00
|
|
|
|
* `start`: the starting time of the change, as a JSON object of the form
|
2021-04-21 13:44:01 -04:00
|
|
|
|
`{'base': 'epoch', 'rel90k': t}` or `{'base': 'now', 'rel90k': t}`. In
|
|
|
|
|
the `epoch` form, `rel90k` is 90 kHz units since 1970-01-01 00:00:00 UTC.
|
|
|
|
|
In the `now` form, `rel90k` is relative to current time and may be
|
|
|
|
|
negative.
|
|
|
|
|
* `end`: the ending time of the change, in the same form as `start`.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
2021-09-01 18:01:42 -04:00
|
|
|
|
The response will be an `application/json` body JSON object with the following
|
2019-06-07 13:19:38 -04:00
|
|
|
|
attributes:
|
|
|
|
|
|
2019-06-14 00:55:15 -04:00
|
|
|
|
* `time90k`: the current time. When the request's `startTime90k` is absent
|
|
|
|
|
and/or its `endBase` is `now`, this is needed to know the effect of the
|
|
|
|
|
earlier request.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
|
|
|
|
Example request sequence:
|
|
|
|
|
|
|
|
|
|
#### Request 1
|
|
|
|
|
|
|
|
|
|
The client responsible for reporting live driveway motion has just started. It
|
|
|
|
|
observes motion now. It records no history and predicts there will be motion
|
|
|
|
|
for the next minute.
|
|
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"signalIds": [1],
|
|
|
|
|
"states": [2],
|
2021-04-21 13:44:01 -04:00
|
|
|
|
"start": {"base": "now", "rel90k": 0},
|
|
|
|
|
"end": {"base": "now", "rel90k": 5400000}
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"time90k": 140067468000000
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Request 2
|
|
|
|
|
|
|
|
|
|
30 seconds later (half the prediction interval), the client still observes
|
2019-06-14 00:55:15 -04:00
|
|
|
|
motion. It leaves the prior data alone and predicts the motion will continue.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"signalIds": [1],
|
|
|
|
|
"states": [2],
|
2021-04-21 13:44:01 -04:00
|
|
|
|
"start": {"base": "epoch", "rel90k": 140067468000000},
|
|
|
|
|
"end": {"base": "now", "rel90k": 5400000}
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"time90k": 140067470700000
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-04-01 15:10:43 -04:00
|
|
|
|
#### Request 3
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
2019-06-14 00:55:15 -04:00
|
|
|
|
5 seconds later, the client observes motion has ended. It leaves the prior
|
|
|
|
|
data alone and predicts no more motion.
|
2019-06-07 13:19:38 -04:00
|
|
|
|
|
|
|
|
|
Request:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"signalIds": [1],
|
|
|
|
|
"states": [2],
|
2021-04-21 13:44:01 -04:00
|
|
|
|
"start": {"base": "now", "rel90k": 0},
|
|
|
|
|
"end": {"base": "now", "rel90k": 5400000}
|
2019-06-14 00:55:15 -04:00
|
|
|
|
}
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Response:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2019-06-14 00:55:15 -04:00
|
|
|
|
"time90k": 140067471150000
|
2019-06-07 13:19:38 -04:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2022-12-24 13:09:05 -05:00
|
|
|
|
### User management
|
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
#### `GET /api/users/`
|
2022-12-24 13:09:05 -05:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Requires the `adminUsers` permission.
|
2022-12-24 13:09:05 -05:00
|
|
|
|
|
|
|
|
|
Lists all users. Currently there's no paging. Returns a JSON object with
|
2023-01-05 13:11:28 -05:00
|
|
|
|
a `users` key with an array of objects, each with the following keys:
|
|
|
|
|
|
|
|
|
|
* `id`: a number.
|
2023-01-08 04:14:03 -05:00
|
|
|
|
* `user`: a `UserSubset`.
|
2022-12-24 13:09:05 -05:00
|
|
|
|
|
2023-01-08 04:14:03 -05:00
|
|
|
|
#### `POST /api/users/`
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Requires the `adminUsers` permission.
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Adds a user. Expects a JSON object as follows:
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `csrf`: a CSRF token, required when using session authentication.
|
|
|
|
|
* `user`: a `UserSubset` as defined below.
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
|
|
|
|
Returns status 204 (No Content) on success.
|
|
|
|
|
|
2022-12-24 13:09:05 -05:00
|
|
|
|
#### `GET /api/users/<id>`
|
2022-12-24 12:38:13 -05:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Retrieves the user. Requires the `adminUsers` permission if the caller is
|
2022-12-24 12:38:13 -05:00
|
|
|
|
not authenticated as the user in question.
|
|
|
|
|
|
2023-01-08 04:14:03 -05:00
|
|
|
|
Returns a HTTP status 200 on success with a JSON `UserSubset`.
|
2022-12-24 12:38:13 -05:00
|
|
|
|
|
2023-01-08 04:14:03 -05:00
|
|
|
|
#### `PATCH /api/users/<id>`
|
2021-09-01 18:01:42 -04:00
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Updates the given user. Requires the `adminUsers` permission if the caller is
|
|
|
|
|
not authenticated as the user in question.
|
2021-09-01 18:01:42 -04:00
|
|
|
|
|
|
|
|
|
Expects a JSON object:
|
|
|
|
|
|
2022-12-23 15:43:00 -05:00
|
|
|
|
* `csrf`: a CSRF token, required when using session authentication.
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `update`: `UserSubset`, sets the provided fields. Field-specific notes:
|
2023-01-31 09:47:25 -05:00
|
|
|
|
* `disabled`: requires `adminUsers` permission.
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `password`: when updating the password, the previous password must
|
2023-01-08 04:14:03 -05:00
|
|
|
|
be supplied as a precondition, unless the caller has `adminUsers`
|
2023-01-05 13:11:28 -05:00
|
|
|
|
permission.
|
|
|
|
|
* `permissions`: requires `adminUsers` permission. Note that updating a
|
|
|
|
|
user's permissions currently neither adds nor limits permissions of
|
|
|
|
|
existing sessions; it only changes what is available to newly created
|
|
|
|
|
sessions.
|
2023-01-31 09:47:25 -05:00
|
|
|
|
* `username`: requires `adminUsers` permission.
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `precondition`: `UserSubset`, forces the request to fail with HTTP status
|
|
|
|
|
412 (Precondition failed) if the provided fields don't have the given
|
|
|
|
|
values.
|
2021-09-01 18:01:42 -04:00
|
|
|
|
|
|
|
|
|
Returns HTTP status 204 (No Content) on success.
|
|
|
|
|
|
2022-12-24 15:21:06 -05:00
|
|
|
|
#### `DELETE /api/users/<id>`
|
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
Deletes the given user. Requires the `adminUsers` permission.
|
|
|
|
|
|
|
|
|
|
Expects a JSON object body with the following parameters:
|
|
|
|
|
|
|
|
|
|
* `csrf`: a CSRF token, required when using session authentication.
|
2022-12-24 15:21:06 -05:00
|
|
|
|
|
|
|
|
|
Returns HTTP status 204 (No Content) on success.
|
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
## Types
|
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
### UserSubset
|
|
|
|
|
|
|
|
|
|
A JSON object with any of the following parameters:
|
|
|
|
|
|
2023-01-31 09:47:25 -05:00
|
|
|
|
* `disabled`, boolean indicating if all logins from the user are rejected.
|
2023-01-08 04:14:03 -05:00
|
|
|
|
* `password`
|
|
|
|
|
* on retrieval, a placeholder string to indicate a password is set,
|
|
|
|
|
or null.
|
|
|
|
|
* in preconditions, may be left absent to ignore, set to null to require
|
|
|
|
|
no password, or set to a plaintext string.
|
|
|
|
|
* in updates, may be left absent to keep as-is, set to null to disable
|
|
|
|
|
session creation, or set to a plaintext string.
|
2023-01-05 13:11:28 -05:00
|
|
|
|
* `permissions`, a `Permissions` as described below.
|
2023-01-31 09:47:25 -05:00
|
|
|
|
* `preferences`, a JSON object which the server stores without interpreting.
|
|
|
|
|
This field is meant for user-level preferences meaningful to the UI.
|
|
|
|
|
* `username`
|
2023-01-05 13:11:28 -05:00
|
|
|
|
|
2022-12-31 12:08:26 -05:00
|
|
|
|
### Permissions
|
|
|
|
|
|
|
|
|
|
A JSON object of permissions to perform various actions:
|
|
|
|
|
|
|
|
|
|
* `adminUsers`: bool
|
|
|
|
|
* `readCameraConfigs`: bool, read camera configs including credentials
|
|
|
|
|
* `updateSignals`: bool
|
|
|
|
|
* `viewVideo`: bool
|
|
|
|
|
|
|
|
|
|
See endpoints above for more details on the contexts in which these are
|
|
|
|
|
required.
|
|
|
|
|
|
2023-01-05 13:11:28 -05:00
|
|
|
|
## Cross-site request forgery (CSRF) protection
|
|
|
|
|
|
|
|
|
|
The API includes several standard protections against [cross-site request
|
|
|
|
|
forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks, in
|
|
|
|
|
which a malicious third-party website convinces the user's browser to send
|
|
|
|
|
requests on its behalf.
|
|
|
|
|
|
|
|
|
|
The following protections apply regardless of method of authentication (session,
|
|
|
|
|
no authentication + `allowUnauthenticatedPermissions`, or the browser proxying
|
|
|
|
|
to a Unix socket with `ownUidIsPrivileged`):
|
|
|
|
|
|
|
|
|
|
* The `GET` method is always "safe". Actions that have significant side
|
|
|
|
|
effects require another method such as `DELETE`, `POST`, or `PUT`. This
|
|
|
|
|
prevents simple hyperlinks from causing damage.
|
|
|
|
|
* Mutations always require some non-default request header (e.g.
|
|
|
|
|
`Content-Type: application/json`) so that a `<form method="POST">` will be
|
|
|
|
|
rejected.
|
|
|
|
|
* The server does *not* override the default
|
|
|
|
|
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) policy.
|
|
|
|
|
Thus, cross-domain Ajax requests (via `XMLHTTPRequest` or `fetch`) should
|
|
|
|
|
fail.
|
2023-02-15 10:04:50 -05:00
|
|
|
|
* WebSocket upgrade requests are rejected if the `Origin` header is present
|
|
|
|
|
and does not match `Host`. This is the sole protection against
|
|
|
|
|
[Cross-Site WebSocket Hijacting (CSWSH)](https://christian-schneider.net/CrossSiteWebSocketHijacking.html).
|
2023-01-05 13:11:28 -05:00
|
|
|
|
|
|
|
|
|
The following additional protections apply only when using session
|
|
|
|
|
authentication:
|
|
|
|
|
|
|
|
|
|
* Session cookies are set with the [`SameSite=Lax` attribute](samesite-lax),
|
|
|
|
|
so that sufficiently modern web browsers will never send the session cookie
|
|
|
|
|
on subrequests. (Note they still send session cookies when following links
|
|
|
|
|
and on WebSocket upgrade. In these cases, we rely on the protections
|
|
|
|
|
described above.)
|
|
|
|
|
* Mutations use a `csrf` token, requiring the caller to prove it is able
|
|
|
|
|
to read the `GET /api/` response. (This is subect to change. We may decide
|
|
|
|
|
to implement these tokens in a way that doesn't require session
|
|
|
|
|
authentication or decide they're entirely unnecessary.)
|
|
|
|
|
|
2017-10-01 18:29:22 -04:00
|
|
|
|
[media-segment]: https://w3c.github.io/media-source/isobmff-byte-stream-format.html#iso-media-segments
|
|
|
|
|
[init-segment]: https://w3c.github.io/media-source/isobmff-byte-stream-format.html#iso-init-segments
|
|
|
|
|
[rfc-6381]: https://tools.ietf.org/html/rfc6381
|
2020-02-29 00:41:31 -05:00
|
|
|
|
[rfc-6455]: https://tools.ietf.org/html/rfc6455
|
|
|
|
|
[multipart-mixed-js]: https://github.com/scottlamb/multipart-mixed-js
|
2023-01-05 13:11:28 -05:00
|
|
|
|
[samesite-lax]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
|