Goal
Embed the same player used by the dashboard, keep credentials server-side, and connect captions and analytics to real API state.Prerequisites
- A ready native-video package and access to its asset ID.
- The shared Assets player package and its stylesheet installed in your application.
- For private videos, an application backend that authenticates viewers and authorizes each requested asset.
Workflow
1
Build/install the player and include its stylesheet.
2
Create a fresh playback grant on demand, add renewal, and propagate numeric HTTP status on callback failures.
3
Upload WebVTT as an ordinary Assets file, then attach its immutable version to the video version.
4
Enable the optional event callback and read authorized aggregate analytics.
Package installation and lifecycle
The shared package exports createAssetsPlayer for JavaScript and StackshiftAssetsPlayer from the /react entrypoint. HLS.js and icons are bundled; video processing and delivery use StackShift. To build from the repository, run the commands below and install the resulting local package in your application.Working browser callbacks for a public video
This example uses only an anonymous public-session endpoint and ephemeral playback credentials. Replace the asset ID with your actual public ready video. For private video, replace getSession with a request to your authenticated application backend; do not add a StackShift API key to this code.React and plain JavaScript embeds
Both examples import playbackOptions from the callback module above. The host must have a nonzero width. The player preserves the complete frame, including portrait footage, and adapts controls to its container. CSS is mandatory.Private-session backend contract
Your host endpoint must authenticate the viewer, resolve an asset from trusted application records, check that viewer’s access, and only then call stackshift.assets.video.createSession(assetId). Do not accept arbitrary browser asset IDs under an account-wide service credential. The SDK returns an unwrapped grant. If the browser callback expects response.data, return {success: true, data: grant}; otherwise return the grant and adjust the callback consistently. Set Cache-Control: private, no-store and prevent grant/query-string logging, including at proxies and error-reporting middleware. If you use cookie-based host authentication, apply your normal CSRF protection to the session endpoint. Renewal can use the ephemeral credential directly at /playback/sessions/renew. Revocation uses the authenticated management route and should be authorized by your host.Options and recovery behavior
- Required: getSession(). Optional: renewSession(grant), sendEvents(grant, events), onError(code), title, showTitle, poster, controls, autoplay, muted, loop, phase and phaseDetail.
- Defaults: controls enabled, user-initiated playback. Autoplay requests are muted. Responsive sizing follows the host; there is no separate responsive option in this interface.
- The browser bundle is dist/player.js with dist/style.css. Self-host both; its global API is StackshiftAssetsPlayer.createAssetsPlayer. Do not invent a hosted CDN package URL.
- Use native HLS when available, otherwise bundled HLS.js. Unsupported or irrecoverable media errors can fall back to MP4; 401/403 authorization failures must not trigger access-bypassing fallback.
- Renewal is scheduled one minute before expiry; without a renewal callback playback stops at expiry. Transient renewal failures retry only while the current grant remains valid. Preserve numeric error.status so expired/revoked states are distinguishable.
- Settings expose available quality choices and Auto separately from observed playback quality. Speeds are 0.5, 0.75, 1, 1.25, 1.5 and 2×; unsupported picture-in-picture controls are omitted.
- Keyboard: Tab to controls, arrows/Home/End in menus, Escape to dismiss and restore focus. On the player surface Space/K toggles playback, arrows seek five seconds, M mutes and F enters fullscreen. Native OS fullscreen may use different controls.
Caption ingestion and validation
Captions are not generated automatically. Upload an owned UTF-8 .vtt file through normal ingestion, then attach caption_asset_id and caption_version_id to the target video version. Both assets must belong to the same asset space and remain authorized. Attachment runs bounded WebVTT validation and a native ClamAV scan.- Maximum file size: 2 MiB. A WEBVTT header must be followed by a blank line. UTF-8 BOM and CRLF line endings are accepted; NUL bytes are rejected.
- Use 1–20,000 cues with nondecreasing start times and positive durations. Timestamps use mm:ss.mmm or hh:mm:ss.mmm within the validator’s 24-hour limit.
- NOTE blocks are allowed. STYLE and REGION blocks are rejected. Only the supported standard vertical/align/size/position/line cue settings are accepted; duplicate settings are invalid.
- language must parse as a language tag, such as en or fr. label is required and limited to 80 bytes by the service. kind is captions or subtitles.
- Cues are rendered through browser text tracks, not inserted as raw HTML.
Caption replacement, removal and authorization
To replace a track, upload a new caption asset/version and pass replace_caption_id with the old track ID in addCaption. The change is atomic and creates a new immutable track reference. Omitting replacement adds a track; setting is_default selects that default within the video version. removeCaption(assetId, versionId, captionId) detaches the active track. Neither replacement nor removal re-encodes the video. Fetch a fresh/renewed grant to refresh the player’s available caption list. Already loaded cue text cannot be recalled from a browser. Caption routes are /api/v1/assets/{assetID}/versions/{versionID}/video/captions, with the caption ID appended for DELETE. GET requires assets:read; POST/DELETE require assets:write. Caption delivery uses the same package/session authorization, not a public upload URL.Telemetry contract and interpretation
The optional sendEvents callback sends {events} to POST /playback/events with the playback credential in Authorization. Successful ingestion returns 202. Do not attach persistent viewer identifiers, API keys, grant URLs or arbitrary metadata.- Batch size: 1–64 events; body limit 16 KiB. Admission rejects a batch when existing events in the last minute plus its size exceed 120 for that session.
- Each event has sequence, kind and value. Sequence is an integer from 0 to 100000. Use monotonically increasing values and retain the same sequence numbers when retrying a failed batch.
- kind is startup, rebuffer, error, quality, watched or completed. value must be finite and nonnegative, at most 86400000; startup/rebuffer/watched duration values use milliseconds.
- Duplicate (session, sequence) events do not increment aggregates again. Events are session-scoped and revalidated against current authorization.
- GET /api/v1/assets/{assetID}/video/analytics or assets.video.analytics(assetId) requires assets:read. It returns starts, completions, errors, startup_ms, rebuffer_ms and watched_ms across the current UTC day and previous 29 days.
- Duration fields are summed milliseconds, not means or p95 values. When starts > 0, startup_ms / starts is the observed mean startup time; never label it a percentile. Zero starts is an empty-data state, not a measured zero-latency result.
- Raw events have seven-day retention and daily aggregates 30-day retention through maintenance. Client telemetry is not used for billing.
Go, Python and PHP: attach or replace an English caption track
First upload the UTF-8 WebVTT file through the native upload workflow and keep its asset ID and version ID. This function links those immutable bytes to the video version, then reloads the track list. Pass the existing track ID to replace it atomically; pass an empty string to create a new track. The old reference remains until replacement validation succeeds. Use the same pattern for other languages and subtitles by changing language, label and kind. Set is_default on the intended default track. Refresh the playback grant or reload the preview to obtain the updated caption URLs. No video re-encoding is required. For removal, call RemoveCaption(ctx, assetID, versionID, captionID), remove_caption(asset_id, version_id, caption_id), or removeCaption(versionId, $captionId). This removes the attachment from that video version; manage the uploaded caption asset separately through the library lifecycle.HTTP operations for this workflow
Management requests use Authorization: Bearer with your server-side Assets credential and X-Asset-Space-ID for the selected space. API-key scopes and space/collection permissions both apply. Successful JSON responses use {success: true, data: …}; the SDK methods return the unwrapped data. Paths shown outside /api/v1 use their dedicated short-lived credential.
For If-Match, quote the revision number, for example If-Match: “7”. On 412 assets.revision_conflict, reload the relevant resource and reconcile the edit before retrying. Use the revision of the resource being changed: asset, collaborator, schema, publication or gallery. A bucket-policy save instead places its current revision in the JSON body.
Expected result
The shared player obtains and renews short-lived grants, exposes the attached WebVTT tracks, and submits optional session-scoped events that can be read through authorized analytics.
Common failures
Related guides
Native video processing and secure playback
Version-pinned video packages, scan and review gates, replacement behavior, playback sessions, API routes, and failure recovery.
Operator reference: Assets environment
Every Assets environment variable, its preparation default, secret boundary, runtime destination, and activation prerequisite.
Operator reference: Assets deployment and rollback
Configure Assets services, build images, run deployment checks, and roll back processing admission without interrupting existing delivery.
Assets SDKs: JavaScript, Go, Python and PHP
Configure space-scoped clients and use native video, governed DAM, model rendering, galleries and CMS capabilities in all four SDKs.