@openrails/contentkit-ui 0.90.3 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/http.d.ts +13 -8
- package/dist/client/http.js +16 -4
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/client.d.ts +3 -3
- package/dist/client/media/client.js +2 -2
- package/dist/client/media/client.js.map +1 -1
- package/dist/components/hover-preview.js +1 -1
- package/dist/components/hover-preview.js.map +1 -1
- package/dist/components/media-gallery.d.ts +2 -2
- package/dist/components/media-gallery.js +4 -4
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/video-mini-player.js +1 -1
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +3 -3
- package/dist/components/video-player.js +3 -3
- package/dist/components/video-player.js.map +1 -1
- package/dist/react/gallery.d.ts +6 -3
- package/dist/react/gallery.js +303 -228
- package/dist/react/gallery.js.map +1 -1
- package/dist/react/player.d.ts +4 -6
- package/dist/react/player.js +54 -61
- package/dist/react/player.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"xhrSetup\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, xhrSetup, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: () => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, xhrSetup: p.xhrSetup ?? client.media.xhrSetup, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n xhrSetup={ctx.xhrSetup}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {!tile && (\n <>\n <p className=\"text-sm font-medium text-balance\">{t(\"gallery.locked\")}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx }: { ctx: Ctx }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {t(\"gallery.locked\")}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} xhrSetup={ctx.xhrSetup} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, xhrSetup: ctx.xhrSetup, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"loader\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, loader, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: () => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, loader: p.loader ?? client.media.hls, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n loader={ctx.loader}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {!tile && (\n <>\n <p className=\"text-sm font-medium text-balance\">{t(\"gallery.locked\")}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx }: { ctx: Ctx }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {t(\"gallery.locked\")}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} loader={ctx.loader} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, loader: ctx.loader, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
|
@@ -121,7 +121,7 @@ function v({ handoff: v, onExpand: _e, onClose: y, onProgress: b, progressInterv
|
|
|
121
121
|
className: "pointer-events-none absolute inset-0 size-full object-contain"
|
|
122
122
|
}) : /* @__PURE__ */ h(l, {
|
|
123
123
|
vtt: `${v.base}sprite.vtt`,
|
|
124
|
-
|
|
124
|
+
loader: C.loader
|
|
125
125
|
})),
|
|
126
126
|
/* @__PURE__ */ h("div", {
|
|
127
127
|
"aria-hidden": !0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-mini-player.js","names":[],"sources":["../../src/components/video-mini-player.tsx"],"sourcesContent":["import { Cancel01Icon, GoBackward10SecIcon, GoForward10SecIcon, SquareArrowExpand01Icon, PauseIcon, PlayIcon, VolumeHighIcon, VolumeOffIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties, type MouseEvent } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { JUMP_SEEK, PLAYER_VOLUME_KEY, playerKeyAction, writeVolume, type PlayerAction } from \"../client/player.js\";\nimport { publicRenditions } from \"../client/public.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useActiveCues, useMediaState, useProgressReports, type PlaybackProgress } from \"../react/player.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { Captions, ControlButton, SeekBar, Spinner, clock } from \"./player-controls.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport { SpriteFrame, keyAllowed, type PlayerHandoff } from \"./video-player.js\";\n\nconst CONTROLS_IDLE_MS = 2500;\n\nexport interface VideoMiniPlayerProps extends Omit<HlsPlayerOptions, \"src\" | \"startAt\" | \"autoPlay\" | \"audioLanguage\" | \"subtitleLanguage\" | \"active\"> {\n /** What VideoPlayer's onMiniPlayer handed over. */\n handoff: PlayerHandoff;\n /** Back to the full player, where this one is. */\n onExpand: (state: { time: number; playing: boolean }) => void;\n onClose: (state: { time: number }) => void;\n onProgress?: (progress: PlaybackProgress) => void;\n /** Milliseconds. Default 10000. */\n progressInterval?: number;\n /** K, J, L and M page-wide (default), every shortcut while focus is in it, or none. */\n keyboard?: \"global\" | \"focus\" | false;\n /** The video's title. */\n label?: string;\n position?: \"bottom-right\" | \"bottom-left\";\n crossOrigin?: \"anonymous\" | \"use-credentials\";\n className?: string;\n style?: CSSProperties;\n appearance?: ContentKitUiAppearance;\n}\n\n/**\n * The video docked in a corner while the viewer browses: play, ±10 s, mute,\n * a seek line, expand back to the full player and close. It starts where\n * the handoff stopped, playing if it was.\n */\nexport function VideoMiniPlayer({\n handoff,\n onExpand,\n onClose,\n onProgress,\n progressInterval = 10_000,\n keyboard = \"global\",\n label,\n position = \"bottom-right\",\n crossOrigin,\n className,\n style,\n appearance,\n ...options\n}: VideoMiniPlayerProps) {\n const { t } = useMessages();\n const src = `${handoff.base}master.m3u8`;\n const player = useHlsPlayer({\n ...options,\n src,\n startAt: handoff.time,\n autoPlay: handoff.playing,\n audioLanguage: handoff.audioLanguage,\n subtitleLanguage: handoff.subtitleLanguage,\n // The handoff carries the viewer's choices; a few hundred pixels need no manual rung.\n trackKey: null,\n qualityKey: null,\n });\n const { status, error, started } = player;\n const [video, setVideo] = useState<HTMLVideoElement | null>(null);\n const attach = player.ref;\n const videoRef = useCallback(\n (el: HTMLVideoElement | null) => {\n attach(el);\n setVideo(el);\n },\n [attach],\n );\n const media = useMediaState(video);\n const cues = useActiveCues(video);\n useProgressReports(video, { enabled: started, source: started ? src : null, interval: progressInterval, onProgress });\n\n const [poked, setPoked] = useState(true);\n const idle = useRef<ReturnType<typeof setTimeout>>(undefined);\n const poke = useCallback(() => {\n setPoked(true);\n clearTimeout(idle.current);\n idle.current = setTimeout(() => setPoked(false), CONTROLS_IDLE_MS);\n }, []);\n const hide = useCallback(() => {\n clearTimeout(idle.current);\n setPoked(false);\n }, []);\n useEffect(() => () => clearTimeout(idle.current), []);\n const [scrubbing, setScrubbing] = useState(false);\n const playing = !media.paused && !media.ended;\n const shown = poked || !playing || scrubbing || !!error;\n\n const done = useRef(false);\n const time = () => video?.currentTime || media.time || handoff.time;\n const expand = () => {\n if (done.current) return;\n done.current = true;\n onExpand({ time: time(), playing });\n };\n const close = () => {\n if (done.current) return;\n done.current = true;\n video?.pause();\n onClose({ time: time() });\n };\n const toggle = () => {\n if (!video) return;\n if (!started || video.paused || video.ended) player.play();\n else video.pause();\n };\n const dur = media.duration || handoff.duration || 0;\n const seek = (to: number) => {\n if (video) video.currentTime = Math.min(dur || to, Math.max(0, to));\n };\n const volumeKey = options.volumeKey === undefined ? PLAYER_VOLUME_KEY : options.volumeKey;\n const toggleMute = () => {\n if (!video) return;\n video.muted = !video.muted;\n if (!video.muted && video.volume === 0) video.volume = 1;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n };\n\n const run = (a: PlayerAction, key: string): boolean => {\n if (!video) return false;\n // Page-wide, only K, J, L and M: Space and the arrows scroll the page.\n if (keyboard === \"global\" && !\"kjlmKJLM\".includes(key)) return false;\n switch (a.type) {\n case \"toggle\":\n toggle();\n break;\n case \"seekBy\":\n if (!started) return false;\n seek(video.currentTime + a.seconds);\n break;\n case \"mute\":\n toggleMute();\n break;\n default:\n return false;\n }\n poke();\n return true;\n };\n const runRef = useRef(run);\n runRef.current = run;\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!keyboard || !root) return;\n const on: EventTarget = keyboard === \"global\" ? document : root;\n const down = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.defaultPrevented) return;\n const action = playerKeyAction(k);\n if (!action || !keyAllowed(k.target, k.key, root) || !runRef.current(action, k.key)) return;\n k.preventDefault();\n };\n on.addEventListener(\"keydown\", down);\n return () => on.removeEventListener(\"keydown\", down);\n }, [keyboard, root]);\n\n // Touch has no hover: a tap shows or hides the controls. A click plays and pauses.\n const pointerType = useRef(\"mouse\");\n const onSurfaceClick = (e: MouseEvent) => {\n e.preventDefault();\n if (pointerType.current === \"mouse\") {\n toggle();\n poke();\n } else if (shown && playing) hide();\n else poke();\n };\n\n const aspect = handoff.width && handoff.height ? handoff.width / handoff.height : (media.aspect ?? 16 / 9);\n const posterOutputs = typeof handoff.poster === \"string\" ? [] : publicRenditions(handoff.poster);\n const busy = !error && (status === \"loading\" || status === \"buffering\");\n const overlay = \"rounded-full text-white hover:bg-white/15\";\n return (\n <ContentKitUiRoot\n ref={setRoot}\n appearance={appearance}\n role=\"region\"\n aria-label={label ? `${t(\"player.miniPlayer\")}: ${label}` : t(\"player.miniPlayer\")}\n data-ckui=\"mini-player\"\n data-status={status}\n className={cn(\n \"@container fixed bottom-4 z-50 touch-manipulation overflow-hidden rounded-md bg-black text-white shadow-lg select-none\",\n position === \"bottom-left\" ? \"left-4\" : \"right-4\",\n className,\n )}\n style={{ aspectRatio: String(aspect), width: `min(24rem, calc(100vw - 2rem), calc(60svh * ${aspect}))`, ...style }}\n onPointerDown={(e) => (pointerType.current = e.pointerType || \"mouse\")}\n onPointerMove={(e) => e.pointerType === \"mouse\" && poke()}\n onPointerLeave={(e) => e.pointerType === \"mouse\" && !scrubbing && playing && hide()}\n >\n <video ref={videoRef} className=\"absolute inset-0 size-full object-contain\" playsInline preload=\"none\" crossOrigin={crossOrigin} aria-label={label} />\n {!started &&\n (typeof handoff.poster === \"string\" ? (\n <img alt=\"\" src={handoff.poster} className=\"pointer-events-none absolute inset-0 size-full object-contain\" />\n ) : posterOutputs.length ? (\n <RenditionImg outputs={posterOutputs} className=\"pointer-events-none absolute inset-0 size-full object-contain\" />\n ) : (\n <SpriteFrame vtt={`${handoff.base}sprite.vtt`} xhrSetup={options.xhrSetup} />\n ))}\n <div aria-hidden data-ckui=\"surface\" className=\"absolute inset-0\" onClick={onSurfaceClick} />\n <Captions cues={started ? cues : []} bottom={shown ? \"2.75rem\" : \"0.75rem\"} />\n <div\n data-ckui=\"controls\"\n className={cn(\n \"pointer-events-none absolute inset-0 bg-black/45 transition-opacity duration-200 focus-within:opacity-100 motion-reduce:transition-none\",\n shown ? \"opacity-100 [&_button]:pointer-events-auto\" : \"opacity-0\",\n )}\n >\n <div className=\"absolute inset-x-0 top-0 flex items-center justify-between p-1.5\">\n <ControlButton label={t(\"player.expand\")} icon={SquareArrowExpand01Icon} onClick={expand} className={overlay} data-ckui=\"mini-expand\" />\n <ControlButton label={t(\"player.close\")} icon={Cancel01Icon} onClick={close} className={overlay} data-ckui=\"mini-close\" />\n </div>\n <div className=\"absolute inset-0 m-auto flex h-fit w-fit items-center gap-3 @sm:gap-5\">\n <ControlButton\n label={t(\"player.seekBack\", { seconds: JUMP_SEEK })}\n icon={GoBackward10SecIcon}\n disabled={!started}\n onClick={() => {\n seek(time() - JUMP_SEEK);\n poke();\n }}\n className={cn(overlay, \"[&_svg]:size-6\")}\n />\n <button\n type=\"button\"\n aria-label={playing ? t(\"player.pause\") : t(\"player.resume\")}\n title={playing ? t(\"player.pause\") : t(\"player.resume\")}\n onClick={() => {\n toggle();\n poke();\n }}\n className={cn(\"flex size-12 cursor-pointer items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-white/80\", overlay)}\n data-ckui=\"mini-play\"\n >\n {busy && started ? <Spinner className=\"size-7\" /> : <HugeiconsIcon icon={playing ? PauseIcon : PlayIcon} className=\"size-7 fill-current\" strokeWidth={1.5} aria-hidden />}\n </button>\n <ControlButton\n label={t(\"player.seekForward\", { seconds: JUMP_SEEK })}\n icon={GoForward10SecIcon}\n disabled={!started}\n onClick={() => {\n seek(time() + JUMP_SEEK);\n poke();\n }}\n className={cn(overlay, \"[&_svg]:size-6\")}\n />\n </div>\n <div className=\"absolute inset-x-0 bottom-3 flex items-center justify-between px-2.5\">\n <span className=\"text-xs font-medium tabular-nums\">{dur > 0 ? `${clock(media.time || handoff.time)} / ${clock(dur)}` : clock(media.time || handoff.time)}</span>\n <ControlButton label={media.muted ? t(\"player.unmute\") : t(\"player.mute\")} icon={media.muted ? VolumeOffIcon : VolumeHighIcon} onClick={toggleMute} className={cn(overlay, \"size-8\")} />\n </div>\n </div>\n <div className=\"absolute inset-x-0 bottom-0 z-10\">\n <SeekBar thin time={media.time || handoff.time} duration={dur} buffered={media.buffered} onSeek={seek} onScrub={(v) => setScrubbing(v !== null)} />\n </div>\n {error && (\n <div role=\"alert\" className=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-black/80 p-3 text-center text-xs\">\n <p>{t(`player.errors.${error.kind}`)}</p>\n <button type=\"button\" onClick={player.retry} className=\"rounded-md bg-white/15 px-2 py-1 font-medium hover:bg-white/25\">\n {t(\"player.retry\")}\n </button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"video-mini-player.js","names":[],"sources":["../../src/components/video-mini-player.tsx"],"sourcesContent":["import { Cancel01Icon, GoBackward10SecIcon, GoForward10SecIcon, SquareArrowExpand01Icon, PauseIcon, PlayIcon, VolumeHighIcon, VolumeOffIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties, type MouseEvent } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { JUMP_SEEK, PLAYER_VOLUME_KEY, playerKeyAction, writeVolume, type PlayerAction } from \"../client/player.js\";\nimport { publicRenditions } from \"../client/public.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useActiveCues, useMediaState, useProgressReports, type PlaybackProgress } from \"../react/player.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { Captions, ControlButton, SeekBar, Spinner, clock } from \"./player-controls.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport { SpriteFrame, keyAllowed, type PlayerHandoff } from \"./video-player.js\";\n\nconst CONTROLS_IDLE_MS = 2500;\n\nexport interface VideoMiniPlayerProps extends Omit<HlsPlayerOptions, \"src\" | \"startAt\" | \"autoPlay\" | \"audioLanguage\" | \"subtitleLanguage\" | \"active\"> {\n /** What VideoPlayer's onMiniPlayer handed over. */\n handoff: PlayerHandoff;\n /** Back to the full player, where this one is. */\n onExpand: (state: { time: number; playing: boolean }) => void;\n onClose: (state: { time: number }) => void;\n onProgress?: (progress: PlaybackProgress) => void;\n /** Milliseconds. Default 10000. */\n progressInterval?: number;\n /** K, J, L and M page-wide (default), every shortcut while focus is in it, or none. */\n keyboard?: \"global\" | \"focus\" | false;\n /** The video's title. */\n label?: string;\n position?: \"bottom-right\" | \"bottom-left\";\n crossOrigin?: \"anonymous\" | \"use-credentials\";\n className?: string;\n style?: CSSProperties;\n appearance?: ContentKitUiAppearance;\n}\n\n/**\n * The video docked in a corner while the viewer browses: play, ±10 s, mute,\n * a seek line, expand back to the full player and close. It starts where\n * the handoff stopped, playing if it was.\n */\nexport function VideoMiniPlayer({\n handoff,\n onExpand,\n onClose,\n onProgress,\n progressInterval = 10_000,\n keyboard = \"global\",\n label,\n position = \"bottom-right\",\n crossOrigin,\n className,\n style,\n appearance,\n ...options\n}: VideoMiniPlayerProps) {\n const { t } = useMessages();\n const src = `${handoff.base}master.m3u8`;\n const player = useHlsPlayer({\n ...options,\n src,\n startAt: handoff.time,\n autoPlay: handoff.playing,\n audioLanguage: handoff.audioLanguage,\n subtitleLanguage: handoff.subtitleLanguage,\n // The handoff carries the viewer's choices; a few hundred pixels need no manual rung.\n trackKey: null,\n qualityKey: null,\n });\n const { status, error, started } = player;\n const [video, setVideo] = useState<HTMLVideoElement | null>(null);\n const attach = player.ref;\n const videoRef = useCallback(\n (el: HTMLVideoElement | null) => {\n attach(el);\n setVideo(el);\n },\n [attach],\n );\n const media = useMediaState(video);\n const cues = useActiveCues(video);\n useProgressReports(video, { enabled: started, source: started ? src : null, interval: progressInterval, onProgress });\n\n const [poked, setPoked] = useState(true);\n const idle = useRef<ReturnType<typeof setTimeout>>(undefined);\n const poke = useCallback(() => {\n setPoked(true);\n clearTimeout(idle.current);\n idle.current = setTimeout(() => setPoked(false), CONTROLS_IDLE_MS);\n }, []);\n const hide = useCallback(() => {\n clearTimeout(idle.current);\n setPoked(false);\n }, []);\n useEffect(() => () => clearTimeout(idle.current), []);\n const [scrubbing, setScrubbing] = useState(false);\n const playing = !media.paused && !media.ended;\n const shown = poked || !playing || scrubbing || !!error;\n\n const done = useRef(false);\n const time = () => video?.currentTime || media.time || handoff.time;\n const expand = () => {\n if (done.current) return;\n done.current = true;\n onExpand({ time: time(), playing });\n };\n const close = () => {\n if (done.current) return;\n done.current = true;\n video?.pause();\n onClose({ time: time() });\n };\n const toggle = () => {\n if (!video) return;\n if (!started || video.paused || video.ended) player.play();\n else video.pause();\n };\n const dur = media.duration || handoff.duration || 0;\n const seek = (to: number) => {\n if (video) video.currentTime = Math.min(dur || to, Math.max(0, to));\n };\n const volumeKey = options.volumeKey === undefined ? PLAYER_VOLUME_KEY : options.volumeKey;\n const toggleMute = () => {\n if (!video) return;\n video.muted = !video.muted;\n if (!video.muted && video.volume === 0) video.volume = 1;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n };\n\n const run = (a: PlayerAction, key: string): boolean => {\n if (!video) return false;\n // Page-wide, only K, J, L and M: Space and the arrows scroll the page.\n if (keyboard === \"global\" && !\"kjlmKJLM\".includes(key)) return false;\n switch (a.type) {\n case \"toggle\":\n toggle();\n break;\n case \"seekBy\":\n if (!started) return false;\n seek(video.currentTime + a.seconds);\n break;\n case \"mute\":\n toggleMute();\n break;\n default:\n return false;\n }\n poke();\n return true;\n };\n const runRef = useRef(run);\n runRef.current = run;\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!keyboard || !root) return;\n const on: EventTarget = keyboard === \"global\" ? document : root;\n const down = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.defaultPrevented) return;\n const action = playerKeyAction(k);\n if (!action || !keyAllowed(k.target, k.key, root) || !runRef.current(action, k.key)) return;\n k.preventDefault();\n };\n on.addEventListener(\"keydown\", down);\n return () => on.removeEventListener(\"keydown\", down);\n }, [keyboard, root]);\n\n // Touch has no hover: a tap shows or hides the controls. A click plays and pauses.\n const pointerType = useRef(\"mouse\");\n const onSurfaceClick = (e: MouseEvent) => {\n e.preventDefault();\n if (pointerType.current === \"mouse\") {\n toggle();\n poke();\n } else if (shown && playing) hide();\n else poke();\n };\n\n const aspect = handoff.width && handoff.height ? handoff.width / handoff.height : (media.aspect ?? 16 / 9);\n const posterOutputs = typeof handoff.poster === \"string\" ? [] : publicRenditions(handoff.poster);\n const busy = !error && (status === \"loading\" || status === \"buffering\");\n const overlay = \"rounded-full text-white hover:bg-white/15\";\n return (\n <ContentKitUiRoot\n ref={setRoot}\n appearance={appearance}\n role=\"region\"\n aria-label={label ? `${t(\"player.miniPlayer\")}: ${label}` : t(\"player.miniPlayer\")}\n data-ckui=\"mini-player\"\n data-status={status}\n className={cn(\n \"@container fixed bottom-4 z-50 touch-manipulation overflow-hidden rounded-md bg-black text-white shadow-lg select-none\",\n position === \"bottom-left\" ? \"left-4\" : \"right-4\",\n className,\n )}\n style={{ aspectRatio: String(aspect), width: `min(24rem, calc(100vw - 2rem), calc(60svh * ${aspect}))`, ...style }}\n onPointerDown={(e) => (pointerType.current = e.pointerType || \"mouse\")}\n onPointerMove={(e) => e.pointerType === \"mouse\" && poke()}\n onPointerLeave={(e) => e.pointerType === \"mouse\" && !scrubbing && playing && hide()}\n >\n <video ref={videoRef} className=\"absolute inset-0 size-full object-contain\" playsInline preload=\"none\" crossOrigin={crossOrigin} aria-label={label} />\n {!started &&\n (typeof handoff.poster === \"string\" ? (\n <img alt=\"\" src={handoff.poster} className=\"pointer-events-none absolute inset-0 size-full object-contain\" />\n ) : posterOutputs.length ? (\n <RenditionImg outputs={posterOutputs} className=\"pointer-events-none absolute inset-0 size-full object-contain\" />\n ) : (\n <SpriteFrame vtt={`${handoff.base}sprite.vtt`} loader={options.loader} />\n ))}\n <div aria-hidden data-ckui=\"surface\" className=\"absolute inset-0\" onClick={onSurfaceClick} />\n <Captions cues={started ? cues : []} bottom={shown ? \"2.75rem\" : \"0.75rem\"} />\n <div\n data-ckui=\"controls\"\n className={cn(\n \"pointer-events-none absolute inset-0 bg-black/45 transition-opacity duration-200 focus-within:opacity-100 motion-reduce:transition-none\",\n shown ? \"opacity-100 [&_button]:pointer-events-auto\" : \"opacity-0\",\n )}\n >\n <div className=\"absolute inset-x-0 top-0 flex items-center justify-between p-1.5\">\n <ControlButton label={t(\"player.expand\")} icon={SquareArrowExpand01Icon} onClick={expand} className={overlay} data-ckui=\"mini-expand\" />\n <ControlButton label={t(\"player.close\")} icon={Cancel01Icon} onClick={close} className={overlay} data-ckui=\"mini-close\" />\n </div>\n <div className=\"absolute inset-0 m-auto flex h-fit w-fit items-center gap-3 @sm:gap-5\">\n <ControlButton\n label={t(\"player.seekBack\", { seconds: JUMP_SEEK })}\n icon={GoBackward10SecIcon}\n disabled={!started}\n onClick={() => {\n seek(time() - JUMP_SEEK);\n poke();\n }}\n className={cn(overlay, \"[&_svg]:size-6\")}\n />\n <button\n type=\"button\"\n aria-label={playing ? t(\"player.pause\") : t(\"player.resume\")}\n title={playing ? t(\"player.pause\") : t(\"player.resume\")}\n onClick={() => {\n toggle();\n poke();\n }}\n className={cn(\"flex size-12 cursor-pointer items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-white/80\", overlay)}\n data-ckui=\"mini-play\"\n >\n {busy && started ? <Spinner className=\"size-7\" /> : <HugeiconsIcon icon={playing ? PauseIcon : PlayIcon} className=\"size-7 fill-current\" strokeWidth={1.5} aria-hidden />}\n </button>\n <ControlButton\n label={t(\"player.seekForward\", { seconds: JUMP_SEEK })}\n icon={GoForward10SecIcon}\n disabled={!started}\n onClick={() => {\n seek(time() + JUMP_SEEK);\n poke();\n }}\n className={cn(overlay, \"[&_svg]:size-6\")}\n />\n </div>\n <div className=\"absolute inset-x-0 bottom-3 flex items-center justify-between px-2.5\">\n <span className=\"text-xs font-medium tabular-nums\">{dur > 0 ? `${clock(media.time || handoff.time)} / ${clock(dur)}` : clock(media.time || handoff.time)}</span>\n <ControlButton label={media.muted ? t(\"player.unmute\") : t(\"player.mute\")} icon={media.muted ? VolumeOffIcon : VolumeHighIcon} onClick={toggleMute} className={cn(overlay, \"size-8\")} />\n </div>\n </div>\n <div className=\"absolute inset-x-0 bottom-0 z-10\">\n <SeekBar thin time={media.time || handoff.time} duration={dur} buffered={media.buffered} onSeek={seek} onScrub={(v) => setScrubbing(v !== null)} />\n </div>\n {error && (\n <div role=\"alert\" className=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-black/80 p-3 text-center text-xs\">\n <p>{t(`player.errors.${error.kind}`)}</p>\n <button type=\"button\" onClick={player.retry} className=\"rounded-md bg-white/15 px-2 py-1 font-medium hover:bg-white/25\">\n {t(\"player.retry\")}\n </button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,11 +6,11 @@ import { HlsPlayerOptions } from '../react/gallery.js';
|
|
|
6
6
|
import { PlaybackProgress } from '../react/player.js';
|
|
7
7
|
import { PublicPreset } from '../client/public.js';
|
|
8
8
|
import { EncodeProgress as Progress } from '../client/generated/wire.js';
|
|
9
|
-
type
|
|
9
|
+
type Loader = HlsPlayerOptions["loader"];
|
|
10
10
|
/** The first frame of a video's seek sprite (sprite.vtt), fitted like object-fit. */
|
|
11
|
-
export declare function SpriteFrame({ vtt,
|
|
11
|
+
export declare function SpriteFrame({ vtt, loader, fit, className }: {
|
|
12
12
|
vtt: string;
|
|
13
|
-
|
|
13
|
+
loader?: Loader;
|
|
14
14
|
fit?: "contain" | "cover";
|
|
15
15
|
className?: string;
|
|
16
16
|
}): import('react').JSX.Element | null;
|
|
@@ -16,7 +16,7 @@ import { cn as m } from "cn";
|
|
|
16
16
|
import { AlertCircleIcon as h, ArrowShrink02Icon as Ce, ClosedCaptionIcon as we, FullScreenIcon as Te, PauseIcon as Ee, PictureInPicture01Icon as De, PictureInPictureExitIcon as Oe, PictureInPictureOnIcon as ke, PlayIcon as Ae, RectangularIcon as je, Refresh01Icon as Me, Settings01Icon as Ne } from "@hugeicons/core-free-icons";
|
|
17
17
|
import { HugeiconsIcon as g } from "@hugeicons/react";
|
|
18
18
|
//#region src/components/video-player.tsx
|
|
19
|
-
function Pe({ vtt: e,
|
|
19
|
+
function Pe({ vtt: e, loader: t, fit: n = "contain", className: r }) {
|
|
20
20
|
let i = me(e, t), a = i?.cues[0];
|
|
21
21
|
return !i || !a ? null : /* @__PURE__ */ f("svg", {
|
|
22
22
|
"aria-hidden": !0,
|
|
@@ -56,7 +56,7 @@ function b({ base: o, poster: h, width: _, height: v, duration: y, pending: b, p
|
|
|
56
56
|
start: () => j.preview(We(Xe, y)),
|
|
57
57
|
stop: j.unload
|
|
58
58
|
});
|
|
59
|
-
let V = de(B), H = ue(z, B), U = fe(B), mt = le(B), ht = me(P && o ? `${o}sprite.vtt` : null, D.
|
|
59
|
+
let V = de(B), H = ue(z, B), U = fe(B), mt = le(B), ht = me(P && o ? `${o}sprite.vtt` : null, D.loader), gt = !F && P;
|
|
60
60
|
pe(B, {
|
|
61
61
|
enabled: gt,
|
|
62
62
|
source: gt ? lt : null,
|
|
@@ -423,7 +423,7 @@ function b({ base: o, poster: h, width: _, height: v, duration: y, pending: b, p
|
|
|
423
423
|
className: m("pointer-events-none absolute inset-0 size-full object-contain", $)
|
|
424
424
|
}) : /* @__PURE__ */ f(Pe, {
|
|
425
425
|
vtt: `${o}sprite.vtt`,
|
|
426
|
-
|
|
426
|
+
loader: D.loader,
|
|
427
427
|
className: $
|
|
428
428
|
}),
|
|
429
429
|
/* @__PURE__ */ f(Ue, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-player.js","names":[],"sources":["../../src/components/video-player.tsx"],"sourcesContent":["import {\n AlertCircleIcon,\n ArrowShrink02Icon,\n ClosedCaptionIcon,\n FullScreenIcon,\n PauseIcon,\n PictureInPicture01Icon,\n PictureInPictureExitIcon,\n PictureInPictureOnIcon,\n PlayIcon,\n RectangularIcon,\n Refresh01Icon,\n Settings01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties, type MouseEvent, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration } from \"../client/gallery.js\";\nimport type { PlaybackError } from \"../client/playback.js\";\nimport {\n JUMP_SEEK,\n PLAYBACK_SPEEDS,\n PLAYER_VOLUME_KEY,\n nextSpeed,\n playerKeyAction,\n resumeAt,\n writeVolume,\n type PlayerAction,\n type PlayerTrack,\n} from \"../client/player.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useActiveCues, useFullscreen, useMediaState, usePictureInPicture, useProgressReports, useSprite, type PlaybackProgress } from \"../react/player.js\";\nimport { useLanguageName, useMessages } from \"../i18n/context.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport type { EncodeProgress as Progress } from \"../client/generated/wire.js\";\nimport { EncodeProgress } from \"./encode-progress.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Captions, ControlButton, SeekBar, SeekFlash, SettingsMenu, Spinner, VolumeControl, clock, playerButtonClass, type MenuSection, type SeekFlashState } from \"./player-controls.js\";\n\ntype XhrSetup = HlsPlayerOptions[\"xhrSetup\"];\n\n/** The first frame of a video's seek sprite (sprite.vtt), fitted like object-fit. */\nexport function SpriteFrame({ vtt, xhrSetup, fit = \"contain\", className }: { vtt: string; xhrSetup?: XhrSetup; fit?: \"contain\" | \"cover\"; className?: string }) {\n const sprite = useSprite(vtt, xhrSetup);\n const cue = sprite?.cues[0];\n if (!sprite || !cue) return null;\n return (\n <svg\n aria-hidden\n className={cn(\"pointer-events-none absolute inset-0 size-full\", className)}\n viewBox={`${cue.x} ${cue.y} ${cue.w} ${cue.h}`}\n preserveAspectRatio={fit === \"cover\" ? \"xMidYMid slice\" : \"xMidYMid meet\"}\n data-ckui=\"sprite-frame\"\n >\n <image href={cue.url} width={sprite.width} height={sprite.height} />\n </svg>\n );\n}\n\ntype At<T> = T extends unknown ? T & { time: number } : never;\n\n/** Playback as it happens, for analytics; never during an inline preview. */\nexport type PlayerEvent = At<\n | { type: \"play\" | \"playing\" | \"pause\" | \"waiting\" | \"ended\" }\n | { type: \"seek\"; from: number }\n | { type: \"rate\"; rate: number }\n | { type: \"volume\"; volume: number; muted: boolean }\n /** The viewer's quality choice; height null is Auto. */\n | { type: \"quality\"; height: number | null }\n | { type: \"audio\"; track: PlayerTrack }\n | { type: \"subtitles\"; track: PlayerTrack | null }\n | { type: \"fullscreen\" | \"pip\" | \"theater\"; on: boolean }\n | { type: \"error\"; error: PlaybackError }\n>;\n\n/** Everything a VideoMiniPlayer needs to carry on where the player stopped. */\nexport interface PlayerHandoff {\n base: string;\n time: number;\n playing: boolean;\n width?: number;\n height?: number;\n duration?: number;\n poster?: string | PublicPreset | null;\n audioLanguage?: string;\n subtitleLanguage?: string | null;\n}\n\n/** A host entry in the settings menu: a choice (e.g. version) or an action (e.g. report a problem). */\nexport type PlayerMenuItem =\n | { label: string; value: string; options: readonly { value: string; label: string }[]; onChange: (value: string) => void }\n | { label: string; onSelect: () => void };\n\nexport interface PlayerSlotContext {\n /** Where popups must portal to stay visible: the player while fullscreen, else undefined (the body). */\n container?: HTMLElement;\n /** The control buttons' class. */\n className: string;\n}\n\nexport interface VideoPlayerProps extends Omit<HlsPlayerOptions, \"src\"> {\n /** The ladder's HLS folder (client.media.hlsBase: {media}/{kind}/{id}/hls/{dir}): master.m3u8 and sprite.vtt resolve under it. */\n base?: string | null;\n /** Shown until playback starts: the poster's public preset or a URL; default the first sprite frame. */\n poster?: string | PublicPreset | null;\n /** The video's size (read API w/h): the box is reserved before anything loads; default the decoded frame's. */\n width?: number;\n height?: number;\n duration?: number;\n /** A pending encode (an editor read's `pending` and `progress`): shows its progress instead of a player. */\n pending?: boolean;\n progress?: Progress | null;\n /** Why the video cannot be encoded (an editor read's `failed.message`). */\n failed?: string;\n /**\n * `frame` is as wide as its column allows at the video's aspect, never\n * taller than `maxHeight`; `fill` fills a sized parent (a carousel slide).\n */\n layout?: \"frame\" | \"fill\";\n maxHeight?: string;\n /** Preview muted inline before play (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** Where the preview starts, seconds; default 10% in. */\n previewStart?: number;\n /** Shortcuts while focus is in the player (default), page-wide (a watch page: one such player per page), or none. */\n keyboard?: \"focus\" | \"global\" | false;\n /** Resume points and watch time: every `progressInterval` of playback, and on pause, seek, end, page hide and unload. */\n onProgress?: (progress: PlaybackProgress) => void;\n /** Milliseconds. Default 10000. */\n progressInterval?: number;\n onEvent?: (event: PlayerEvent) => void;\n /** Theater mode is the host's layout; the button shows with onTheaterChange. */\n theater?: boolean;\n onTheaterChange?: (theater: boolean) => void;\n /** Shows the mini-player button: the player pauses and hands over where it was. */\n onMiniPlayer?: (handoff: PlayerHandoff) => void;\n /** A control-bar slot for the host's downloads. */\n renderDownloads?: (ctx: PlayerSlotContext) => ReactNode;\n /** Host entries in the settings menu, after its own. */\n menuItems?: readonly PlayerMenuItem[];\n crossOrigin?: \"anonymous\" | \"use-credentials\";\n label?: string;\n className?: string;\n style?: CSSProperties;\n appearance?: ContentKitUiAppearance;\n}\n\nconst CONTROLS_IDLE_MS = 2500;\nconst DOUBLE_TAP_MS = 300;\nconst TAP_SEEK_CONTINUE_MS = 700;\nconst SEEK_FLASH_MS = 650;\n\nconst TEXT_ENTRY =\n 'textarea, select, [contenteditable]:not([contenteditable=\"false\"]), [role=textbox], [role=combobox], [role=searchbox], input:not([type=range]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=reset])';\nconst MENU = \"[role=menu], [role=menuitem], [role=menuitemradio], [role=menuitemcheckbox], [role=listbox], [role=option]\";\nconst OUTSIDE_INTERACTIVE = \"a, button, input, [role=button], [role=dialog], [role=alertdialog]\";\n\n/** A track's name: its playlist name, or its language's name when the name is only the language code. */\nexport function trackLabel(tr: Pick<PlayerTrack, \"label\" | \"lang\">, languageName: (code: string) => string): string {\n const code = tr.lang && (!tr.label || tr.label.toLowerCase() === tr.lang.toLowerCase()) ? tr.lang : undefined;\n return code ? languageName(code) : tr.label;\n}\n\n/**\n * Whether a shortcut key belongs to the player: never while typing or in a\n * menu, not the arrows on a slider (it steps itself), and outside the\n * player (page-wide shortcuts) not on another control.\n */\nexport function keyAllowed(target: EventTarget | null, key: string, root: Element): boolean {\n if (!(target instanceof Element)) return true;\n if (target.closest(TEXT_ENTRY) || target.closest(MENU)) return false;\n if (target.closest(\"input[type=range], [role=slider]\") && /^(Arrow|Page|Home$|End$)/.test(key)) return false;\n return root.contains(target) || !target.closest(OUTSIDE_INTERACTIVE);\n}\n\n// Space on one of the player's buttons is play/pause, not a click on the button last used.\nconst spaceOnButton = (target: EventTarget | null, root: Element) =>\n target instanceof Element && root.contains(target) && !!target.closest(\"button\") && !target.closest(MENU);\n\n/**\n * An HLS player that never spins forever: poster and play control, its own\n * controls (seek with sprite preview, volume, subtitles, audio, quality,\n * speed, picture-in-picture, theater, fullscreen, mini player), shortcuts,\n * touch gestures, resume and progress reports, and a specific error with\n * Retry and a support code.\n */\nexport function VideoPlayer({\n base,\n poster,\n width,\n height,\n duration,\n pending,\n progress,\n failed,\n layout = \"frame\",\n maxHeight = \"80svh\",\n inlinePreview,\n previewStart,\n keyboard = \"focus\",\n onProgress,\n progressInterval = 10_000,\n onEvent,\n theater,\n onTheaterChange,\n onMiniPlayer,\n renderDownloads,\n menuItems,\n crossOrigin,\n label,\n className,\n style,\n appearance,\n startAt,\n ...options\n}: VideoPlayerProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const playable = !!base && !pending && !failed;\n const src = playable ? `${base}master.m3u8` : null;\n const start = resumeAt(startAt, duration);\n const player = useHlsPlayer({ ...options, src, startAt: start });\n const { status, error, started, previewing, audio, subtitles, quality } = player;\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n const [video, setVideo] = useState<HTMLVideoElement | null>(null);\n const attach = player.ref;\n const videoRef = useCallback(\n (el: HTMLVideoElement | null) => {\n attach(el);\n setVideo(el);\n },\n [attach],\n );\n useInlinePreview({\n setting: inlinePreview,\n available: playable && !started && !error && options.active !== false && !options.autoPlay,\n target: root,\n playing: started && status === \"playing\",\n start: () => player.preview(previewStartAt(previewStart, duration)),\n stop: player.unload,\n });\n const media = useMediaState(video);\n const fs = useFullscreen(root, video);\n const pip = usePictureInPicture(video);\n const cues = useActiveCues(video);\n const sprite = useSprite(started && base ? `${base}sprite.vtt` : null, options.xhrSetup);\n const live = !previewing && started;\n useProgressReports(video, { enabled: live, source: live ? src : null, interval: progressInterval, onProgress });\n\n // Events.\n const emit = useRef(onEvent);\n emit.current = onEvent;\n const sending = useRef(false);\n sending.current = !previewing && status !== \"idle\";\n const send = useCallback((e: { type: string; [k: string]: unknown }, always = false) => {\n if ((always || sending.current) && emit.current) emit.current({ ...e, time: video?.currentTime ?? 0 } as PlayerEvent);\n }, [video]);\n useEffect(() => {\n if (!video) return;\n let from = 0;\n const h: Record<string, () => void> = {\n play: () => send({ type: \"play\" }),\n playing: () => send({ type: \"playing\" }),\n pause: () => !video.ended && send({ type: \"pause\" }),\n waiting: () => send({ type: \"waiting\" }),\n ended: () => send({ type: \"ended\" }),\n timeupdate: () => {\n if (!video.seeking) from = video.currentTime;\n },\n seeked: () => {\n send({ type: \"seek\", from });\n from = video.currentTime;\n },\n ratechange: () => send({ type: \"rate\", rate: video.playbackRate }),\n };\n for (const [k, f] of Object.entries(h)) video.addEventListener(k, f);\n return () => {\n for (const [k, f] of Object.entries(h)) video.removeEventListener(k, f);\n };\n }, [video, send]);\n useEffect(() => {\n if (error && !previewing) send({ type: \"error\", error }, true);\n }, [error, previewing, send]);\n const was = useRef({ fs: false, pip: false });\n useEffect(() => {\n if (was.current.fs !== fs.active) send({ type: \"fullscreen\", on: fs.active });\n if (was.current.pip !== pip.active) send({ type: \"pip\", on: pip.active });\n was.current = { fs: fs.active, pip: pip.active };\n }, [fs.active, pip.active, send]);\n\n // A new resume point for the playing source seeks to it.\n const lastStart = useRef(start);\n useEffect(() => {\n if (lastStart.current === start) return;\n lastStart.current = start;\n if (video && started) video.currentTime = start;\n }, [start, video, started]);\n\n // Controls show on pointer movement and hide after a pause in it while playing.\n const [poked, setPoked] = useState(false);\n const idle = useRef<ReturnType<typeof setTimeout>>(undefined);\n const poke = useCallback(() => {\n setPoked(true);\n clearTimeout(idle.current);\n idle.current = setTimeout(() => setPoked(false), CONTROLS_IDLE_MS);\n }, []);\n const hide = useCallback(() => {\n clearTimeout(idle.current);\n setPoked(false);\n }, []);\n useEffect(() => () => clearTimeout(idle.current), []);\n useEffect(() => {\n if (started) poke();\n }, [started, poke]);\n const [menuOpen, setMenuOpen] = useState(false);\n const [scrubbing, setScrubbing] = useState(false);\n const [keyFocus, setKeyFocus] = useState(false);\n const controlsShown = started && !error && (poked || media.paused || media.ended || menuOpen || scrubbing || keyFocus);\n\n const [flash, setFlash] = useState<SeekFlashState | null>(null);\n const flashTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const flashSeek = useCallback((direction: -1 | 1, seconds: number) => {\n const continuing = flashTimer.current !== undefined;\n clearTimeout(flashTimer.current);\n setFlash((prev) => ({ direction, seconds: continuing && prev?.direction === direction ? prev.seconds + seconds : seconds }));\n flashTimer.current = setTimeout(() => {\n setFlash(null);\n flashTimer.current = undefined;\n }, SEEK_FLASH_MS);\n }, []);\n useEffect(() => () => clearTimeout(flashTimer.current), []);\n\n const volumeKey = options.volumeKey === undefined ? PLAYER_VOLUME_KEY : options.volumeKey;\n const dur = media.duration || duration || 0;\n const seek = (to: number) => {\n if (video) video.currentTime = Math.min(dur || to, Math.max(0, to));\n };\n const seekBy = (seconds: number) => {\n if (!video) return;\n seek(video.currentTime + seconds);\n flashSeek(seconds < 0 ? -1 : 1, Math.abs(seconds));\n };\n const toggle = () => {\n if (!video) return;\n if (!started || video.ended) {\n if (video.ended) video.currentTime = 0;\n player.play();\n } else if (video.paused) player.play();\n else video.pause();\n };\n const setVolume = (v: number) => {\n if (!video) return;\n video.volume = Math.min(1, Math.max(0, v));\n if (video.volume > 0 && video.muted) video.muted = false;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n send({ type: \"volume\", volume: video.volume, muted: video.muted });\n };\n const toggleMute = () => {\n if (!video) return;\n video.muted = !video.muted;\n if (!video.muted && video.volume === 0) video.volume = 1;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n send({ type: \"volume\", volume: video.volume, muted: video.muted });\n };\n const selectAudio = (i: number) => {\n audio.select(i);\n const track = audio.list[i];\n if (track) send({ type: \"audio\", track });\n };\n const lastSub = useRef(-1);\n if (subtitles.selected >= 0) lastSub.current = subtitles.selected;\n const selectSubs = (i: number) => {\n subtitles.select(i);\n send({ type: \"subtitles\", track: subtitles.list[i] ?? null });\n };\n const toggleCaptions = () => selectSubs(subtitles.selected >= 0 ? -1 : lastSub.current >= 0 ? lastSub.current : 0);\n const selectQuality = (i: number) => {\n quality.select(i);\n send({ type: \"quality\", height: quality.levels.find((l) => l.index === i)?.height ?? null });\n };\n const toggleTheater = () => {\n onTheaterChange?.(!theater);\n send({ type: \"theater\", on: !theater });\n };\n const openMini = () => {\n if (!onMiniPlayer || !base || !video) return;\n const a = audio.list[audio.selected];\n const s = subtitles.list[subtitles.selected];\n const handoff: PlayerHandoff = {\n base,\n time: video.currentTime,\n playing: !video.paused && !video.ended,\n width: width ?? (video.videoWidth || undefined),\n height: height ?? (video.videoHeight || undefined),\n duration: dur || undefined,\n poster,\n audioLanguage: a?.lang,\n subtitleLanguage: s?.lang ?? null,\n };\n video.pause();\n if (fs.active) fs.toggle();\n onMiniPlayer(handoff);\n };\n\n const run = (a: PlayerAction): boolean => {\n if (!video) return false;\n if (!started && a.type !== \"toggle\") return false;\n switch (a.type) {\n case \"toggle\":\n toggle();\n break;\n case \"seekBy\":\n seekBy(a.seconds);\n break;\n case \"seekTo\":\n if (!dur) return false;\n seek(dur * a.fraction);\n break;\n case \"volumeBy\":\n setVolume((video.muted ? 0 : video.volume) + a.delta);\n break;\n case \"mute\":\n toggleMute();\n break;\n case \"captions\":\n if (!subtitles.list.length) return false;\n toggleCaptions();\n break;\n case \"fullscreen\":\n if (!fs.supported) return false;\n fs.toggle();\n break;\n case \"theater\":\n if (!onTheaterChange) return false;\n toggleTheater();\n break;\n case \"miniPlayer\":\n if (!onMiniPlayer) return false;\n openMini();\n break;\n case \"speed\":\n video.playbackRate = nextSpeed(video.playbackRate, a.step);\n break;\n }\n if (started) poke();\n return true;\n };\n const runRef = useRef(run);\n runRef.current = run;\n const active = options.active !== false;\n useEffect(() => {\n if (!keyboard || !root || !active) return;\n const on: EventTarget = keyboard === \"global\" ? document : root;\n const down = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.defaultPrevented) return;\n const action = playerKeyAction(k);\n if (!action || !keyAllowed(k.target, k.key, root) || !runRef.current(action)) return;\n k.preventDefault();\n };\n const up = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.key === \" \" && spaceOnButton(k.target, root)) k.preventDefault();\n };\n on.addEventListener(\"keydown\", down);\n on.addEventListener(\"keyup\", up);\n return () => {\n on.removeEventListener(\"keydown\", down);\n on.removeEventListener(\"keyup\", up);\n };\n }, [keyboard, root, active]);\n\n // Mouse: click plays and pauses, double click is fullscreen. Touch: a tap\n // shows or hides the controls; double taps on either side seek 10 s, and\n // taps that keep coming keep seeking.\n const pointerType = useRef(\"mouse\");\n const lastTap = useRef<{ at: number; side: -1 | 1 } | null>(null);\n const tapSeekUntil = useRef(0);\n const onSurfaceClick = (e: MouseEvent<HTMLDivElement>) => {\n if (pointerType.current === \"mouse\") {\n toggle();\n poke();\n return;\n }\n const r = e.currentTarget.getBoundingClientRect();\n const x = r.width ? (e.clientX - r.left) / r.width : 0.5;\n const side = x < 0.4 ? -1 : x > 0.6 ? 1 : null;\n const now = Date.now();\n const last = lastTap.current;\n lastTap.current = side ? { at: now, side } : null;\n if (side && last?.side === side && (now < tapSeekUntil.current || now - last.at <= DOUBLE_TAP_MS)) {\n tapSeekUntil.current = now + TAP_SEEK_CONTINUE_MS;\n seekBy(side * JUMP_SEEK);\n return;\n }\n if (controlsShown && !media.paused) hide();\n else poke();\n };\n const onSurfaceDoubleClick = () => {\n if (Date.now() >= tapSeekUntil.current && fs.supported) fs.toggle();\n };\n\n const aspect = width && height ? width / height : (media.aspect ?? 16 / 9);\n const frame: CSSProperties = fs.active\n ? { width: \"100%\", height: \"100%\", borderRadius: 0 }\n : layout === \"frame\"\n ? { aspectRatio: String(aspect), width: `min(100%, calc(${maxHeight} * ${aspect}))`, marginInline: \"auto\" }\n : {};\n const posterOutputs = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const busy = !error && !previewing && (status === \"loading\" || status === \"buffering\");\n // The preview shows once frames play; the cover stays until then.\n const shown = previewing && status === \"playing\";\n const fade = cn(\"transition-opacity duration-300 motion-reduce:transition-none\", shown && \"opacity-0\");\n const portal = fs.active && root ? root : undefined;\n const sections = settingsSections();\n\n function trackName(tr: PlayerTrack) {\n const name = trackLabel(tr, languageName) || t(\"player.track\", { number: tr.index + 1 });\n return tr.forced ? t(\"player.forced\", { label: name }) : name;\n }\n function settingsSections(): MenuSection[] {\n const out: MenuSection[] = [];\n if (audio.list.length > 1)\n out.push({ key: \"audio\", label: t(\"player.audio\"), value: String(audio.selected), options: audio.list.map((x) => ({ value: String(x.index), label: trackName(x) })), onChange: (v) => selectAudio(Number(v)) });\n if (subtitles.list.length)\n out.push({\n key: \"subtitles\",\n label: t(\"player.subtitles\"),\n value: String(subtitles.selected),\n options: [{ value: \"-1\", label: t(\"player.subtitlesOff\") }, ...subtitles.list.map((x) => ({ value: String(x.index), label: trackName(x) }))],\n onChange: (v) => selectSubs(Number(v)),\n });\n if (quality.levels.length > 1) {\n const playing = quality.levels.find((l) => l.index === quality.current);\n out.push({\n key: \"quality\",\n label: t(\"player.quality\"),\n value: String(quality.selected),\n options: [\n ...[...quality.levels].reverse().map((l) => ({ value: String(l.index), label: qualityLabel(l.height) })),\n { value: \"-1\", label: playing && quality.selected === -1 ? t(\"player.autoCurrent\", { quality: qualityLabel(playing.height).split(\" \")[0]! }) : t(\"player.auto\") },\n ],\n onChange: (v) => selectQuality(Number(v)),\n });\n }\n out.push({\n key: \"speed\",\n label: t(\"player.speed\"),\n value: String(media.rate),\n options: PLAYBACK_SPEEDS.map((r) => ({ value: String(r), label: r === 1 ? t(\"player.normal\") : `${r}×` })),\n onChange: (v) => {\n if (video) video.playbackRate = Number(v);\n },\n });\n menuItems?.forEach((m, i) => {\n if (\"options\" in m) out.push({ key: `host-${i}`, label: m.label, value: m.value, options: m.options, onChange: m.onChange });\n });\n return out;\n }\n const actions = menuItems?.filter((m): m is Extract<PlayerMenuItem, { onSelect: () => void }> => \"onSelect\" in m);\n\n return (\n <ContentKitUiRoot\n appearance={appearance}\n className={cn(\n \"@container relative w-full touch-manipulation overflow-hidden bg-black text-white outline-none\",\n layout === \"fill\" ? \"size-full\" : !fs.active && \"rounded-lg\",\n started && !controlsShown && \"cursor-none\",\n className,\n )}\n style={{ ...frame, ...style }}\n data-ckui=\"video-player\"\n data-status={failed ? \"failed\" : pending ? \"pending\" : status}\n data-previewing={previewing ? \"\" : undefined}\n data-fullscreen={fs.active ? \"\" : undefined}\n data-controls={controlsShown ? \"\" : undefined}\n ref={setRoot}\n role=\"group\"\n aria-label={label ?? t(\"player.label\")}\n tabIndex={playable ? -1 : undefined}\n onPointerDown={(e) => (pointerType.current = e.pointerType || \"mouse\")}\n onPointerMove={(e) => e.pointerType === \"mouse\" && started && poke()}\n onPointerLeave={(e) => e.pointerType === \"mouse\" && !scrubbing && !menuOpen && hide()}\n onFocus={(e) => setKeyFocus(focusVisible(e.target))}\n onBlur={() => setKeyFocus(false)}\n >\n {failed ? (\n <Panel icon>{t(\"player.failed\")}</Panel>\n ) : pending || !base ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-muted p-6 text-foreground\">\n <EncodeProgress progress={progress} className=\"max-w-xs\" />\n </div>\n ) : (\n <>\n <video ref={videoRef} className=\"absolute inset-0 size-full object-contain\" playsInline preload=\"none\" crossOrigin={crossOrigin} aria-label={label} />\n {started && !error && <div data-ckui=\"surface\" className=\"absolute inset-0\" onClick={onSurfaceClick} onDoubleClick={onSurfaceDoubleClick} />}\n {!started && !error && (\n <>\n {typeof poster === \"string\" ? (\n <img alt=\"\" src={poster} decoding=\"async\" className={cn(\"pointer-events-none absolute inset-0 size-full object-contain\", fade)} />\n ) : posterOutputs.length ? (\n <RenditionImg outputs={posterOutputs} className={cn(\"pointer-events-none absolute inset-0 size-full object-contain\", fade)} />\n ) : (\n <SpriteFrame vtt={`${base}sprite.vtt`} xhrSetup={options.xhrSetup} className={fade} />\n )}\n <BigButton label={t(\"player.play\")} onClick={player.play} disabled={busy} className={fade}>\n {busy ? <Spinner /> : <HugeiconsIcon icon={PlayIcon} className=\"ml-1 size-7 fill-current\" strokeWidth={1.5} />}\n </BigButton>\n {duration ? (\n <span className=\"pointer-events-none absolute right-2 bottom-2 rounded bg-black/65 px-1.5 py-0.5 text-xs font-medium tabular-nums\">{formatDuration(duration)}</span>\n ) : null}\n </>\n )}\n {started && !error && media.ended && (\n <BigButton label={t(\"player.replay\")} onClick={toggle}>\n <HugeiconsIcon icon={Refresh01Icon} className=\"size-7\" strokeWidth={1.75} />\n </BigButton>\n )}\n <Captions cues={live ? cues : []} bottom={controlsShown ? \"4.75rem\" : \"1.25rem\"} />\n {started && busy && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-black/45\">\n <Spinner />\n </span>\n </div>\n )}\n <SeekFlash flash={flash} />\n {started && !error && (\n <div\n data-ckui=\"controls\"\n data-ckui-noswipe=\"\"\n className={cn(\n \"pointer-events-none absolute inset-x-0 bottom-0 z-10 flex flex-col bg-linear-to-t from-black/75 via-black/35 to-transparent px-2 pt-12 pb-1 transition-opacity duration-200 motion-reduce:transition-none\",\n controlsShown ? \"opacity-100 *:pointer-events-auto\" : \"opacity-0\",\n )}\n >\n <SeekBar time={media.time} duration={dur} buffered={media.buffered} onSeek={seek} onScrub={(v) => setScrubbing(v !== null)} sprite={sprite} />\n <div className=\"flex items-center gap-0.5\">\n <ControlButton\n label={media.ended ? t(\"player.replay\") : media.paused ? t(\"player.resume\") : t(\"player.pause\")}\n shortcut=\"k\"\n icon={media.ended ? Refresh01Icon : media.paused ? PlayIcon : PauseIcon}\n onClick={toggle}\n data-ckui=\"play\"\n />\n <VolumeControl volume={media.volume} muted={media.muted} onVolume={setVolume} onMute={toggleMute} />\n <span className=\"ml-1 shrink-0 text-xs font-medium whitespace-nowrap text-white/90 tabular-nums\" data-ckui=\"time\">\n {clock(media.time)} / {clock(dur)}\n </span>\n <div className=\"min-w-0 flex-1\" />\n {subtitles.list.length > 0 && (\n <ControlButton label={t(\"player.subtitles\")} shortcut=\"c\" icon={ClosedCaptionIcon} aria-pressed={subtitles.selected >= 0} onClick={toggleCaptions} data-ckui=\"captions-toggle\" />\n )}\n {renderDownloads?.({ container: portal, className: playerButtonClass })}\n <SettingsMenu icon={Settings01Icon} label={t(\"player.settings\")} sections={sections} actions={actions} container={portal} onOpenChange={setMenuOpen} />\n {onMiniPlayer && <ControlButton className=\"@max-sm:hidden\" label={t(\"player.miniPlayer\")} shortcut=\"i\" icon={PictureInPictureOnIcon} onClick={openMini} data-ckui=\"mini-player-open\" />}\n {pip.supported && (\n <ControlButton\n className=\"@max-md:hidden\"\n label={pip.active ? t(\"player.exitPip\") : t(\"player.pip\")}\n icon={pip.active ? PictureInPictureExitIcon : PictureInPicture01Icon}\n onClick={pip.toggle}\n data-ckui=\"pip\"\n />\n )}\n {onTheaterChange && (\n <ControlButton\n className=\"max-lg:hidden\"\n label={t(\"player.theater\")}\n shortcut=\"t\"\n icon={RectangularIcon}\n aria-pressed={!!theater}\n onClick={toggleTheater}\n data-ckui=\"theater\"\n />\n )}\n {fs.supported && (\n <ControlButton\n label={fs.active ? t(\"player.exitFullscreen\") : t(\"player.fullscreen\")}\n shortcut=\"f\"\n icon={fs.active ? ArrowShrink02Icon : FullScreenIcon}\n onClick={fs.toggle}\n data-ckui=\"fullscreen\"\n />\n )}\n </div>\n </div>\n )}\n <span className=\"sr-only\" aria-live=\"polite\">\n {busy ? t(\"player.loading\") : \"\"}\n </span>\n {error && (\n <Panel icon code={error.code} onRetry={player.retry}>\n {t(`player.errors.${error.kind}`)}\n </Panel>\n )}\n </>\n )}\n </ContentKitUiRoot>\n );\n}\n\nfunction focusVisible(el: EventTarget) {\n try {\n return el instanceof Element && el.matches(\":focus-visible\") && !!el.closest(\"[data-ckui=controls]\");\n } catch {\n return false;\n }\n}\n\nfunction BigButton({ label, onClick, disabled, className, children }: { label: string; onClick: () => void; disabled?: boolean; className?: string; children: ReactNode }) {\n return (\n <button\n type=\"button\"\n className=\"absolute inset-0 z-[7] flex items-center justify-center outline-none focus-visible:[&>span]:ring-4 focus-visible:[&>span]:ring-white/60\"\n onClick={onClick}\n aria-label={label}\n disabled={disabled}\n >\n <span className={cn(\"flex size-16 items-center justify-center rounded-full bg-black/55 shadow-lg backdrop-blur-sm transition motion-safe:hover:scale-105\", className)}>{children}</span>\n </button>\n );\n}\n\n/** A preview's start: the given second, else 10% in, kept a second before the end. */\nexport function previewStartAt(start: number | undefined, duration: number | undefined): number {\n const d = duration ?? 0;\n const t = start ?? d * 0.1;\n return Math.max(0, d > 1 ? Math.min(t, d - 1) : 0);\n}\n\n/** \"2160p 4K\", \"1080p HD\", \"720p\": a rung by its short side. */\nexport function qualityLabel(height: number): string {\n return `${height}p${height >= 2160 ? \" 4K\" : height === 1080 ? \" HD\" : \"\"}`;\n}\n\nfunction Panel({ children, icon, code, onRetry }: { children: string; icon?: boolean; code?: string; onRetry?: () => void }) {\n const { t } = useMessages();\n return (\n <div role=\"alert\" data-ckui=\"player-error\" data-ckui-noswipe=\"\" className=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-black/80 p-6 text-center\">\n {icon && <HugeiconsIcon icon={AlertCircleIcon} className=\"size-7 text-white/80\" strokeWidth={1.75} />}\n <p className=\"max-w-sm text-sm font-medium text-balance\">{children}</p>\n {onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n <HugeiconsIcon icon={Refresh01Icon} data-icon=\"inline-start\" />\n {t(\"player.retry\")}\n </Button>\n )}\n {code && <p className=\"font-mono text-[11px] text-white/55\">{t(\"player.code\", { code })}</p>}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"video-player.js","names":[],"sources":["../../src/components/video-player.tsx"],"sourcesContent":["import {\n AlertCircleIcon,\n ArrowShrink02Icon,\n ClosedCaptionIcon,\n FullScreenIcon,\n PauseIcon,\n PictureInPicture01Icon,\n PictureInPictureExitIcon,\n PictureInPictureOnIcon,\n PlayIcon,\n RectangularIcon,\n Refresh01Icon,\n Settings01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties, type MouseEvent, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration } from \"../client/gallery.js\";\nimport type { PlaybackError } from \"../client/playback.js\";\nimport {\n JUMP_SEEK,\n PLAYBACK_SPEEDS,\n PLAYER_VOLUME_KEY,\n nextSpeed,\n playerKeyAction,\n resumeAt,\n writeVolume,\n type PlayerAction,\n type PlayerTrack,\n} from \"../client/player.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useActiveCues, useFullscreen, useMediaState, usePictureInPicture, useProgressReports, useSprite, type PlaybackProgress } from \"../react/player.js\";\nimport { useLanguageName, useMessages } from \"../i18n/context.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport type { EncodeProgress as Progress } from \"../client/generated/wire.js\";\nimport { EncodeProgress } from \"./encode-progress.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Captions, ControlButton, SeekBar, SeekFlash, SettingsMenu, Spinner, VolumeControl, clock, playerButtonClass, type MenuSection, type SeekFlashState } from \"./player-controls.js\";\n\ntype Loader = HlsPlayerOptions[\"loader\"];\n\n/** The first frame of a video's seek sprite (sprite.vtt), fitted like object-fit. */\nexport function SpriteFrame({ vtt, loader, fit = \"contain\", className }: { vtt: string; loader?: Loader; fit?: \"contain\" | \"cover\"; className?: string }) {\n const sprite = useSprite(vtt, loader);\n const cue = sprite?.cues[0];\n if (!sprite || !cue) return null;\n return (\n <svg\n aria-hidden\n className={cn(\"pointer-events-none absolute inset-0 size-full\", className)}\n viewBox={`${cue.x} ${cue.y} ${cue.w} ${cue.h}`}\n preserveAspectRatio={fit === \"cover\" ? \"xMidYMid slice\" : \"xMidYMid meet\"}\n data-ckui=\"sprite-frame\"\n >\n <image href={cue.url} width={sprite.width} height={sprite.height} />\n </svg>\n );\n}\n\ntype At<T> = T extends unknown ? T & { time: number } : never;\n\n/** Playback as it happens, for analytics; never during an inline preview. */\nexport type PlayerEvent = At<\n | { type: \"play\" | \"playing\" | \"pause\" | \"waiting\" | \"ended\" }\n | { type: \"seek\"; from: number }\n | { type: \"rate\"; rate: number }\n | { type: \"volume\"; volume: number; muted: boolean }\n /** The viewer's quality choice; height null is Auto. */\n | { type: \"quality\"; height: number | null }\n | { type: \"audio\"; track: PlayerTrack }\n | { type: \"subtitles\"; track: PlayerTrack | null }\n | { type: \"fullscreen\" | \"pip\" | \"theater\"; on: boolean }\n | { type: \"error\"; error: PlaybackError }\n>;\n\n/** Everything a VideoMiniPlayer needs to carry on where the player stopped. */\nexport interface PlayerHandoff {\n base: string;\n time: number;\n playing: boolean;\n width?: number;\n height?: number;\n duration?: number;\n poster?: string | PublicPreset | null;\n audioLanguage?: string;\n subtitleLanguage?: string | null;\n}\n\n/** A host entry in the settings menu: a choice (e.g. version) or an action (e.g. report a problem). */\nexport type PlayerMenuItem =\n | { label: string; value: string; options: readonly { value: string; label: string }[]; onChange: (value: string) => void }\n | { label: string; onSelect: () => void };\n\nexport interface PlayerSlotContext {\n /** Where popups must portal to stay visible: the player while fullscreen, else undefined (the body). */\n container?: HTMLElement;\n /** The control buttons' class. */\n className: string;\n}\n\nexport interface VideoPlayerProps extends Omit<HlsPlayerOptions, \"src\"> {\n /** The ladder's HLS folder (client.media.hlsBase: {media}/{kind}/{id}/hls/{dir}): master.m3u8 and sprite.vtt resolve under it. */\n base?: string | null;\n /** Shown until playback starts: the poster's public preset or a URL; default the first sprite frame. */\n poster?: string | PublicPreset | null;\n /** The video's size (read API w/h): the box is reserved before anything loads; default the decoded frame's. */\n width?: number;\n height?: number;\n duration?: number;\n /** A pending encode (an editor read's `pending` and `progress`): shows its progress instead of a player. */\n pending?: boolean;\n progress?: Progress | null;\n /** Why the video cannot be encoded (an editor read's `failed.message`). */\n failed?: string;\n /**\n * `frame` is as wide as its column allows at the video's aspect, never\n * taller than `maxHeight`; `fill` fills a sized parent (a carousel slide).\n */\n layout?: \"frame\" | \"fill\";\n maxHeight?: string;\n /** Preview muted inline before play (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** Where the preview starts, seconds; default 10% in. */\n previewStart?: number;\n /** Shortcuts while focus is in the player (default), page-wide (a watch page: one such player per page), or none. */\n keyboard?: \"focus\" | \"global\" | false;\n /** Resume points and watch time: every `progressInterval` of playback, and on pause, seek, end, page hide and unload. */\n onProgress?: (progress: PlaybackProgress) => void;\n /** Milliseconds. Default 10000. */\n progressInterval?: number;\n onEvent?: (event: PlayerEvent) => void;\n /** Theater mode is the host's layout; the button shows with onTheaterChange. */\n theater?: boolean;\n onTheaterChange?: (theater: boolean) => void;\n /** Shows the mini-player button: the player pauses and hands over where it was. */\n onMiniPlayer?: (handoff: PlayerHandoff) => void;\n /** A control-bar slot for the host's downloads. */\n renderDownloads?: (ctx: PlayerSlotContext) => ReactNode;\n /** Host entries in the settings menu, after its own. */\n menuItems?: readonly PlayerMenuItem[];\n crossOrigin?: \"anonymous\" | \"use-credentials\";\n label?: string;\n className?: string;\n style?: CSSProperties;\n appearance?: ContentKitUiAppearance;\n}\n\nconst CONTROLS_IDLE_MS = 2500;\nconst DOUBLE_TAP_MS = 300;\nconst TAP_SEEK_CONTINUE_MS = 700;\nconst SEEK_FLASH_MS = 650;\n\nconst TEXT_ENTRY =\n 'textarea, select, [contenteditable]:not([contenteditable=\"false\"]), [role=textbox], [role=combobox], [role=searchbox], input:not([type=range]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=reset])';\nconst MENU = \"[role=menu], [role=menuitem], [role=menuitemradio], [role=menuitemcheckbox], [role=listbox], [role=option]\";\nconst OUTSIDE_INTERACTIVE = \"a, button, input, [role=button], [role=dialog], [role=alertdialog]\";\n\n/** A track's name: its playlist name, or its language's name when the name is only the language code. */\nexport function trackLabel(tr: Pick<PlayerTrack, \"label\" | \"lang\">, languageName: (code: string) => string): string {\n const code = tr.lang && (!tr.label || tr.label.toLowerCase() === tr.lang.toLowerCase()) ? tr.lang : undefined;\n return code ? languageName(code) : tr.label;\n}\n\n/**\n * Whether a shortcut key belongs to the player: never while typing or in a\n * menu, not the arrows on a slider (it steps itself), and outside the\n * player (page-wide shortcuts) not on another control.\n */\nexport function keyAllowed(target: EventTarget | null, key: string, root: Element): boolean {\n if (!(target instanceof Element)) return true;\n if (target.closest(TEXT_ENTRY) || target.closest(MENU)) return false;\n if (target.closest(\"input[type=range], [role=slider]\") && /^(Arrow|Page|Home$|End$)/.test(key)) return false;\n return root.contains(target) || !target.closest(OUTSIDE_INTERACTIVE);\n}\n\n// Space on one of the player's buttons is play/pause, not a click on the button last used.\nconst spaceOnButton = (target: EventTarget | null, root: Element) =>\n target instanceof Element && root.contains(target) && !!target.closest(\"button\") && !target.closest(MENU);\n\n/**\n * An HLS player that never spins forever: poster and play control, its own\n * controls (seek with sprite preview, volume, subtitles, audio, quality,\n * speed, picture-in-picture, theater, fullscreen, mini player), shortcuts,\n * touch gestures, resume and progress reports, and a specific error with\n * Retry and a support code.\n */\nexport function VideoPlayer({\n base,\n poster,\n width,\n height,\n duration,\n pending,\n progress,\n failed,\n layout = \"frame\",\n maxHeight = \"80svh\",\n inlinePreview,\n previewStart,\n keyboard = \"focus\",\n onProgress,\n progressInterval = 10_000,\n onEvent,\n theater,\n onTheaterChange,\n onMiniPlayer,\n renderDownloads,\n menuItems,\n crossOrigin,\n label,\n className,\n style,\n appearance,\n startAt,\n ...options\n}: VideoPlayerProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const playable = !!base && !pending && !failed;\n const src = playable ? `${base}master.m3u8` : null;\n const start = resumeAt(startAt, duration);\n const player = useHlsPlayer({ ...options, src, startAt: start });\n const { status, error, started, previewing, audio, subtitles, quality } = player;\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n const [video, setVideo] = useState<HTMLVideoElement | null>(null);\n const attach = player.ref;\n const videoRef = useCallback(\n (el: HTMLVideoElement | null) => {\n attach(el);\n setVideo(el);\n },\n [attach],\n );\n useInlinePreview({\n setting: inlinePreview,\n available: playable && !started && !error && options.active !== false && !options.autoPlay,\n target: root,\n playing: started && status === \"playing\",\n start: () => player.preview(previewStartAt(previewStart, duration)),\n stop: player.unload,\n });\n const media = useMediaState(video);\n const fs = useFullscreen(root, video);\n const pip = usePictureInPicture(video);\n const cues = useActiveCues(video);\n const sprite = useSprite(started && base ? `${base}sprite.vtt` : null, options.loader);\n const live = !previewing && started;\n useProgressReports(video, { enabled: live, source: live ? src : null, interval: progressInterval, onProgress });\n\n // Events.\n const emit = useRef(onEvent);\n emit.current = onEvent;\n const sending = useRef(false);\n sending.current = !previewing && status !== \"idle\";\n const send = useCallback((e: { type: string; [k: string]: unknown }, always = false) => {\n if ((always || sending.current) && emit.current) emit.current({ ...e, time: video?.currentTime ?? 0 } as PlayerEvent);\n }, [video]);\n useEffect(() => {\n if (!video) return;\n let from = 0;\n const h: Record<string, () => void> = {\n play: () => send({ type: \"play\" }),\n playing: () => send({ type: \"playing\" }),\n pause: () => !video.ended && send({ type: \"pause\" }),\n waiting: () => send({ type: \"waiting\" }),\n ended: () => send({ type: \"ended\" }),\n timeupdate: () => {\n if (!video.seeking) from = video.currentTime;\n },\n seeked: () => {\n send({ type: \"seek\", from });\n from = video.currentTime;\n },\n ratechange: () => send({ type: \"rate\", rate: video.playbackRate }),\n };\n for (const [k, f] of Object.entries(h)) video.addEventListener(k, f);\n return () => {\n for (const [k, f] of Object.entries(h)) video.removeEventListener(k, f);\n };\n }, [video, send]);\n useEffect(() => {\n if (error && !previewing) send({ type: \"error\", error }, true);\n }, [error, previewing, send]);\n const was = useRef({ fs: false, pip: false });\n useEffect(() => {\n if (was.current.fs !== fs.active) send({ type: \"fullscreen\", on: fs.active });\n if (was.current.pip !== pip.active) send({ type: \"pip\", on: pip.active });\n was.current = { fs: fs.active, pip: pip.active };\n }, [fs.active, pip.active, send]);\n\n // A new resume point for the playing source seeks to it.\n const lastStart = useRef(start);\n useEffect(() => {\n if (lastStart.current === start) return;\n lastStart.current = start;\n if (video && started) video.currentTime = start;\n }, [start, video, started]);\n\n // Controls show on pointer movement and hide after a pause in it while playing.\n const [poked, setPoked] = useState(false);\n const idle = useRef<ReturnType<typeof setTimeout>>(undefined);\n const poke = useCallback(() => {\n setPoked(true);\n clearTimeout(idle.current);\n idle.current = setTimeout(() => setPoked(false), CONTROLS_IDLE_MS);\n }, []);\n const hide = useCallback(() => {\n clearTimeout(idle.current);\n setPoked(false);\n }, []);\n useEffect(() => () => clearTimeout(idle.current), []);\n useEffect(() => {\n if (started) poke();\n }, [started, poke]);\n const [menuOpen, setMenuOpen] = useState(false);\n const [scrubbing, setScrubbing] = useState(false);\n const [keyFocus, setKeyFocus] = useState(false);\n const controlsShown = started && !error && (poked || media.paused || media.ended || menuOpen || scrubbing || keyFocus);\n\n const [flash, setFlash] = useState<SeekFlashState | null>(null);\n const flashTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const flashSeek = useCallback((direction: -1 | 1, seconds: number) => {\n const continuing = flashTimer.current !== undefined;\n clearTimeout(flashTimer.current);\n setFlash((prev) => ({ direction, seconds: continuing && prev?.direction === direction ? prev.seconds + seconds : seconds }));\n flashTimer.current = setTimeout(() => {\n setFlash(null);\n flashTimer.current = undefined;\n }, SEEK_FLASH_MS);\n }, []);\n useEffect(() => () => clearTimeout(flashTimer.current), []);\n\n const volumeKey = options.volumeKey === undefined ? PLAYER_VOLUME_KEY : options.volumeKey;\n const dur = media.duration || duration || 0;\n const seek = (to: number) => {\n if (video) video.currentTime = Math.min(dur || to, Math.max(0, to));\n };\n const seekBy = (seconds: number) => {\n if (!video) return;\n seek(video.currentTime + seconds);\n flashSeek(seconds < 0 ? -1 : 1, Math.abs(seconds));\n };\n const toggle = () => {\n if (!video) return;\n if (!started || video.ended) {\n if (video.ended) video.currentTime = 0;\n player.play();\n } else if (video.paused) player.play();\n else video.pause();\n };\n const setVolume = (v: number) => {\n if (!video) return;\n video.volume = Math.min(1, Math.max(0, v));\n if (video.volume > 0 && video.muted) video.muted = false;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n send({ type: \"volume\", volume: video.volume, muted: video.muted });\n };\n const toggleMute = () => {\n if (!video) return;\n video.muted = !video.muted;\n if (!video.muted && video.volume === 0) video.volume = 1;\n writeVolume(volumeKey, { volume: video.volume, muted: video.muted });\n send({ type: \"volume\", volume: video.volume, muted: video.muted });\n };\n const selectAudio = (i: number) => {\n audio.select(i);\n const track = audio.list[i];\n if (track) send({ type: \"audio\", track });\n };\n const lastSub = useRef(-1);\n if (subtitles.selected >= 0) lastSub.current = subtitles.selected;\n const selectSubs = (i: number) => {\n subtitles.select(i);\n send({ type: \"subtitles\", track: subtitles.list[i] ?? null });\n };\n const toggleCaptions = () => selectSubs(subtitles.selected >= 0 ? -1 : lastSub.current >= 0 ? lastSub.current : 0);\n const selectQuality = (i: number) => {\n quality.select(i);\n send({ type: \"quality\", height: quality.levels.find((l) => l.index === i)?.height ?? null });\n };\n const toggleTheater = () => {\n onTheaterChange?.(!theater);\n send({ type: \"theater\", on: !theater });\n };\n const openMini = () => {\n if (!onMiniPlayer || !base || !video) return;\n const a = audio.list[audio.selected];\n const s = subtitles.list[subtitles.selected];\n const handoff: PlayerHandoff = {\n base,\n time: video.currentTime,\n playing: !video.paused && !video.ended,\n width: width ?? (video.videoWidth || undefined),\n height: height ?? (video.videoHeight || undefined),\n duration: dur || undefined,\n poster,\n audioLanguage: a?.lang,\n subtitleLanguage: s?.lang ?? null,\n };\n video.pause();\n if (fs.active) fs.toggle();\n onMiniPlayer(handoff);\n };\n\n const run = (a: PlayerAction): boolean => {\n if (!video) return false;\n if (!started && a.type !== \"toggle\") return false;\n switch (a.type) {\n case \"toggle\":\n toggle();\n break;\n case \"seekBy\":\n seekBy(a.seconds);\n break;\n case \"seekTo\":\n if (!dur) return false;\n seek(dur * a.fraction);\n break;\n case \"volumeBy\":\n setVolume((video.muted ? 0 : video.volume) + a.delta);\n break;\n case \"mute\":\n toggleMute();\n break;\n case \"captions\":\n if (!subtitles.list.length) return false;\n toggleCaptions();\n break;\n case \"fullscreen\":\n if (!fs.supported) return false;\n fs.toggle();\n break;\n case \"theater\":\n if (!onTheaterChange) return false;\n toggleTheater();\n break;\n case \"miniPlayer\":\n if (!onMiniPlayer) return false;\n openMini();\n break;\n case \"speed\":\n video.playbackRate = nextSpeed(video.playbackRate, a.step);\n break;\n }\n if (started) poke();\n return true;\n };\n const runRef = useRef(run);\n runRef.current = run;\n const active = options.active !== false;\n useEffect(() => {\n if (!keyboard || !root || !active) return;\n const on: EventTarget = keyboard === \"global\" ? document : root;\n const down = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.defaultPrevented) return;\n const action = playerKeyAction(k);\n if (!action || !keyAllowed(k.target, k.key, root) || !runRef.current(action)) return;\n k.preventDefault();\n };\n const up = (e: Event) => {\n const k = e as KeyboardEvent;\n if (k.key === \" \" && spaceOnButton(k.target, root)) k.preventDefault();\n };\n on.addEventListener(\"keydown\", down);\n on.addEventListener(\"keyup\", up);\n return () => {\n on.removeEventListener(\"keydown\", down);\n on.removeEventListener(\"keyup\", up);\n };\n }, [keyboard, root, active]);\n\n // Mouse: click plays and pauses, double click is fullscreen. Touch: a tap\n // shows or hides the controls; double taps on either side seek 10 s, and\n // taps that keep coming keep seeking.\n const pointerType = useRef(\"mouse\");\n const lastTap = useRef<{ at: number; side: -1 | 1 } | null>(null);\n const tapSeekUntil = useRef(0);\n const onSurfaceClick = (e: MouseEvent<HTMLDivElement>) => {\n if (pointerType.current === \"mouse\") {\n toggle();\n poke();\n return;\n }\n const r = e.currentTarget.getBoundingClientRect();\n const x = r.width ? (e.clientX - r.left) / r.width : 0.5;\n const side = x < 0.4 ? -1 : x > 0.6 ? 1 : null;\n const now = Date.now();\n const last = lastTap.current;\n lastTap.current = side ? { at: now, side } : null;\n if (side && last?.side === side && (now < tapSeekUntil.current || now - last.at <= DOUBLE_TAP_MS)) {\n tapSeekUntil.current = now + TAP_SEEK_CONTINUE_MS;\n seekBy(side * JUMP_SEEK);\n return;\n }\n if (controlsShown && !media.paused) hide();\n else poke();\n };\n const onSurfaceDoubleClick = () => {\n if (Date.now() >= tapSeekUntil.current && fs.supported) fs.toggle();\n };\n\n const aspect = width && height ? width / height : (media.aspect ?? 16 / 9);\n const frame: CSSProperties = fs.active\n ? { width: \"100%\", height: \"100%\", borderRadius: 0 }\n : layout === \"frame\"\n ? { aspectRatio: String(aspect), width: `min(100%, calc(${maxHeight} * ${aspect}))`, marginInline: \"auto\" }\n : {};\n const posterOutputs = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const busy = !error && !previewing && (status === \"loading\" || status === \"buffering\");\n // The preview shows once frames play; the cover stays until then.\n const shown = previewing && status === \"playing\";\n const fade = cn(\"transition-opacity duration-300 motion-reduce:transition-none\", shown && \"opacity-0\");\n const portal = fs.active && root ? root : undefined;\n const sections = settingsSections();\n\n function trackName(tr: PlayerTrack) {\n const name = trackLabel(tr, languageName) || t(\"player.track\", { number: tr.index + 1 });\n return tr.forced ? t(\"player.forced\", { label: name }) : name;\n }\n function settingsSections(): MenuSection[] {\n const out: MenuSection[] = [];\n if (audio.list.length > 1)\n out.push({ key: \"audio\", label: t(\"player.audio\"), value: String(audio.selected), options: audio.list.map((x) => ({ value: String(x.index), label: trackName(x) })), onChange: (v) => selectAudio(Number(v)) });\n if (subtitles.list.length)\n out.push({\n key: \"subtitles\",\n label: t(\"player.subtitles\"),\n value: String(subtitles.selected),\n options: [{ value: \"-1\", label: t(\"player.subtitlesOff\") }, ...subtitles.list.map((x) => ({ value: String(x.index), label: trackName(x) }))],\n onChange: (v) => selectSubs(Number(v)),\n });\n if (quality.levels.length > 1) {\n const playing = quality.levels.find((l) => l.index === quality.current);\n out.push({\n key: \"quality\",\n label: t(\"player.quality\"),\n value: String(quality.selected),\n options: [\n ...[...quality.levels].reverse().map((l) => ({ value: String(l.index), label: qualityLabel(l.height) })),\n { value: \"-1\", label: playing && quality.selected === -1 ? t(\"player.autoCurrent\", { quality: qualityLabel(playing.height).split(\" \")[0]! }) : t(\"player.auto\") },\n ],\n onChange: (v) => selectQuality(Number(v)),\n });\n }\n out.push({\n key: \"speed\",\n label: t(\"player.speed\"),\n value: String(media.rate),\n options: PLAYBACK_SPEEDS.map((r) => ({ value: String(r), label: r === 1 ? t(\"player.normal\") : `${r}×` })),\n onChange: (v) => {\n if (video) video.playbackRate = Number(v);\n },\n });\n menuItems?.forEach((m, i) => {\n if (\"options\" in m) out.push({ key: `host-${i}`, label: m.label, value: m.value, options: m.options, onChange: m.onChange });\n });\n return out;\n }\n const actions = menuItems?.filter((m): m is Extract<PlayerMenuItem, { onSelect: () => void }> => \"onSelect\" in m);\n\n return (\n <ContentKitUiRoot\n appearance={appearance}\n className={cn(\n \"@container relative w-full touch-manipulation overflow-hidden bg-black text-white outline-none\",\n layout === \"fill\" ? \"size-full\" : !fs.active && \"rounded-lg\",\n started && !controlsShown && \"cursor-none\",\n className,\n )}\n style={{ ...frame, ...style }}\n data-ckui=\"video-player\"\n data-status={failed ? \"failed\" : pending ? \"pending\" : status}\n data-previewing={previewing ? \"\" : undefined}\n data-fullscreen={fs.active ? \"\" : undefined}\n data-controls={controlsShown ? \"\" : undefined}\n ref={setRoot}\n role=\"group\"\n aria-label={label ?? t(\"player.label\")}\n tabIndex={playable ? -1 : undefined}\n onPointerDown={(e) => (pointerType.current = e.pointerType || \"mouse\")}\n onPointerMove={(e) => e.pointerType === \"mouse\" && started && poke()}\n onPointerLeave={(e) => e.pointerType === \"mouse\" && !scrubbing && !menuOpen && hide()}\n onFocus={(e) => setKeyFocus(focusVisible(e.target))}\n onBlur={() => setKeyFocus(false)}\n >\n {failed ? (\n <Panel icon>{t(\"player.failed\")}</Panel>\n ) : pending || !base ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-muted p-6 text-foreground\">\n <EncodeProgress progress={progress} className=\"max-w-xs\" />\n </div>\n ) : (\n <>\n <video ref={videoRef} className=\"absolute inset-0 size-full object-contain\" playsInline preload=\"none\" crossOrigin={crossOrigin} aria-label={label} />\n {started && !error && <div data-ckui=\"surface\" className=\"absolute inset-0\" onClick={onSurfaceClick} onDoubleClick={onSurfaceDoubleClick} />}\n {!started && !error && (\n <>\n {typeof poster === \"string\" ? (\n <img alt=\"\" src={poster} decoding=\"async\" className={cn(\"pointer-events-none absolute inset-0 size-full object-contain\", fade)} />\n ) : posterOutputs.length ? (\n <RenditionImg outputs={posterOutputs} className={cn(\"pointer-events-none absolute inset-0 size-full object-contain\", fade)} />\n ) : (\n <SpriteFrame vtt={`${base}sprite.vtt`} loader={options.loader} className={fade} />\n )}\n <BigButton label={t(\"player.play\")} onClick={player.play} disabled={busy} className={fade}>\n {busy ? <Spinner /> : <HugeiconsIcon icon={PlayIcon} className=\"ml-1 size-7 fill-current\" strokeWidth={1.5} />}\n </BigButton>\n {duration ? (\n <span className=\"pointer-events-none absolute right-2 bottom-2 rounded bg-black/65 px-1.5 py-0.5 text-xs font-medium tabular-nums\">{formatDuration(duration)}</span>\n ) : null}\n </>\n )}\n {started && !error && media.ended && (\n <BigButton label={t(\"player.replay\")} onClick={toggle}>\n <HugeiconsIcon icon={Refresh01Icon} className=\"size-7\" strokeWidth={1.75} />\n </BigButton>\n )}\n <Captions cues={live ? cues : []} bottom={controlsShown ? \"4.75rem\" : \"1.25rem\"} />\n {started && busy && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-black/45\">\n <Spinner />\n </span>\n </div>\n )}\n <SeekFlash flash={flash} />\n {started && !error && (\n <div\n data-ckui=\"controls\"\n data-ckui-noswipe=\"\"\n className={cn(\n \"pointer-events-none absolute inset-x-0 bottom-0 z-10 flex flex-col bg-linear-to-t from-black/75 via-black/35 to-transparent px-2 pt-12 pb-1 transition-opacity duration-200 motion-reduce:transition-none\",\n controlsShown ? \"opacity-100 *:pointer-events-auto\" : \"opacity-0\",\n )}\n >\n <SeekBar time={media.time} duration={dur} buffered={media.buffered} onSeek={seek} onScrub={(v) => setScrubbing(v !== null)} sprite={sprite} />\n <div className=\"flex items-center gap-0.5\">\n <ControlButton\n label={media.ended ? t(\"player.replay\") : media.paused ? t(\"player.resume\") : t(\"player.pause\")}\n shortcut=\"k\"\n icon={media.ended ? Refresh01Icon : media.paused ? PlayIcon : PauseIcon}\n onClick={toggle}\n data-ckui=\"play\"\n />\n <VolumeControl volume={media.volume} muted={media.muted} onVolume={setVolume} onMute={toggleMute} />\n <span className=\"ml-1 shrink-0 text-xs font-medium whitespace-nowrap text-white/90 tabular-nums\" data-ckui=\"time\">\n {clock(media.time)} / {clock(dur)}\n </span>\n <div className=\"min-w-0 flex-1\" />\n {subtitles.list.length > 0 && (\n <ControlButton label={t(\"player.subtitles\")} shortcut=\"c\" icon={ClosedCaptionIcon} aria-pressed={subtitles.selected >= 0} onClick={toggleCaptions} data-ckui=\"captions-toggle\" />\n )}\n {renderDownloads?.({ container: portal, className: playerButtonClass })}\n <SettingsMenu icon={Settings01Icon} label={t(\"player.settings\")} sections={sections} actions={actions} container={portal} onOpenChange={setMenuOpen} />\n {onMiniPlayer && <ControlButton className=\"@max-sm:hidden\" label={t(\"player.miniPlayer\")} shortcut=\"i\" icon={PictureInPictureOnIcon} onClick={openMini} data-ckui=\"mini-player-open\" />}\n {pip.supported && (\n <ControlButton\n className=\"@max-md:hidden\"\n label={pip.active ? t(\"player.exitPip\") : t(\"player.pip\")}\n icon={pip.active ? PictureInPictureExitIcon : PictureInPicture01Icon}\n onClick={pip.toggle}\n data-ckui=\"pip\"\n />\n )}\n {onTheaterChange && (\n <ControlButton\n className=\"max-lg:hidden\"\n label={t(\"player.theater\")}\n shortcut=\"t\"\n icon={RectangularIcon}\n aria-pressed={!!theater}\n onClick={toggleTheater}\n data-ckui=\"theater\"\n />\n )}\n {fs.supported && (\n <ControlButton\n label={fs.active ? t(\"player.exitFullscreen\") : t(\"player.fullscreen\")}\n shortcut=\"f\"\n icon={fs.active ? ArrowShrink02Icon : FullScreenIcon}\n onClick={fs.toggle}\n data-ckui=\"fullscreen\"\n />\n )}\n </div>\n </div>\n )}\n <span className=\"sr-only\" aria-live=\"polite\">\n {busy ? t(\"player.loading\") : \"\"}\n </span>\n {error && (\n <Panel icon code={error.code} onRetry={player.retry}>\n {t(`player.errors.${error.kind}`)}\n </Panel>\n )}\n </>\n )}\n </ContentKitUiRoot>\n );\n}\n\nfunction focusVisible(el: EventTarget) {\n try {\n return el instanceof Element && el.matches(\":focus-visible\") && !!el.closest(\"[data-ckui=controls]\");\n } catch {\n return false;\n }\n}\n\nfunction BigButton({ label, onClick, disabled, className, children }: { label: string; onClick: () => void; disabled?: boolean; className?: string; children: ReactNode }) {\n return (\n <button\n type=\"button\"\n className=\"absolute inset-0 z-[7] flex items-center justify-center outline-none focus-visible:[&>span]:ring-4 focus-visible:[&>span]:ring-white/60\"\n onClick={onClick}\n aria-label={label}\n disabled={disabled}\n >\n <span className={cn(\"flex size-16 items-center justify-center rounded-full bg-black/55 shadow-lg backdrop-blur-sm transition motion-safe:hover:scale-105\", className)}>{children}</span>\n </button>\n );\n}\n\n/** A preview's start: the given second, else 10% in, kept a second before the end. */\nexport function previewStartAt(start: number | undefined, duration: number | undefined): number {\n const d = duration ?? 0;\n const t = start ?? d * 0.1;\n return Math.max(0, d > 1 ? Math.min(t, d - 1) : 0);\n}\n\n/** \"2160p 4K\", \"1080p HD\", \"720p\": a rung by its short side. */\nexport function qualityLabel(height: number): string {\n return `${height}p${height >= 2160 ? \" 4K\" : height === 1080 ? \" HD\" : \"\"}`;\n}\n\nfunction Panel({ children, icon, code, onRetry }: { children: string; icon?: boolean; code?: string; onRetry?: () => void }) {\n const { t } = useMessages();\n return (\n <div role=\"alert\" data-ckui=\"player-error\" data-ckui-noswipe=\"\" className=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-black/80 p-6 text-center\">\n {icon && <HugeiconsIcon icon={AlertCircleIcon} className=\"size-7 text-white/80\" strokeWidth={1.75} />}\n <p className=\"max-w-sm text-sm font-medium text-balance\">{children}</p>\n {onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n <HugeiconsIcon icon={Refresh01Icon} data-icon=\"inline-start\" />\n {t(\"player.retry\")}\n </Button>\n )}\n {code && <p className=\"font-mono text-[11px] text-white/55\">{t(\"player.code\", { code })}</p>}\n </div>\n );\n}\n"],"mappings":"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"}
|