@growth-labs/video
v0.3.0
Published
Astro 6 integration for HLS video playback from R2, reusable R2 HLS bundle ingestion, captions/chapters sidecars, YouTube embeds, and optional Worker-proxied premium gating.
Downloads
719
Readme
@growth-labs/video
Astro 6 integration for HLS video playback from R2, reusable R2 HLS bundle ingestion, captions/chapters sidecars, YouTube embeds, and optional Worker-proxied premium gating.
Install
pnpm add @growth-labs/video vidstackConfig
import video from '@growth-labs/video'
video({
publicDomain: 'media.example.com',
r2Binding: 'MEDIA_BUCKET',
admin: {
importRoute: {
enabled: true,
path: '/api/admin/video/import',
requireAuth: true,
},
},
})Premium playback additionally needs premium.enabled, an accessCheck, and a GL_VIDEO_SESSION_SECRET secret of at least 32 bytes:
video({
publicDomain: 'media.example.com',
premium: {
enabled: true,
sessionScope: 'per-video', // default; 'wildcard' opts into the 0.2.x behavior
sessionTtl: '1h', // duration string: '1h' | '30m' | '2h30m' | '45s'
},
accessCheck: async ({ videoId, request, env }, context) => {
// Returns { allowed, reason?, subjectId? }
},
})per-video scope (the 0.3.0 default) issues a cookie restricted to one videoId, so video status changes propagate within sessionTtl. wildcard issues a cookie that plays every premium video for the cookie's lifetime — cheaper per page but status changes do not propagate until the cookie expires.
R2 Layout
video/<videoId>/hls/master.m3u8
video/<videoId>/hls/<rendition>/index.m3u8
video/<videoId>/hls/<rendition>/init.mp4
video/<videoId>/hls/<rendition>/segment-00001.m4s
video/<videoId>/poster.jpg
video/<videoId>/captions_en.vtt
video/<videoId>/chapters.vttIngest Helpers
import { ingestHlsBundle, writeCaptionsVtt, writeChaptersVtt } from '@growth-labs/video/utils'
await ingestHlsBundle({
bucket: env.MEDIA_BUCKET,
publicDomain: 'media.example.com',
videoId: 'intro-video',
files: [
{ path: 'master.m3u8', body: masterManifest },
{ path: 'h264/720p/index.m3u8', body: renditionManifest },
{ path: 'h264/720p/init.mp4', body: initBytes },
{ path: 'h264/720p/segment-00001.m4s', body: segmentBytes },
],
poster: { path: 'poster.jpg', body: posterBytes, contentType: 'image/jpeg' },
})
await writeCaptionsVtt({ bucket: env.MEDIA_BUCKET, videoId: 'intro-video', vtt: captionsVtt })
await writeChaptersVtt({ bucket: env.MEDIA_BUCKET, videoId: 'intro-video', vtt: chaptersVtt })The ingestion helper validates master.m3u8, referenced rendition playlists, segment paths, and local HLS URI="..." attributes such as EXT-X-MAP, EXT-X-MEDIA, EXT-X-I-FRAME-STREAM-INF, and EXT-X-KEY before writing to R2. Absolute/external URLs and unsafe relative paths are rejected. Transcoding is out of scope; pass already-generated HLS and VTT files.
All R2 key helpers normalize videoId with the same safe contract: IDs must start with an ASCII letter or number and then contain only ASCII letters, numbers, _, or -. Text-track helpers write only under video/<videoId>/captions_<lang>.vtt and video/<videoId>/chapters.vtt.
Injected routes read bindings from cloudflare:workers env; no locals.runtime shim is required.
