Files
moreminimore-redesign/packages/cloudflare/src/media/stream.ts
Matt Kane 2e863566b3 Fix scope
2026-04-01 10:58:32 +01:00

119 lines
3.3 KiB
TypeScript

/**
* Cloudflare Stream Media Provider
*
* Provides integration with Cloudflare Stream for video hosting and streaming.
*
* Features:
* - Browse uploaded videos
* - Upload new videos (direct upload)
* - Delete videos
* - HLS/DASH streaming URLs
* - Thumbnail generation
*
* @see https://developers.cloudflare.com/stream/
*/
import type { MediaProviderDescriptor } from "emdash/media";
/**
* Cloudflare Stream configuration
*/
export interface CloudflareStreamConfig {
/**
* Cloudflare Account ID
* If not provided, reads from accountIdEnvVar at runtime
*/
accountId?: string;
/**
* Environment variable name containing the Account ID
* @default "CF_ACCOUNT_ID"
*/
accountIdEnvVar?: string;
/**
* API Token with Stream permissions
* If not provided, reads from apiTokenEnvVar at runtime
* Should have "Stream: Read" and "Stream: Edit" permissions
*/
apiToken?: string;
/**
* Environment variable name containing the API token
* @default "CF_STREAM_TOKEN"
*/
apiTokenEnvVar?: string;
/**
* Customer subdomain for Stream delivery (optional)
* If not provided, uses customer-{hash}.cloudflarestream.com format
*/
customerSubdomain?: string;
/**
* Default player controls setting
* @default true
*/
controls?: boolean;
/**
* Autoplay videos (muted by default to comply with browser policies)
* @default false
*/
autoplay?: boolean;
/**
* Loop videos
* @default false
*/
loop?: boolean;
/**
* Mute videos
* @default false (true if autoplay is enabled)
*/
muted?: boolean;
}
// Cloudflare Stream icon (inline SVG as data URL)
const STREAM_ICON = `data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><g clip-path="url(#a)"><path fill="#F63" d="M59.87 30.176a11.73 11.73 0 0 0-8-2.72 19.3 19.3 0 0 0-37-4.59 13.63 13.63 0 0 0-9.67 3.19 14.599 14.599 0 0 0-5.2 11 14.24 14.24 0 0 0 14.18 14.25h37.88a12 12 0 0 0 7.81-21.13Zm-7.81 17.13H14.19A10.24 10.24 0 0 1 4 37.086a10.58 10.58 0 0 1 3.77-8 9.55 9.55 0 0 1 6.23-2.25c.637 0 1.273.058 1.9.17l1.74.31.51-1.69A15.29 15.29 0 0 1 48 29.686l.1 2.32 2.26-.36a8.239 8.239 0 0 1 6.91 1.62 8.098 8.098 0 0 1 2.73 6.1 8 8 0 0 1-7.94 7.94Z"/><path fill="#F63" fill-rule="evenodd" d="m25.72 24.89 3.02-1.72 15.085 8.936.004 3.44-15.087 8.973L25.72 42.8V24.89Zm4 3.51v10.883l9.168-5.452L29.72 28.4Z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h64v64H0z"/></clipPath></defs></svg>')}`;
/**
* Cloudflare Stream media provider
*
* @example
* ```ts
* import { cloudflareStream } from "@emdash-cms/cloudflare";
*
* emdash({
* mediaProviders: [
* // Uses CF_ACCOUNT_ID and CF_STREAM_TOKEN env vars by default
* cloudflareStream({}),
*
* // Or with custom env var names
* cloudflareStream({
* accountIdEnvVar: "MY_CF_ACCOUNT",
* apiTokenEnvVar: "MY_CF_STREAM_KEY",
* }),
* ],
* })
* ```
*/
export function cloudflareStream(
config: CloudflareStreamConfig,
): MediaProviderDescriptor<CloudflareStreamConfig> {
return {
id: "cloudflare-stream",
name: "Cloudflare Stream",
icon: STREAM_ICON,
entrypoint: "@emdash-cms/cloudflare/media/stream-runtime",
capabilities: {
browse: true,
search: true,
upload: true,
delete: true,
},
config,
};
}