@opensite/video
v0.3.5
Published
OpenSite optimal video component with progressive enhancement and conditional streaming.
Downloads
44
Readme
@opensite/video
High‑performance, native‑like HTML5 video for the OpenSite ecosystem. It accepts a numeric mediaId, fetches the optimal variants from the DashTrack CDN, and renders the best source for the viewer’s device and browser. It upgrades to adaptive streaming on demand to keep initial bundles tiny.
Key points
- Renders a plain
<video>element (no wrapper, no default styles). - Accepts all native
<video>props and ref; drop‑in replacement. - Chooses the best source: native HLS (Safari) → WebM → MP4.
- Optional streaming upgrade using
hls.js/dashjsvia dynamic import. - Poster behavior:
posterprop overrides,falsedisables, or defaults to CDNposter_url.
Custom controls
- Renders custom controls when
controlsprop is set; native<video controls>are always hidden. - Autohides controls on inactivity; shows a centered play button overlay when paused.
- Clicking the poster/video toggles play/pause.
Installation
Core (required):
npm i @opensite/videoStreaming libraries (optional; only needed for non‑Safari HLS/DASH playback):
npm i hls.js dashjsNotes
hls.jsanddashjsare dynamically imported at runtime only when needed.- The component works without these packages for progressive playback and for native HLS on Safari/iOS.
Quick Start
Minimal (progressive playback, tiny bundle):
import { Video } from '@opensite/video';
export function Hero({ mediaId }: { mediaId: number }) {
return (
<Video
mediaId={mediaId}
autoPlay
muted
loop
controls={false}
preload="metadata"
className="hero-video"
/>
);
}Upgrade to adaptive streaming only on user interaction (e.g., first play):
<Video mediaId={mediaId} controls streamingOnInteraction />Force adaptive streaming immediately (if available):
<Video mediaId={mediaId} controls adaptiveStreaming />Prefer a progressive codec/size (optional):
<Video mediaId={mediaId} preferCodec="WEBM" preferSize="md" />Poster behavior:
poster={"https://…"}→ use provided URL.poster={false}→ disable poster entirely.- Poster omitted → auto‑use
poster_urlfrom CDN payload if provided.
Props
Required
mediaId: number— DashTrack media record ID used to fetch the media snapshot.
Optional
cdnHost?: string— Override CDN origin. Default:https://edge.dashtrack.com.adaptiveStreaming?: boolean— Immediately attach HLS/DASH adapter when applicable.streamingOnInteraction?: boolean— Progressive first, upgrade on first play.streamingUpgrade?: boolean— Allow upgrades at all (default: true). Set false to force progressive.preferCodec?: 'HLS' | 'DASH' | 'WEBM' | 'MP4'— Preferred codec when multiple are present.preferSize?: 'sm' | 'md' | 'lg' | 'full'— Preferred progressive size variant.poster?: string | false— Poster override or disable; omitted defaults to CDNposter_url.onVideoData?: (data: VideoData) => void— Callback when CDN payload is loaded.All native
<video>attributes (e.g.,controls,muted,playsInline,style,className, etc.).
Direct src fallback
- You can omit
mediaIdand providesrcto play a direct progressive asset (MP4/WebM). In this mode, no CDN request is performed and streaming adapters are not attached.
Ref
- The component forwards a
refto the underlying<video>element for direct control and event subscriptions.
CDN Integration
- Default fetch URL:
https://edge.dashtrack.com/assets/videos/<mediaId>. - Override origin via
cdnHostprop; path shape is fixed by the module. - The response is cached at the module level to avoid redundant network calls.
Expected payload (subset)
type VideoData = {
id: number;
name?: string;
media_type?: string;
poster_url?: string | null;
meta?: {
duration?: number;
duration_iso8601?: string;
content_manifest?: {
optimized_filename?: string;
title?: string;
summary?: string;
description?: string;
};
};
variants_data: {
variants: {
HLS?: {
cdn_master_playlist_url?: string;
origin_master_playlist_url?: string;
full_size?: string; // chosen playlist URL (CDN or origin)
rungs?: { height?: number; width?: number; bandwidth?: number; average_bandwidth?: number; bitrate_kbps?: number; segment_count?: number; encode_seconds?: number }[];
};
DASH?: {
cdn_manifest_url?: string;
origin_manifest_url?: string;
full_size?: string; // chosen manifest URL (CDN or origin)
};
PROGRESSIVE_MP4?: Partial<Record<'sm'|'md'|'lg'|'full', string>>;
WEBM?: Partial<Record<'sm'|'md'|'lg'|'full', string>>;
};
metadata?: { width?: number; height?: number };
};
};Source Selection Logic
Order of preference (auto mode)
- Native HLS (Safari/iOS) via
application/vnd.apple.mpegurl. - Progressive WebM (modern browsers) by preferred/available size.
- Progressive MP4 (universal fallback) by preferred/available size.
- If only streaming manifests exist and not natively supported, attach
hls.jsordashjswhen allowed.
You can override with preferCodec and preferSize as hints; the component will still ensure the final result is playable on the current browser.
Streaming Options
adaptiveStreaming— Attach streaming adapter immediately if an HLS or DASH manifest exists and the browser needs an adapter.streamingOnInteraction— Keep progressive first, then upgrade on firstplayevent (recommended to minimize initial JS).streamingUpgrade={false}— Disable all upgrades and stick to progressive assets.
Adapters (dynamic imports)
- HLS (non‑Safari):
hls.jsviasrc/streaming/hls-adapter. - DASH:
dashjsviasrc/streaming/dash-adapter.
These imports happen only when required by the chosen streaming path.
Styling & Layout
- The component renders a custom control wrapper around a
<video>element. - The
classNameandstyleprops are applied to the wrapper container. - Tailwind classes are used for styling; no additional UI libraries required.
Accessibility & Metadata (videos)
aria-labelmaps tometa.content_manifest.summary(trimmed to ~120 chars).titlemaps tometa.content_manifest.titlewhen not provided.posterdefaults toposter_urlwhen not provided.preloaddefaults tometadata.width/heightdefault tovariants_data.metadata.{width,height}when not provided.controlsListautomatically includesnodownloadwhen rendering the download link to avoid the browser’s incorrect default file naming.
Download behavior
- A separate download link is rendered (by default) next to the
<video>element. - The link always points to a progressive asset (prefers WebM, then MP4) and uses a filename derived from
meta.content_manifest.optimized_filenameand the actual file extension. - This ensures correct file extensions and better analytics/SEO when users or crawlers download the asset.
- You can customize visibility/label/style with
showDownloadLink,downloadLabel, anddownloadClassNameprops.
Example
<Video mediaId={mediaId} className="w-full h-auto rounded-xl shadow" />SSR & Environments
- All network requests and capability detection happen in
useEffect, so nothing runs on the server. - On the server, the component renders a bare
<video>; sources are applied after mount. - If immediate poster visibility is required with potential network latency, pass
posterexplicitly so it shows before the CDN payload loads.
TypeScript
Types are exported from the root module:
import type { VideoData, PreferredCodec, PreferredSize } from '@opensite/video';Useful types
VideoData,Variants,VariantRungBrowserCapabilities,SelectedSourcePreferredCodec,PreferredSize
Tree‑Shakable Exports
Recommended
import { Video } from '@opensite/video';Sub‑paths (advanced)
@opensite/video/core— capability/source helpers@opensite/video/streaming/hls— HLS adapter (dynamic import normally handles this)@opensite/video/streaming/dash— DASH adapter (dynamic import normally handles this)
Package.json sets sideEffects: false and an exports map for optimal tree‑shaking.
Examples
Hero/Background (progressive only)
<Video mediaId={mediaId} autoPlay muted loop controls={false} preload="metadata" streamingUpgrade={false} />Interactive B‑Roll (upgrade on play)
<Video mediaId={mediaId} controls streamingOnInteraction />Short‑Form (full streaming)
<Video mediaId={mediaId} controls adaptiveStreaming />Custom poster and sizing
<Video mediaId={mediaId} poster="https://cdn.example.com/posters/123.jpg" style={{ aspectRatio: '16 / 9', width: '100%' }} />Using a ref
const ref = useRef<HTMLVideoElement>(null);
<Video mediaId={mediaId} controls ref={ref} />
// later
ref.current?.play();Accessibility (reduced motion)
const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
<Video mediaId={mediaId} autoPlay={!prefersReducedMotion} muted loop />Browser Support
- Safari/iOS: native HLS supported.
- Chrome/Edge/Firefox: progressive WebM/MP4; optional HLS/DASH via adapters.
- The component automatically chooses the best playable source.
Troubleshooting
- No video appears
- Check
mediaIdand network tab for the CDN request. - Verify your CDN returns at least one progressive asset or a streaming manifest.
- Check
- Streaming doesn’t start on non‑Safari
- Install
hls.jsand/ordashjs. - Ensure
adaptiveStreamingorstreamingOnInteractionis set.
- Install
- Poster not shown
- If
posteris omitted and the payload has noposter_url, no poster will be used.
- If
Architecture Notes
- Capability detection and source selection are in
src/core/. - Streaming adapters (HLS/DASH) live in
src/streaming/and are imported dynamically. - CDN integration is isolated in
src/utils/api.tswith lightweight caching. - The module follows the patterns outlined in
ECOSYSTEM_GUIDELINES.md.
Contributing
- Keep the component free of wrappers and default CSS.
- Preserve progressive‑first behavior; treat streaming as an upgrade.
- Maintain tree‑shakability and small initial bundle size.
- When extending functionality (e.g., custom controls), ensure features are optional and tree‑shakable.
License
Private module for the OpenSite ecosystem.
