Video
An item with a video becomes a video card. The field list is in Item fields; this page explains how to
fill it and what the host must allow. The wall plays only video that WebGL can read, which means the file must come with CORS
headers. YouTube and Vimeo cannot be read that way, so they open as an embed.
Formats
Section titled “Formats”video points at | video_type | Notes |
|---|---|---|
An .mp4 or .webm file | file | The simplest choice. H.264 mp4 plays everywhere. |
An .m3u8 playlist | hls | Adaptive streaming. |
An .mpd manifest | dash | Adaptive streaming. |
video_type is optional. When it is absent, a path ending in .m3u8 means hls, a path ending in .mpd means dash, and
anything else means file. An item with a video and no media_type is a video: the wall (and every adapter and plugin)
fills in media_type: "video".
{ "id": "clip-7", "link": "/clips/7", "title": "Harbour at dawn", "image": "https://cdn.example.com/clip-7/poster-512.jpg", "aspect": 1.7778, "video": "https://cdn.example.com/clip-7/master.m3u8", "duration": 94}duration is in seconds and is shown on the card. A negative or non-numeric duration is ignored.
Several qualities
Section titled “Several qualities”video_sources lists up to 8 alternative renditions. Use it for 480p, 720p and 1080p mp4 files, or for an AV1 and an H.264
version of the same stream. If video is absent, the first source becomes video.
"video_sources": [ { "src": "https://cdn.example.com/clip-7/480.mp4", "height": 480, "type": "file" }, { "src": "https://cdn.example.com/clip-7/1080.mp4", "height": 1080, "type": "file", "codecs": "avc1.640028", "bitrate": 5000000 }, { "src": "https://cdn.example.com/clip-7/av1.m3u8", "type": "hls", "codecs": "av01.0.08M.08" }]| Field | Meaning |
|---|---|
src | URL of the rendition. Required. Only http(s) and relative URLs are kept. |
type | file, hls or dash. |
height | Pixel height, an integer above 0. |
codecs | A codecs string such as avc1.640028. |
bitrate | Bits per second, an integer above 0. |
Entries beyond the eighth are dropped, and so is an entry without a valid src. The item stays.
360 and 180 degree video, and stereo
Section titled “360 and 180 degree video, and stereo”projection is flat (the default), equirect180 or equirect360. stereo is mono (the default), sbs (side by side) or
tb (top and bottom). List the projections you use in media.projections of the manifest.
YouTube and Vimeo
Section titled “YouTube and Vimeo”Put the page URL in video. The link is used only when the item has neither video nor media_type. The adapters and plugins turn these forms into an embed:
| Provider | Recognised forms |
|---|---|
| YouTube | youtube.com/watch?v=ID, m.youtube.com/watch?v=ID, youtu.be/ID, youtube.com/shorts/ID, youtube.com/embed/ID, youtube-nocookie.com/embed/ID |
| Vimeo | vimeo.com/ID, vimeo.com/ID/HASH, player.vimeo.com/video/ID with an optional ?h=HASH |
The result has no video, has media_type: "video", and has unplayable: { "provider": "YouTube", "embed": "https://www.youtube-nocookie.com/embed/ID" }.
The host name must match exactly: youtube.com.evil.example is not YouTube. A YouTube ID is 11 characters, a Vimeo ID is digits.
- A YouTube item with no
imagegetshttps://i.ytimg.com/vi/ID/hqdefault.jpgas its picture, at aspect 16/9. - A Vimeo item gets no invented picture (that would need Vimeo’s API). Without an
imageit is a text card when it has atitle, and is dropped when it has none. unplayable.embedis honoured only forhttps://www.youtube-nocookie.com/embed/ID,https://www.youtube.com/embed/ID(shown through the nocookie host) andhttps://player.vimeo.com/video/ID(theh=hash is kept). Any otherembedis removed.
Headers the host must send
Section titled “Headers the host must send”Access-Control-Allow-Origin: *(or the requesting origin) on the video file. For HLS and DASH it must be on every request: the playlist or manifest, each segment, and any key or init file. A playlist with CORS and segments without it will not play.Accept-Ranges: bytesfor mp4 and webm, so the player can seek.- List the host in
media.hostsof the manifest so the validator can check it (only hosts other than your own site). - Live DASH: send
Access-Control-Expose-Headers: Dateon the DASH manifest (or add aUTCTimingelement to it). The player reads the server’s clock from that header to find the live edge. Without it the stream still plays, but on the headset’s own clock, so the live position can be off by as much as that clock is.
Content Security Policy
Section titled “Content Security Policy”If the page that embeds the wall sets a Content Security Policy, it needs these on top of what the wall itself needs:
| Directive | Allow | Why |
|---|---|---|
media-src | the video hosts, and blob: for HLS and DASH | mp4 and webm play straight from their host. HLS and DASH play through Media Source Extensions: the video element’s source is a blob: URL. |
connect-src | the hosts of playlists, manifests and segments | hls.js and dash.js fetch them. |
script-src | the host of wall-hls.js and wall-dash.js (the same directory as wall.iife.js) | loaded only when an HLS or DASH video opens. |
worker-src | blob: (recommended) | hls.js converts the video segments in a background worker started from a blob: URL when it can. |
img-src | the poster hosts | posters are textures. |
frame-src | https://www.youtube-nocookie.com https://player.vimeo.com | the YouTube and Vimeo player outside VR. |
A strict example, with the wall’s files and videos on https://cdn.example:
script-src 'self' https://cdn.example; img-src 'self' https://cdn.example; media-src 'self' https://cdn.example blob:;connect-src 'self' https://cdn.example; worker-src 'self' blob:; frame-src https://www.youtube-nocookie.com https://player.vimeo.comTested with HLS: with media-src 'self' an mp4 plays and an HLS stream is refused; with media-src 'self' blob: the stream
plays. DASH works the same way but was not tested under a CSP.
Codecs and size on a headset
Section titled “Codecs and size on a headset”- H.264 (AVC) in mp4 plays on every device. Use it as the default.
- A Quest 3 should decode HEVC and AV1 up to 8K. We have not tested this on a real headset. A Quest 2 and Quest Pro should decode AV1 in software only, so offer an H.264 rendition next to it.
- Elsewhere, keep video at 2160p or below. Larger streams cost more than the card can show.
What the adapters read
Section titled “What the adapters read”| Source | Video fields it reads |
|---|---|
| JSON feed, well-known | All the fields above, checked as described. |
| RSS and Atom | A video enclosure, media:content with type="video/..." or medium="video" (also inside media:group; several give video_sources with height), media:thumbnail as the picture, media:content@duration or itunes:duration as duration. |
| DOM annotations | data-wall-video, data-wall-video-sources (a JSON array), data-wall-video-type, data-wall-duration. Without data-wall-video: a <video src> or its first <source src> inside the item, then og:video:secure_url or og:video when the page holds one item. |
| Recipes | video, video_type, video_sources (api recipes), duration, projection, stereo, media_type. |
An RSS or DOM entry that has a video but no picture is still a text card, without the video: give it a thumbnail to make it a video card.