mirror of https://github.com/calzoneman/sync.git
custom-media: import spec and fix a minor missed validation
This commit is contained in:
parent
04c9d48779
commit
92f0a956b9
|
@ -0,0 +1,160 @@
|
||||||
|
CyTube Custom Content Metadata
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Last Updated: 2017-08-08
|
||||||
|
|
||||||
|
Status: Experimental
|
||||||
|
|
||||||
|
## Purpose ##
|
||||||
|
|
||||||
|
CyTube currently supports adding custom audio/video content by allowing the user
|
||||||
|
to supply a direct URL to an audio/video file. The server uses `ffprobe` to
|
||||||
|
probe the file for various metadata, including the codec/container format and
|
||||||
|
the duration. This approach has a few disadvantages over the officially
|
||||||
|
supported media providers, namely:
|
||||||
|
|
||||||
|
* Since it accepts a single file, it is not possible to provide multiple
|
||||||
|
source URLs with varying formats or bitrates to allow viewers to select the
|
||||||
|
best source for their computer.
|
||||||
|
- It also means it is not possible to provide text tracks for subtitles or
|
||||||
|
closed captioning, or to provide image URLs for thumbnails/previews.
|
||||||
|
* Probing the file with `ffprobe` is slow, especially if the content is hosted
|
||||||
|
in a far away network location, which at best is inconvenient and at worst
|
||||||
|
results in timeouts and inability to add the content.
|
||||||
|
* Parsing the `ffprobe` output is inexact, and may sometimes result in
|
||||||
|
detecting the wrong format, or failing to detect the title.
|
||||||
|
|
||||||
|
This document specifies a new supported media provider which allows users to
|
||||||
|
provide a JSON manifest specifying the metadata for custom content in a way that
|
||||||
|
avoids the above issues and is more flexible for extension.
|
||||||
|
|
||||||
|
## Manifest Format ##
|
||||||
|
|
||||||
|
To add custom content, the user provides a JSON object with the following keys:
|
||||||
|
|
||||||
|
* `title`: A nonempty string specifying the title of the content. For legacy
|
||||||
|
reasons, CyTube currently truncates this to 100 UTF-8 characters.
|
||||||
|
* `duration`: A non-negative, finite number specifying the duration, in
|
||||||
|
seconds, of the content. This is what the server will use for timing
|
||||||
|
purposes. Decimals are allowed, but CyTube's timer truncates the value as
|
||||||
|
an integer number of seconds, so including fractional seconds lends no
|
||||||
|
advantage.
|
||||||
|
* `live`: An optional boolean (default: `false`) indicating whether the
|
||||||
|
content is live or pre-recorded. For live content, the `duration` is
|
||||||
|
ignored, and the server won't advance the playlist automatically.
|
||||||
|
* `thumbnail`: An optional string specifying a URL for a thumbnail image of
|
||||||
|
the content. CyTube currently does not support displaying thumbnails in the
|
||||||
|
playlist, but this functionality may be offered in the future.
|
||||||
|
* `sources`: A nonempty list of playable sources for the content. The format
|
||||||
|
is described below.
|
||||||
|
* `textTracks`: An optional list of text tracks for subtitles or closed
|
||||||
|
captioning. The format is described below.
|
||||||
|
|
||||||
|
### Source Format ###
|
||||||
|
|
||||||
|
Each source entry is a JSON object with the following keys:
|
||||||
|
|
||||||
|
* `url`: A valid URL that browsers can use to retrieve the content. The URL
|
||||||
|
must resolve to a publicly-routed IP address, and must the `https:` scheme.
|
||||||
|
* `contentType`: A string representing the MIME type of the content at `url`.
|
||||||
|
A list of acceptable MIME types is provided below.
|
||||||
|
* `quality`: A number representing the quality level of the source. The
|
||||||
|
supported quality levels are `240`, `360`, `480`, `540`, `720`, `1080`,
|
||||||
|
`1440`, and `2160`. This may be extended in the future.
|
||||||
|
* `bitrate`: An optional number indicating the bitrate (in Kbps) of the
|
||||||
|
content. It must be a positive, finite number if provided. The bitrate is
|
||||||
|
not currently used by CyTube, but may be used by extensions or custom
|
||||||
|
scripts to determine whether this source is feasible to play on the viewer's
|
||||||
|
internet connection.
|
||||||
|
|
||||||
|
#### Acceptable MIME Types ####
|
||||||
|
|
||||||
|
The following MIME types are accepted for the `contentType` field:
|
||||||
|
|
||||||
|
* `video/mp4`
|
||||||
|
* `video/webm`
|
||||||
|
* `video/ogg`
|
||||||
|
* `application/x-mpegURL` (HLS streams)
|
||||||
|
- HLS is only supported for livestreams. Metadata with HLS sources
|
||||||
|
but without `live: true` will be rejected.
|
||||||
|
* ~~`rtmp/flv`~~
|
||||||
|
- In light of Adobe phasing out support for Flash, and many browsers
|
||||||
|
already dropping support, RTMP is not supported by this feature.
|
||||||
|
RTMP streams are only supported through the existing `rt:` media
|
||||||
|
type.
|
||||||
|
* `audio/aac`
|
||||||
|
* `audio/ogg`
|
||||||
|
* `audio/mpeg`
|
||||||
|
|
||||||
|
Other audio or video formats, such as AVI, MKV, and FLAC, are not supported due
|
||||||
|
to lack of common support across browsers for playing these formats. For more
|
||||||
|
information, refer to
|
||||||
|
[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility).
|
||||||
|
|
||||||
|
### Text Track Format ###
|
||||||
|
|
||||||
|
Each text track entry is a JSON object with the following keys:
|
||||||
|
|
||||||
|
|
||||||
|
* `url`: A valid URL that browsers can use to retrieve the track. The URL
|
||||||
|
must resolve to a publicly-routed IP address, and must the `https:` scheme.
|
||||||
|
* `contentType`: A string representing the MIME type of the track at `url`.
|
||||||
|
The only currently supported MIME type is
|
||||||
|
[`text/vtt`](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API).
|
||||||
|
* `name`: A name for the text track. This is displayed in the menu for the
|
||||||
|
viewer to select a text track.
|
||||||
|
|
||||||
|
**Important note regarding text tracks and CORS:**
|
||||||
|
|
||||||
|
By default, browsers block requests for WebVTT tracks hosted on different
|
||||||
|
domains than the current page. In order for text tracks to work cross-origin,
|
||||||
|
the `Access-Control-Allow-Origin` header needs to be set by the remote server
|
||||||
|
when serving the VTT file. See
|
||||||
|
[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)
|
||||||
|
for more information about setting this header.
|
||||||
|
|
||||||
|
## Example ##
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "Test Video",
|
||||||
|
"duration": 10,
|
||||||
|
"live": false,
|
||||||
|
"thumbnail": "https://example.com/thumb.jpg",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://example.com/video.mp4",
|
||||||
|
"contentType": "video/mp4",
|
||||||
|
"quality": 1080,
|
||||||
|
"bitrate": 5000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textTracks": [
|
||||||
|
{
|
||||||
|
"url": "https://example.com/subtitles.vtt",
|
||||||
|
"contentType": "text/vtt",
|
||||||
|
"name": "English Subtitles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
## Permissions ##
|
||||||
|
|
||||||
|
The permission node to allow users to add custom content is the same as the
|
||||||
|
permission node for the existing raw file support. Custom content is considered
|
||||||
|
as an extension of the existing feature.
|
||||||
|
|
||||||
|
## Unsupported/Undefined Behavior ##
|
||||||
|
|
||||||
|
The behavior under any the following circumstances is not defined by this
|
||||||
|
specification, and any technical support in these cases is voided. This list is
|
||||||
|
non-exhaustive.
|
||||||
|
|
||||||
|
* Source URLs or text track URLs are hosted on a third-party website that does
|
||||||
|
not have knowledge of its content being played on CyTube.
|
||||||
|
* The webserver hosting the source or text track URLs serves a different MIME
|
||||||
|
type than the one specified in the manifest.
|
||||||
|
* The webserver hosting the source or text track URLs serves a file that does
|
||||||
|
not match the MIME type specified in the `Content-Type` HTTP header returned
|
||||||
|
to the browser.
|
||||||
|
* The manifest includes source URLs or text track URLs with expiration times,
|
||||||
|
session IDs, etc. in the URL querystring.
|
|
@ -29,6 +29,10 @@ const SOURCE_CONTENT_TYPES = new Set([
|
||||||
'video/webm'
|
'video/webm'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const LIVE_ONLY_CONTENT_TYPES = new Set([
|
||||||
|
'application/x-mpegURL'
|
||||||
|
]);
|
||||||
|
|
||||||
export function lookup(url, opts) {
|
export function lookup(url, opts) {
|
||||||
if (!opts) opts = {};
|
if (!opts) opts = {};
|
||||||
if (!opts.hasOwnProperty('timeout')) opts.timeout = 10000;
|
if (!opts.hasOwnProperty('timeout')) opts.timeout = 10000;
|
||||||
|
@ -158,11 +162,11 @@ export function validate(data) {
|
||||||
validateURL(data.thumbnail);
|
validateURL(data.thumbnail);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateSources(data.sources);
|
validateSources(data.sources, data);
|
||||||
validateTextTracks(data.textTracks);
|
validateTextTracks(data.textTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateSources(sources) {
|
function validateSources(sources, data) {
|
||||||
if (!Array.isArray(sources))
|
if (!Array.isArray(sources))
|
||||||
throw new ValidationError('sources must be a list');
|
throw new ValidationError('sources must be a list');
|
||||||
if (sources.length === 0)
|
if (sources.length === 0)
|
||||||
|
@ -178,6 +182,11 @@ function validateSources(sources) {
|
||||||
`unacceptable source contentType "${source.contentType}"`
|
`unacceptable source contentType "${source.contentType}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (LIVE_ONLY_CONTENT_TYPES.has(source.contentType) && !data.live)
|
||||||
|
throw new ValidationError(
|
||||||
|
`contentType "${source.contentType}" requires live: true`
|
||||||
|
);
|
||||||
|
|
||||||
if (!SOURCE_QUALITIES.has(source.quality))
|
if (!SOURCE_QUALITIES.has(source.quality))
|
||||||
throw new ValidationError(`unacceptable source quality "${source.quality}"`);
|
throw new ValidationError(`unacceptable source quality "${source.quality}"`);
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,16 @@ describe('custom-media', () => {
|
||||||
|
|
||||||
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
|
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects non-live HLS', () => {
|
||||||
|
invalid.live = false;
|
||||||
|
invalid.sources[0].contentType = 'application/x-mpegURL';
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => validate(invalid),
|
||||||
|
/contentType "application\/x-mpegURL" requires live: true/
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#validateSources', () => {
|
describe('#validateSources', () => {
|
||||||
|
|
Loading…
Reference in New Issue