MediaVod media.vod@1

Generated at from openvibe-contracts v0.419.0 and openvibe-sdk v0.161.0.

Version
1.0.0
Owner
media
Visibility
first-party
Status
active
Compatibility
backward
Schema
https://openvibe.network/contracts/media/vod.v1.json

media.vod@1: one recorded stream (VOD) as OpenVibe.Media's API answers it (server/vod/finalize.js vodPublic: GET /api/v1/:app/vods/:id, the list, and every read that returns a row). Its numeric id, its app-local user id, title and description, derived status (pending, recording, ready or failed), duration and size, the file basename, the URL its bytes play from (playback_url; hls_url is the master playlist when the object has one), thumbnail, storage provider, visibility, view counts, readiness and times. watch_id is the id openvibe.video addresses the watch page by (openvibe.video/watch?v=<watch_id>): 11 characters of URL-safe base64 from 8 random bytes, fixed for the row's life, never derived from the numeric id. More fields may be added; a consumer reads what it knows.

Fields

FieldTypeRequiredDescriptionConstraints
idintegeryesThe VOD's numeric id inside Media. Internal: a watch page is addressed by watch_id.
  • minimum 1
watch_idstringThe id of its watch page on openvibe.video (openvibe.video/watch?v=<watch_id>): 8 random bytes as URL-safe base64, 64 bits. One id space across VODs and clips. Optional: a row written before the ids existed may answer without one until Media's backfill fills it.
  • pattern ^[A-Za-z0-9_-]{11}$
app_idstringyesThe tenant that owns the recording (live, media-hub, games…).
stream_idinteger | null
managed_stream_idinteger | null
user_idstring | integer | nullThe owning app's local user id; it is not a Network subject id.
titlestringyes
  • maxLength 300
descriptionstring
statusenumyesDerived (db.vodStatus): recording while it is written, failed when its health quarantined it, ready once a file and a duration or size are there, pending before.
  • one of "pending", "recording", "ready", "failed", "unknown"
durationnumberyes
  • minimum 0
duration_secondsnumber
  • minimum 0
duration_sourcestring | nullprobe (ffprobe), remux (packet timeline) or null (stored 0).
file_sizeinteger
  • minimum 0
file_pathstring | nullThe file's basename only; a server path is never returned.
playback_urlstringyesThe URL its bytes play from on openvibe.media.
thumbnail_urlstring | null
preview_urlstring | nullThe hover preview of this item: about 6 s, muted, 320 px wide, MP4 H.264, stored beside its thumbnail and served at /t/<name>. Null until the preview.make job has made it (audio-only, private and too-short items never get one).
preview_webm_urlstring | nullThe same moment as preview_url, encoded as WebM (VP9, VP8 where the encoder has no VP9). Null while preview_url is.
storage_providerstringlocal, b2 or r2.
visibilityenumyespublic: anyone. unlisted: reached only by its exact id, never listed, noindex. private: missing to everyone but its owner and staff.
  • one of "public", "unlisted", "private"
is_publicbooleanyesTrue only for visibility public (kept beside it for older readers).
health_statusstring | null
clips_onlyboolean
is_recordingboolean
ai_overviewstring | null
ai_analyzed_atstring | null
view_countinteger
  • minimum 0
unique_viewsinteger
  • minimum 0
readinessobjectThe object's readiness levels (server/objects/readiness.js) when the read asked for them.
readiness.metadataboolean
readiness.bytes_verifiedboolean
readiness.playableboolean
readiness.reasonstring | null
hls_urlstringThe object's master playlist, when it has one (MEDIA_HLS_ENABLED).
hls_expires_atstring | integer
created_atstringyesUTC 'YYYY-MM-DD HH:MM:SS' as the store keeps it.

Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

Valid: public-recording-with-watch-id
{
  "id": 6203,
  "watch_id": "dQw4w9WgXcQ",
  "app_id": "live",
  "stream_id": 912,
  "managed_stream_id": 88,
  "user_id": 80,
  "title": "Fortnite, and the last ten minutes",
  "description": "",
  "status": "ready",
  "duration": 5400,
  "duration_seconds": 5400,
  "duration_source": "probe",
  "file_size": 734003200,
  "file_path": "vod-live-6203-1759910400000.webm",
  "playback_url": "https://openvibe.media/v/6203",
  "thumbnail_url": "https://openvibe.media/t/vod-6203.jpg",
  "storage_provider": "local",
  "visibility": "public",
  "is_public": true,
  "health_status": "ok",
  "clips_only": false,
  "is_recording": false,
  "ai_overview": null,
  "ai_analyzed_at": null,
  "view_count": 1321,
  "unique_views": 902,
  "created_at": "2026-11-06 21:00:00"
}
Valid: unlisted-before-the-backfill
{
  "id": 6204,
  "app_id": "live",
  "user_id": 80,
  "title": "An unlisted rehearsal",
  "description": "",
  "status": "ready",
  "duration": 300,
  "duration_seconds": 300,
  "duration_source": "probe",
  "file_size": 18874368,
  "file_path": "vod-live-6204-1759914000000.webm",
  "playback_url": "https://openvibe.media/v/6204",
  "thumbnail_url": null,
  "storage_provider": "b2",
  "visibility": "unlisted",
  "is_public": false,
  "clips_only": false,
  "is_recording": false,
  "view_count": 3,
  "unique_views": 3,
  "created_at": "2026-11-06 22:00:00"
}
Rejected: watch-id-invalid-character
{
  "id": 6205,
  "watch_id": "dQw4w9WgXc!",
  "app_id": "live",
  "title": "A VOD whose watch id has a character base64url never has",
  "status": "ready",
  "duration": 60,
  "visibility": "public",
  "is_public": true,
  "playback_url": "https://openvibe.media/v/6205",
  "created_at": "2026-11-06 23:00:00"
}
Rejected: watch-id-wrong-length
{
  "id": 6206,
  "watch_id": "too-short",
  "app_id": "live",
  "title": "A VOD with a 9-character watch id",
  "status": "ready",
  "duration": 60,
  "visibility": "public",
  "is_public": true,
  "playback_url": "https://openvibe.media/v/6206",
  "created_at": "2026-11-06 23:05:00"
}

Validate

const contracts = require('openvibe-contracts');
contracts.validate('media.vod@1', value);   // { valid, errors: [{ path, message }] }