@openrails/contentkit-ui 0.0.0-placeholder → 0.68.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 +424 -1
- package/dist/appearance.d.ts +38 -0
- package/dist/appearance.js +15 -0
- package/dist/appearance.js.map +1 -0
- package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
- package/dist/client/aspect.d.ts +6 -0
- package/dist/client/aspect.js +17 -0
- package/dist/client/aspect.js.map +1 -0
- package/dist/client/client.d.ts +51 -0
- package/dist/client/client.js +35 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/codes.d.ts +12 -0
- package/dist/client/codes.js +19 -0
- package/dist/client/codes.js.map +1 -0
- package/dist/client/content/bans.d.ts +15 -0
- package/dist/client/content/bans.js +40 -0
- package/dist/client/content/bans.js.map +1 -0
- package/dist/client/content/comments.d.ts +36 -0
- package/dist/client/content/comments.js +93 -0
- package/dist/client/content/comments.js.map +1 -0
- package/dist/client/content/favorites.d.ts +14 -0
- package/dist/client/content/favorites.js +42 -0
- package/dist/client/content/favorites.js.map +1 -0
- package/dist/client/content/moderation.d.ts +18 -0
- package/dist/client/content/moderation.js +37 -0
- package/dist/client/content/moderation.js.map +1 -0
- package/dist/client/content/polls.d.ts +47 -0
- package/dist/client/content/polls.js +148 -0
- package/dist/client/content/polls.js.map +1 -0
- package/dist/client/content/posts.d.ts +44 -0
- package/dist/client/content/posts.js +91 -0
- package/dist/client/content/posts.js.map +1 -0
- package/dist/client/content/reactions.d.ts +12 -0
- package/dist/client/content/reactions.js +36 -0
- package/dist/client/content/reactions.js.map +1 -0
- package/dist/client/content/types.d.ts +91 -0
- package/dist/client/content/types.js +8 -0
- package/dist/client/content/types.js.map +1 -0
- package/dist/client/crop.d.ts +32 -0
- package/dist/client/crop.js +103 -0
- package/dist/client/crop.js.map +1 -0
- package/dist/client/errors.d.ts +54 -0
- package/dist/client/errors.js +80 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/gallery.d.ts +43 -0
- package/dist/client/gallery.js +86 -0
- package/dist/client/gallery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +5 -0
- package/dist/client/generated/error-codes.js +131 -0
- package/dist/client/generated/error-codes.js.map +1 -0
- package/dist/client/generated/routes.d.ts +1086 -0
- package/dist/client/generated/routes.js +1400 -0
- package/dist/client/generated/routes.js.map +1 -0
- package/dist/client/generated/wire.d.ts +630 -0
- package/dist/client/generated/wire.js +6 -0
- package/dist/client/generated/wire.js.map +1 -0
- package/dist/client/http.d.ts +79 -0
- package/dist/client/http.js +74 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/image.d.ts +24 -0
- package/dist/client/image.js +65 -0
- package/dist/client/image.js.map +1 -0
- package/dist/client/index.d.ts +33 -0
- package/dist/client/media/api.d.ts +36 -0
- package/dist/client/media/api.js +61 -0
- package/dist/client/media/api.js.map +1 -0
- package/dist/client/media/client.d.ts +247 -0
- package/dist/client/media/client.js +407 -0
- package/dist/client/media/client.js.map +1 -0
- package/dist/client/media/encode.d.ts +3 -0
- package/dist/client/media/encode.js +8 -0
- package/dist/client/media/encode.js.map +1 -0
- package/dist/client/media/hash-stream.d.ts +6 -0
- package/dist/client/media/hash-stream.js +16 -0
- package/dist/client/media/hash-stream.js.map +1 -0
- package/dist/client/media/hash.d.ts +12 -0
- package/dist/client/media/hash.js +48 -0
- package/dist/client/media/hash.js.map +1 -0
- package/dist/client/media/hash.worker-inline.js +18 -0
- package/dist/client/media/hash.worker-inline.js.map +1 -0
- package/dist/client/media/pacer.d.ts +21 -0
- package/dist/client/media/pacer.js +25 -0
- package/dist/client/media/pacer.js.map +1 -0
- package/dist/client/media/queue.d.ts +112 -0
- package/dist/client/media/queue.js +274 -0
- package/dist/client/media/queue.js.map +1 -0
- package/dist/client/media/ref.d.ts +6 -0
- package/dist/client/media/ref.js +10 -0
- package/dist/client/media/ref.js.map +1 -0
- package/dist/client/media/rules.d.ts +52 -0
- package/dist/client/media/rules.js +139 -0
- package/dist/client/media/rules.js.map +1 -0
- package/dist/client/media/transport.d.ts +13 -0
- package/dist/client/media/transport.js +33 -0
- package/dist/client/media/transport.js.map +1 -0
- package/dist/client/media/windows.d.ts +15 -0
- package/dist/client/media/windows.js +39 -0
- package/dist/client/media/windows.js.map +1 -0
- package/dist/client/playback.d.ts +200 -0
- package/dist/client/playback.js +142 -0
- package/dist/client/playback.js.map +1 -0
- package/dist/client/player.d.ts +102 -0
- package/dist/client/player.js +155 -0
- package/dist/client/player.js.map +1 -0
- package/dist/client/public.d.ts +25 -0
- package/dist/client/public.js +18 -0
- package/dist/client/public.js.map +1 -0
- package/dist/client/rendition.d.ts +19 -0
- package/dist/client/rendition.js +16 -0
- package/dist/client/rendition.js.map +1 -0
- package/dist/client/route.d.ts +39 -0
- package/dist/client/route.js +31 -0
- package/dist/client/route.js.map +1 -0
- package/dist/client/taxonomy.d.ts +30 -0
- package/dist/client/taxonomy.js +77 -0
- package/dist/client/taxonomy.js.map +1 -0
- package/dist/client.js +33 -0
- package/dist/components/encode-progress.d.ts +15 -0
- package/dist/components/encode-progress.js +49 -0
- package/dist/components/encode-progress.js.map +1 -0
- package/dist/components/hover-preview.d.ts +29 -0
- package/dist/components/hover-preview.js +49 -0
- package/dist/components/hover-preview.js.map +1 -0
- package/dist/components/image-crop-dialog.d.ts +48 -0
- package/dist/components/image-crop-dialog.js +359 -0
- package/dist/components/image-crop-dialog.js.map +1 -0
- package/dist/components/lazy-mount.d.ts +10 -0
- package/dist/components/lazy-mount.js +22 -0
- package/dist/components/lazy-mount.js.map +1 -0
- package/dist/components/media-folder-editor.d.ts +45 -0
- package/dist/components/media-folder-editor.js +564 -0
- package/dist/components/media-folder-editor.js.map +1 -0
- package/dist/components/media-gallery.d.ts +54 -0
- package/dist/components/media-gallery.js +597 -0
- package/dist/components/media-gallery.js.map +1 -0
- package/dist/components/media-readiness-notice.d.ts +24 -0
- package/dist/components/media-readiness-notice.js +61 -0
- package/dist/components/media-readiness-notice.js.map +1 -0
- package/dist/components/player-controls.d.ts +71 -0
- package/dist/components/player-controls.js +264 -0
- package/dist/components/player-controls.js.map +1 -0
- package/dist/components/progress.d.ts +5 -0
- package/dist/components/progress.js +14 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/rendition-img.d.ts +34 -0
- package/dist/components/rendition-img.js +78 -0
- package/dist/components/rendition-img.js.map +1 -0
- package/dist/components/slot-editor.d.ts +95 -0
- package/dist/components/slot-editor.js +182 -0
- package/dist/components/slot-editor.js.map +1 -0
- package/dist/components/slot-image.d.ts +23 -0
- package/dist/components/slot-image.js +48 -0
- package/dist/components/slot-image.js.map +1 -0
- package/dist/components/slot-upload.d.ts +41 -0
- package/dist/components/slot-upload.js +213 -0
- package/dist/components/slot-upload.js.map +1 -0
- package/dist/components/social/bans.d.ts +42 -0
- package/dist/components/social/bans.js +300 -0
- package/dist/components/social/bans.js.map +1 -0
- package/dist/components/social/comments.d.ts +42 -0
- package/dist/components/social/comments.js +517 -0
- package/dist/components/social/comments.js.map +1 -0
- package/dist/components/social/engagement.d.ts +45 -0
- package/dist/components/social/engagement.js +129 -0
- package/dist/components/social/engagement.js.map +1 -0
- package/dist/components/social/index.d.ts +6 -0
- package/dist/components/social/moderation.d.ts +32 -0
- package/dist/components/social/moderation.js +383 -0
- package/dist/components/social/moderation.js.map +1 -0
- package/dist/components/social/parts.d.ts +50 -0
- package/dist/components/social/parts.js +106 -0
- package/dist/components/social/parts.js.map +1 -0
- package/dist/components/social/poll-editor.d.ts +34 -0
- package/dist/components/social/poll-editor.js +567 -0
- package/dist/components/social/poll-editor.js.map +1 -0
- package/dist/components/social/poll.d.ts +30 -0
- package/dist/components/social/poll.js +252 -0
- package/dist/components/social/poll.js.map +1 -0
- package/dist/components/sortable-list.d.ts +29 -0
- package/dist/components/sortable-list.js +121 -0
- package/dist/components/sortable-list.js.map +1 -0
- package/dist/components/video-mini-player.d.ts +35 -0
- package/dist/components/video-mini-player.js +236 -0
- package/dist/components/video-mini-player.js.map +1 -0
- package/dist/components/video-player.d.ts +146 -0
- package/dist/components/video-player.js +630 -0
- package/dist/components/video-player.js.map +1 -0
- package/dist/components/video-poster-picker.d.ts +59 -0
- package/dist/components/video-poster-picker.js +335 -0
- package/dist/components/video-poster-picker.js.map +1 -0
- package/dist/components/video-poster.d.ts +24 -0
- package/dist/components/video-poster.js +46 -0
- package/dist/components/video-poster.js.map +1 -0
- package/dist/i18n/context.d.ts +4 -0
- package/dist/i18n/context.js +11 -0
- package/dist/i18n/context.js.map +1 -0
- package/dist/i18n/messages.d.ts +40 -0
- package/dist/i18n/messages.js +94 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +41 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/de.js +271 -0
- package/dist/locales/de.js.map +1 -0
- package/dist/locales/en.d.ts +457 -0
- package/dist/locales/en.js +271 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/locales/es.d.ts +2 -0
- package/dist/locales/es.js +271 -0
- package/dist/locales/es.js.map +1 -0
- package/dist/locales/ja.d.ts +2 -0
- package/dist/locales/ja.js +271 -0
- package/dist/locales/ja.js.map +1 -0
- package/dist/locales/ko.d.ts +2 -0
- package/dist/locales/ko.js +271 -0
- package/dist/locales/ko.js.map +1 -0
- package/dist/locales/social/de.d.ts +2 -0
- package/dist/locales/social/de.js +198 -0
- package/dist/locales/social/de.js.map +1 -0
- package/dist/locales/social/en.d.ts +195 -0
- package/dist/locales/social/en.js +198 -0
- package/dist/locales/social/en.js.map +1 -0
- package/dist/locales/social/es.d.ts +2 -0
- package/dist/locales/social/es.js +198 -0
- package/dist/locales/social/es.js.map +1 -0
- package/dist/locales/social/ja.d.ts +2 -0
- package/dist/locales/social/ja.js +198 -0
- package/dist/locales/social/ja.js.map +1 -0
- package/dist/locales/social/ko.d.ts +2 -0
- package/dist/locales/social/ko.js +198 -0
- package/dist/locales/social/ko.js.map +1 -0
- package/dist/locales/social/zh.d.ts +2 -0
- package/dist/locales/social/zh.js +198 -0
- package/dist/locales/social/zh.js.map +1 -0
- package/dist/locales/zh.d.ts +2 -0
- package/dist/locales/zh.js +271 -0
- package/dist/locales/zh.js.map +1 -0
- package/dist/provider.d.ts +22 -0
- package/dist/provider.js +59 -0
- package/dist/provider.js.map +1 -0
- package/dist/react/canonical.d.ts +33 -0
- package/dist/react/canonical.js +88 -0
- package/dist/react/canonical.js.map +1 -0
- package/dist/react/comments.d.ts +82 -0
- package/dist/react/comments.js +147 -0
- package/dist/react/comments.js.map +1 -0
- package/dist/react/config.d.ts +13 -0
- package/dist/react/config.js +14 -0
- package/dist/react/config.js.map +1 -0
- package/dist/react/context.d.ts +48 -0
- package/dist/react/context.js +37 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/crop.d.ts +49 -0
- package/dist/react/crop.js +107 -0
- package/dist/react/crop.js.map +1 -0
- package/dist/react/engagement.d.ts +38 -0
- package/dist/react/engagement.js +88 -0
- package/dist/react/engagement.js.map +1 -0
- package/dist/react/folder.d.ts +83 -0
- package/dist/react/folder.js +183 -0
- package/dist/react/folder.js.map +1 -0
- package/dist/react/gallery.d.ts +140 -0
- package/dist/react/gallery.js +477 -0
- package/dist/react/gallery.js.map +1 -0
- package/dist/react/index.d.ts +21 -0
- package/dist/react/inline-preview.d.ts +24 -0
- package/dist/react/inline-preview.js +76 -0
- package/dist/react/inline-preview.js.map +1 -0
- package/dist/react/player.d.ts +70 -0
- package/dist/react/player.js +214 -0
- package/dist/react/player.js.map +1 -0
- package/dist/react/polls.d.ts +74 -0
- package/dist/react/polls.js +176 -0
- package/dist/react/polls.js.map +1 -0
- package/dist/react/posts.d.ts +59 -0
- package/dist/react/posts.js +149 -0
- package/dist/react/posts.js.map +1 -0
- package/dist/react/provider.d.ts +23 -0
- package/dist/react/provider.js +35 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/public.d.ts +39 -0
- package/dist/react/public.js +70 -0
- package/dist/react/public.js.map +1 -0
- package/dist/react/read.d.ts +52 -0
- package/dist/react/read.js +143 -0
- package/dist/react/read.js.map +1 -0
- package/dist/react/resources.d.ts +70 -0
- package/dist/react/resources.js +247 -0
- package/dist/react/resources.js.map +1 -0
- package/dist/react/slot.d.ts +102 -0
- package/dist/react/slot.js +181 -0
- package/dist/react/slot.js.map +1 -0
- package/dist/react/store.d.ts +51 -0
- package/dist/react/store.js +164 -0
- package/dist/react/store.js.map +1 -0
- package/dist/react/upload.d.ts +85 -0
- package/dist/react/upload.js +150 -0
- package/dist/react/upload.js.map +1 -0
- package/dist/react/use-resource.d.ts +43 -0
- package/dist/react/use-resource.js +66 -0
- package/dist/react/use-resource.js.map +1 -0
- package/dist/react/video.d.ts +118 -0
- package/dist/react/video.js +184 -0
- package/dist/react/video.js.map +1 -0
- package/dist/react/viewport.d.ts +19 -0
- package/dist/react/viewport.js +30 -0
- package/dist/react/viewport.js.map +1 -0
- package/dist/react.js +21 -0
- package/dist/scope.d.ts +14 -0
- package/dist/scope.js +30 -0
- package/dist/scope.js.map +1 -0
- package/dist/styles.css +3 -0
- package/dist/ui/alert-dialog.d.ts +18 -0
- package/dist/ui/alert-dialog.js +86 -0
- package/dist/ui/alert-dialog.js.map +1 -0
- package/dist/ui/alert.d.ts +10 -0
- package/dist/ui/alert.js +38 -0
- package/dist/ui/alert.js.map +1 -0
- package/dist/ui/avatar.d.ts +11 -0
- package/dist/ui/avatar.js +31 -0
- package/dist/ui/avatar.js.map +1 -0
- package/dist/ui/badge.d.ts +7 -0
- package/dist/ui/badge.js +31 -0
- package/dist/ui/badge.js.map +1 -0
- package/dist/ui/button.d.ts +8 -0
- package/dist/ui/button.js +46 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/dialog.d.ts +17 -0
- package/dist/ui/dialog.js +91 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/dropdown-menu.d.ts +15 -0
- package/dist/ui/dropdown-menu.js +105 -0
- package/dist/ui/dropdown-menu.js.map +1 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.js +17 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/label.d.ts +3 -0
- package/dist/ui/label.js +15 -0
- package/dist/ui/label.js.map +1 -0
- package/dist/ui/progress.d.ts +7 -0
- package/dist/ui/progress.js +31 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/select.d.ts +15 -0
- package/dist/ui/select.js +98 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/skeleton.d.ts +2 -0
- package/dist/ui/skeleton.js +14 -0
- package/dist/ui/skeleton.js.map +1 -0
- package/dist/ui/slider.d.ts +3 -0
- package/dist/ui/slider.js +35 -0
- package/dist/ui/slider.js.map +1 -0
- package/dist/ui/switch.d.ts +5 -0
- package/dist/ui/switch.js +20 -0
- package/dist/ui/switch.js.map +1 -0
- package/dist/ui/tabs.d.ts +10 -0
- package/dist/ui/tabs.js +47 -0
- package/dist/ui/tabs.js.map +1 -0
- package/dist/ui/textarea.d.ts +3 -0
- package/dist/ui/textarea.js +15 -0
- package/dist/ui/textarea.js.map +1 -0
- package/dist/ui/toggle-group.d.ts +5 -0
- package/dist/ui/toggle-group.js +23 -0
- package/dist/ui/toggle-group.js.map +1 -0
- package/dist/urls/index.d.ts +100 -0
- package/dist/urls.js +118 -0
- package/dist/urls.js.map +1 -0
- package/package.json +113 -3
|
@@ -0,0 +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 { 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/**\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 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 = tr.label || 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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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AspectRatio } from '../client/aspect.js';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ContentKitClient } from '../client/client.js';
|
|
4
|
+
import { CropSource } from '../client/image.js';
|
|
5
|
+
import { ContentKitErrorHandler } from '../react/context.js';
|
|
6
|
+
import { VideoSaveState } from '../react/video.js';
|
|
7
|
+
import { PublicPreset } from '../client/public.js';
|
|
8
|
+
import { FileInfo, ReadResult, RefBody } from '../client/generated/wire.js';
|
|
9
|
+
export interface VideoPickerProps {
|
|
10
|
+
open: boolean;
|
|
11
|
+
onOpenChange: (open: boolean) => void;
|
|
12
|
+
/** The video item. */
|
|
13
|
+
item: RefBody;
|
|
14
|
+
/** The video upload path. Default "source". */
|
|
15
|
+
video?: string;
|
|
16
|
+
/** The poster upload path (its Upload.Frames is the video). Default "poster". */
|
|
17
|
+
path?: string;
|
|
18
|
+
/** The poster's public preset, refetched after a save. */
|
|
19
|
+
image?: PublicPreset | null;
|
|
20
|
+
/**
|
|
21
|
+
* Frames from another item's video (its upload at path, default
|
|
22
|
+
* "source"): a chosen frame is uploaded to the poster path as an image.
|
|
23
|
+
* Default the item's own video.
|
|
24
|
+
*/
|
|
25
|
+
frames?: {
|
|
26
|
+
item: RefBody;
|
|
27
|
+
path?: string;
|
|
28
|
+
} | null;
|
|
29
|
+
/** The cover's "W:H"; default the video's shape. */
|
|
30
|
+
aspect?: AspectRatio;
|
|
31
|
+
client?: ContentKitClient;
|
|
32
|
+
/** An editor read of the item from the host; otherwise fetched. */
|
|
33
|
+
read?: ReadResult | null;
|
|
34
|
+
/** Called with the processed poster upload after a save. */
|
|
35
|
+
onChange?: (poster: FileInfo) => void;
|
|
36
|
+
/** Every failure (load, frame grab, save or render); default the provider's. The dialog shows it too. */
|
|
37
|
+
onError?: ContentKitErrorHandler;
|
|
38
|
+
title?: ReactNode;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface VideoPosterPickerProps extends VideoPickerProps {
|
|
42
|
+
/** `accept` of the image input. Default "image/*". */
|
|
43
|
+
accept?: string;
|
|
44
|
+
/** Replaces decodeImage (tests). */
|
|
45
|
+
decode?: (file: File) => Promise<CropSource>;
|
|
46
|
+
}
|
|
47
|
+
/** m:ss.s */
|
|
48
|
+
export declare function formatTime(s: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Sets a video's cover (its poster upload): scrub to an exact frame (a coarse
|
|
51
|
+
* strip, a slider and frame steps over GET /frame) and use it whole or
|
|
52
|
+
* cropped (a frame op), or upload an image and crop it at the video's aspect
|
|
53
|
+
* (a put), or let the worker choose the frame. Covers keep the video's aspect.
|
|
54
|
+
*/
|
|
55
|
+
export declare function VideoPosterPicker(p: VideoPosterPickerProps): import('react').JSX.Element;
|
|
56
|
+
export declare function SaveProgress({ state, label }: {
|
|
57
|
+
state: VideoSaveState;
|
|
58
|
+
label: string;
|
|
59
|
+
}): import('react').JSX.Element | null;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { aspectOf as e, ratio as t } from "../client/aspect.js";
|
|
2
|
+
import { useMessages as n } from "../i18n/context.js";
|
|
3
|
+
import { useContentKitClient as r, useErrorReporter as i } from "../react/context.js";
|
|
4
|
+
import { Progress as a } from "../ui/progress.js";
|
|
5
|
+
import { decodeImage as o } from "../client/image.js";
|
|
6
|
+
import { Alert as ee, AlertDescription as te } from "../ui/alert.js";
|
|
7
|
+
import { Button as s } from "../ui/button.js";
|
|
8
|
+
import { Dialog as c, DialogContent as l, DialogDescription as u, DialogFooter as ne, DialogHeader as d, DialogTitle as f } from "../ui/dialog.js";
|
|
9
|
+
import { Slider as re } from "../ui/slider.js";
|
|
10
|
+
import { progressLabel as p, progressValue as m } from "./progress.js";
|
|
11
|
+
import { ImageCropDialog as ie } from "./image-crop-dialog.js";
|
|
12
|
+
import { useFrameStrip as ae, useVideoFrame as oe, useVideoImages as h, useVideoPoster as se } from "../react/video.js";
|
|
13
|
+
import { useEffect as g, useRef as ce, useState as _ } from "react";
|
|
14
|
+
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
15
|
+
import { cn as x } from "cn";
|
|
16
|
+
import { Alert02Icon as le, ArrowLeft01Icon as ue, ArrowRight01Icon as de, ImageUpload01Icon as S, Loading03Icon as C, Video01Icon as fe } from "@hugeicons/core-free-icons";
|
|
17
|
+
import { HugeiconsIcon as w } from "@hugeicons/react";
|
|
18
|
+
//#region src/components/video-poster-picker.tsx
|
|
19
|
+
var T = 1 / 30;
|
|
20
|
+
function E(e) {
|
|
21
|
+
let t = Math.floor(e / 60), n = e - t * 60;
|
|
22
|
+
return `${t}:${n < 10 ? "0" : ""}${n.toFixed(1)}`;
|
|
23
|
+
}
|
|
24
|
+
function D(e) {
|
|
25
|
+
let { t } = n(), [r, i] = _(!1);
|
|
26
|
+
return /* @__PURE__ */ y(c, {
|
|
27
|
+
open: e.open,
|
|
28
|
+
onOpenChange: (t) => !r && e.onOpenChange(t),
|
|
29
|
+
children: /* @__PURE__ */ b(l, {
|
|
30
|
+
className: x("gap-0 overflow-hidden p-0 sm:max-w-2xl", e.className),
|
|
31
|
+
showCloseButton: !r,
|
|
32
|
+
"data-ckui": "poster-picker",
|
|
33
|
+
children: [/* @__PURE__ */ b(d, {
|
|
34
|
+
className: "px-5 pt-5 pb-4 pr-12",
|
|
35
|
+
children: [/* @__PURE__ */ y(f, { children: e.title ?? t("poster.title") }), /* @__PURE__ */ y(u, { children: t("poster.description") })]
|
|
36
|
+
}), e.open && /* @__PURE__ */ y(O, {
|
|
37
|
+
...e,
|
|
38
|
+
onSaving: i
|
|
39
|
+
})]
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function O(a) {
|
|
44
|
+
let { t: c, error: l } = n(), u = r(a.client), d = i(a.onError), f = h({
|
|
45
|
+
client: u,
|
|
46
|
+
ref: a.item,
|
|
47
|
+
video: a.video,
|
|
48
|
+
poster: a.path,
|
|
49
|
+
read: a.read
|
|
50
|
+
}), p = a.frames ?? null, m = h({
|
|
51
|
+
client: u,
|
|
52
|
+
ref: p?.item ?? a.item,
|
|
53
|
+
video: p?.path ?? a.video,
|
|
54
|
+
poster: a.path,
|
|
55
|
+
read: p ? void 0 : a.read
|
|
56
|
+
}), C = p?.item ?? a.item;
|
|
57
|
+
g(() => void (f.error && d(f.error, "poster.load")), [f.error, d]);
|
|
58
|
+
let D = p ? m.error : void 0;
|
|
59
|
+
g(() => void (D && d(D, "poster.load")), [D, d]);
|
|
60
|
+
let { poster: O } = f, j = m.video, M = p ? void 0 : O?.frame, [N, pe] = _(!p && O && !M ? "upload" : "frame"), [me, P] = _(), [F, I] = _(null), [L, he] = _("frame"), [ge, R] = _(), _e = ce(null), z = j && !j.staged ? j.dur ?? 0 : 0, B = a.aspect ?? (j?.w && j.h ? e(j.w, j.h) : "16:9"), V = me ?? M?.t ?? z * .25, H = z > 0 ? j.path : void 0, U = se({
|
|
61
|
+
client: u,
|
|
62
|
+
ref: a.item,
|
|
63
|
+
path: a.path,
|
|
64
|
+
onSaved: (e) => {
|
|
65
|
+
f.set(e), a.onChange?.(e), a.onOpenChange(!1);
|
|
66
|
+
},
|
|
67
|
+
onError: (e) => d(e, "poster.save")
|
|
68
|
+
}), W = U.state.status === "saving", G = ce(a.onSaving);
|
|
69
|
+
G.current = a.onSaving, g(() => G.current(W), [W]);
|
|
70
|
+
let K = (e) => d(e, "poster.frame"), q = oe({
|
|
71
|
+
client: u,
|
|
72
|
+
ref: C,
|
|
73
|
+
path: H,
|
|
74
|
+
time: z > 0 ? V : void 0,
|
|
75
|
+
width: 960,
|
|
76
|
+
onError: K
|
|
77
|
+
}), ve = ae({
|
|
78
|
+
client: u,
|
|
79
|
+
ref: C,
|
|
80
|
+
path: H,
|
|
81
|
+
duration: z,
|
|
82
|
+
count: 8,
|
|
83
|
+
width: 160,
|
|
84
|
+
onError: K
|
|
85
|
+
}), J = (e, t) => p ? U.saveFrameFrom(C, H, e, t) : U.saveFrame(e, t);
|
|
86
|
+
g(() => () => F?.revoke?.(), [F]);
|
|
87
|
+
let Y = async (e, t) => {
|
|
88
|
+
R(void 0);
|
|
89
|
+
try {
|
|
90
|
+
if (e === "upload") I(await (a.decode ?? o)(t));
|
|
91
|
+
else {
|
|
92
|
+
let e = await u.media.getFrame(C, H, V, 1280), t = URL.createObjectURL(e);
|
|
93
|
+
I({
|
|
94
|
+
url: t,
|
|
95
|
+
width: j.w,
|
|
96
|
+
height: j.h,
|
|
97
|
+
revoke: () => URL.revokeObjectURL(t)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
he(e);
|
|
101
|
+
} catch (t) {
|
|
102
|
+
R(l(t)), d(t, e === "upload" ? "slot.decode" : "poster.frame");
|
|
103
|
+
}
|
|
104
|
+
}, X = (e) => e && void Y("upload", e), [ye, Z] = _(!1), be = {
|
|
105
|
+
onDragOver: (e) => {
|
|
106
|
+
!W && e.dataTransfer.types.includes("Files") && (e.preventDefault(), Z(!0));
|
|
107
|
+
},
|
|
108
|
+
onDragLeave: () => Z(!1),
|
|
109
|
+
onDrop: (e) => {
|
|
110
|
+
e.preventDefault(), Z(!1), X(e.dataTransfer.files[0]);
|
|
111
|
+
}
|
|
112
|
+
}, Q = U.state.status === "error" ? U.state.error : f.error ?? D ?? (q.error && !q.url ? q.error : void 0), $ = ge ?? (Q ? l(Q) : void 0);
|
|
113
|
+
return /* @__PURE__ */ b(v, { children: [
|
|
114
|
+
/* @__PURE__ */ b("div", {
|
|
115
|
+
className: "grid gap-4 px-5",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ y("div", {
|
|
118
|
+
className: "inline-flex w-fit rounded-lg bg-muted p-0.5",
|
|
119
|
+
role: "tablist",
|
|
120
|
+
"aria-label": c("poster.title"),
|
|
121
|
+
children: ["frame", "upload"].map((e) => /* @__PURE__ */ b("button", {
|
|
122
|
+
type: "button",
|
|
123
|
+
role: "tab",
|
|
124
|
+
"aria-selected": N === e,
|
|
125
|
+
disabled: W,
|
|
126
|
+
onClick: () => pe(e),
|
|
127
|
+
className: x("inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-sm font-medium text-muted-foreground transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/50", N === e && "bg-background text-foreground shadow-xs"),
|
|
128
|
+
children: [/* @__PURE__ */ y(w, {
|
|
129
|
+
icon: e === "frame" ? fe : S,
|
|
130
|
+
strokeWidth: 2,
|
|
131
|
+
className: "size-4"
|
|
132
|
+
}), c(e === "frame" ? "poster.frame" : "poster.upload")]
|
|
133
|
+
}, e))
|
|
134
|
+
}),
|
|
135
|
+
N === "frame" && (m.loading && !j ? /* @__PURE__ */ y(k, {
|
|
136
|
+
loading: !0,
|
|
137
|
+
aspect: B
|
|
138
|
+
}) : z ? /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(k, {
|
|
139
|
+
url: q.url,
|
|
140
|
+
loading: q.loading || !q.url && !q.error,
|
|
141
|
+
aspect: B
|
|
142
|
+
}), /* @__PURE__ */ b("div", {
|
|
143
|
+
className: "grid gap-2",
|
|
144
|
+
children: [/* @__PURE__ */ b("div", {
|
|
145
|
+
className: "flex items-center gap-1.5",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ y(s, {
|
|
148
|
+
variant: "ghost",
|
|
149
|
+
size: "icon-sm",
|
|
150
|
+
"aria-label": c("poster.previousFrame"),
|
|
151
|
+
disabled: W || V <= 0,
|
|
152
|
+
onClick: () => P(Math.max(0, V - T)),
|
|
153
|
+
children: /* @__PURE__ */ y(w, {
|
|
154
|
+
icon: ue,
|
|
155
|
+
strokeWidth: 2
|
|
156
|
+
})
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ y(re, {
|
|
159
|
+
"aria-label": c("poster.time"),
|
|
160
|
+
className: "mx-1 flex-1",
|
|
161
|
+
min: 0,
|
|
162
|
+
max: z,
|
|
163
|
+
step: .01,
|
|
164
|
+
value: V,
|
|
165
|
+
disabled: W,
|
|
166
|
+
onValueChange: (e) => P(Array.isArray(e) ? e[0] : e)
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ y(s, {
|
|
169
|
+
variant: "ghost",
|
|
170
|
+
size: "icon-sm",
|
|
171
|
+
"aria-label": c("poster.nextFrame"),
|
|
172
|
+
disabled: W || V >= z,
|
|
173
|
+
onClick: () => P(Math.min(z, V + T)),
|
|
174
|
+
children: /* @__PURE__ */ y(w, {
|
|
175
|
+
icon: de,
|
|
176
|
+
strokeWidth: 2
|
|
177
|
+
})
|
|
178
|
+
}),
|
|
179
|
+
/* @__PURE__ */ y("span", {
|
|
180
|
+
className: "w-14 text-right text-xs text-muted-foreground tabular-nums",
|
|
181
|
+
"data-ckui": "time",
|
|
182
|
+
children: E(V)
|
|
183
|
+
})
|
|
184
|
+
]
|
|
185
|
+
}), /* @__PURE__ */ y("div", {
|
|
186
|
+
className: "grid grid-cols-8 gap-1",
|
|
187
|
+
"data-ckui": "frame-strip",
|
|
188
|
+
children: ve.map((e) => {
|
|
189
|
+
let n = Math.abs(e.time - V) <= z / 16;
|
|
190
|
+
return /* @__PURE__ */ y("button", {
|
|
191
|
+
type: "button",
|
|
192
|
+
disabled: W,
|
|
193
|
+
"aria-label": c("poster.jumpTo", { time: E(e.time) }),
|
|
194
|
+
onClick: () => P(e.time),
|
|
195
|
+
style: { aspectRatio: String(t(B)) },
|
|
196
|
+
className: x("relative overflow-hidden rounded-md bg-muted outline-none ring-offset-1 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring", n && "ring-2 ring-primary"),
|
|
197
|
+
children: e.url && /* @__PURE__ */ y("img", {
|
|
198
|
+
src: e.url,
|
|
199
|
+
alt: "",
|
|
200
|
+
className: "absolute inset-0 size-full object-contain"
|
|
201
|
+
})
|
|
202
|
+
}, e.time);
|
|
203
|
+
})
|
|
204
|
+
})]
|
|
205
|
+
})] }) : /* @__PURE__ */ y("p", {
|
|
206
|
+
className: "rounded-lg bg-muted px-4 py-6 text-center text-sm text-muted-foreground",
|
|
207
|
+
"data-ckui": "processing",
|
|
208
|
+
children: c("poster.processing")
|
|
209
|
+
})),
|
|
210
|
+
N === "upload" && /* @__PURE__ */ b("label", {
|
|
211
|
+
className: x("flex aspect-video cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border text-sm text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5", ye && "border-primary bg-primary/5", W && "pointer-events-none opacity-60"),
|
|
212
|
+
"data-ckui": "poster-drop",
|
|
213
|
+
...be,
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ y(w, {
|
|
216
|
+
icon: S,
|
|
217
|
+
strokeWidth: 1.5,
|
|
218
|
+
className: "size-8"
|
|
219
|
+
}),
|
|
220
|
+
c("poster.drop"),
|
|
221
|
+
/* @__PURE__ */ y("input", {
|
|
222
|
+
ref: _e,
|
|
223
|
+
type: "file",
|
|
224
|
+
accept: a.accept ?? "image/*",
|
|
225
|
+
className: "sr-only",
|
|
226
|
+
"data-ckui": "file",
|
|
227
|
+
disabled: W,
|
|
228
|
+
onChange: (e) => {
|
|
229
|
+
let t = e.target.files?.[0];
|
|
230
|
+
e.target.value = "", X(t);
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
]
|
|
234
|
+
}),
|
|
235
|
+
$ && /* @__PURE__ */ b(ee, {
|
|
236
|
+
variant: "destructive",
|
|
237
|
+
role: "alert",
|
|
238
|
+
children: [/* @__PURE__ */ y(w, {
|
|
239
|
+
icon: le,
|
|
240
|
+
strokeWidth: 2
|
|
241
|
+
}), /* @__PURE__ */ y(te, { children: $ })]
|
|
242
|
+
}),
|
|
243
|
+
/* @__PURE__ */ y(A, {
|
|
244
|
+
state: U.state,
|
|
245
|
+
label: c("poster.rendering")
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
}),
|
|
249
|
+
/* @__PURE__ */ b(ne, {
|
|
250
|
+
className: "px-5 pt-4 pb-5 sm:justify-between",
|
|
251
|
+
children: [p ? /* @__PURE__ */ y("span", {}) : /* @__PURE__ */ y(s, {
|
|
252
|
+
variant: "ghost",
|
|
253
|
+
disabled: W || !!M?.auto,
|
|
254
|
+
onClick: () => void U.saveAuto(),
|
|
255
|
+
title: c("poster.autoHint"),
|
|
256
|
+
children: c("poster.auto")
|
|
257
|
+
}), /* @__PURE__ */ b("div", {
|
|
258
|
+
className: "flex flex-col-reverse gap-2 sm:flex-row",
|
|
259
|
+
children: [/* @__PURE__ */ y(s, {
|
|
260
|
+
variant: "outline",
|
|
261
|
+
disabled: W,
|
|
262
|
+
onClick: () => a.onOpenChange(!1),
|
|
263
|
+
children: c("common.cancel")
|
|
264
|
+
}), N === "frame" && z > 0 && /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(s, {
|
|
265
|
+
variant: "outline",
|
|
266
|
+
disabled: W,
|
|
267
|
+
onClick: () => void Y("frame"),
|
|
268
|
+
children: c("poster.crop")
|
|
269
|
+
}), /* @__PURE__ */ y(s, {
|
|
270
|
+
disabled: W,
|
|
271
|
+
onClick: () => void J(V),
|
|
272
|
+
children: c(W ? "common.saving" : "poster.useFrame")
|
|
273
|
+
})] })]
|
|
274
|
+
})]
|
|
275
|
+
}),
|
|
276
|
+
/* @__PURE__ */ y(ie, {
|
|
277
|
+
open: !!F,
|
|
278
|
+
onOpenChange: (e) => !e && !W && I(null),
|
|
279
|
+
source: F,
|
|
280
|
+
aspect: B,
|
|
281
|
+
rotatable: L === "upload",
|
|
282
|
+
targetWidth: 1920,
|
|
283
|
+
title: c("poster.cropTitle"),
|
|
284
|
+
busy: W,
|
|
285
|
+
progress: U.state.status === "saving" ? U.state.progress : void 0,
|
|
286
|
+
rendering: U.state.status === "saving" && U.state.rendering,
|
|
287
|
+
error: U.state.status === "error" && F ? l(U.state.error) : void 0,
|
|
288
|
+
onConfirm: (e) => {
|
|
289
|
+
L === "frame" ? J(V, e) : F?.file && U.saveUpload(F.file, e);
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
] });
|
|
293
|
+
}
|
|
294
|
+
function k({ url: e, loading: n, aspect: r }) {
|
|
295
|
+
let i = t(r) ?? 16 / 9;
|
|
296
|
+
return /* @__PURE__ */ b("div", {
|
|
297
|
+
className: "relative mx-auto max-h-[50svh] max-w-full overflow-hidden rounded-lg bg-zinc-950",
|
|
298
|
+
style: {
|
|
299
|
+
aspectRatio: String(i),
|
|
300
|
+
width: `min(100%, calc(50svh * ${i}))`
|
|
301
|
+
},
|
|
302
|
+
"data-ckui": "frame-stage",
|
|
303
|
+
children: [e && /* @__PURE__ */ y("img", {
|
|
304
|
+
src: e,
|
|
305
|
+
alt: "",
|
|
306
|
+
className: "absolute inset-0 size-full object-contain"
|
|
307
|
+
}), n && /* @__PURE__ */ y("div", {
|
|
308
|
+
className: "absolute inset-0 flex items-center justify-center text-white/70",
|
|
309
|
+
children: /* @__PURE__ */ y(w, {
|
|
310
|
+
icon: C,
|
|
311
|
+
strokeWidth: 2,
|
|
312
|
+
className: "size-6 animate-spin"
|
|
313
|
+
})
|
|
314
|
+
})]
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function A({ state: e, label: t }) {
|
|
318
|
+
let { t: r } = n();
|
|
319
|
+
if (e.status !== "saving") return null;
|
|
320
|
+
let i = e.rendering || !e.progress, o = i ? t : p(r, e.progress, !1);
|
|
321
|
+
return /* @__PURE__ */ y(a, {
|
|
322
|
+
value: i ? null : m(e.progress, !1),
|
|
323
|
+
className: "gap-2",
|
|
324
|
+
"aria-label": o,
|
|
325
|
+
"data-ckui": "save-progress",
|
|
326
|
+
children: /* @__PURE__ */ y("span", {
|
|
327
|
+
className: "text-xs text-muted-foreground",
|
|
328
|
+
children: o
|
|
329
|
+
})
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { A as SaveProgress, D as VideoPosterPicker, E as formatTime };
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=video-poster-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-poster-picker.js","names":[],"sources":["../../src/components/video-poster-picker.tsx"],"sourcesContent":["import { aspectOf, ratio, type AspectRatio } from \"../client/aspect.js\";\nimport { Alert02Icon, ArrowLeft01Icon, ArrowRight01Icon, ImageUpload01Icon, Loading03Icon, Video01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useRef, useState, type DragEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { decodeImage, type CropSource } from \"../client/image.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../react/context.js\";\nimport { useFrameStrip, useVideoFrame, useVideoImages, useVideoPoster, type VideoSaveState } from \"../react/video.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport type { Edit, FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from \"#ckui/ui/dialog\";\nimport { Progress } from \"#ckui/ui/progress\";\nimport { Slider } from \"#ckui/ui/slider\";\nimport { ImageCropDialog } from \"./image-crop-dialog.js\";\nimport { progressLabel, progressValue } from \"./progress.js\";\n\nconst FRAME_STEP = 1 / 30;\n\nexport interface VideoPickerProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** The video item. */\n item: RefBody;\n /** The video upload path. Default \"source\". */\n video?: string;\n /** The poster upload path (its Upload.Frames is the video). Default \"poster\". */\n path?: string;\n /** The poster's public preset, refetched after a save. */\n image?: PublicPreset | null;\n /**\n * Frames from another item's video (its upload at path, default\n * \"source\"): a chosen frame is uploaded to the poster path as an image.\n * Default the item's own video.\n */\n frames?: { item: RefBody; path?: string } | null;\n /** The cover's \"W:H\"; default the video's shape. */\n aspect?: AspectRatio;\n client?: ContentKitClient;\n /** An editor read of the item from the host; otherwise fetched. */\n read?: ReadResult | null;\n /** Called with the processed poster upload after a save. */\n onChange?: (poster: FileInfo) => void;\n /** Every failure (load, frame grab, save or render); default the provider's. The dialog shows it too. */\n onError?: ContentKitErrorHandler;\n title?: ReactNode;\n className?: string;\n}\n\nexport interface VideoPosterPickerProps extends VideoPickerProps {\n /** `accept` of the image input. Default \"image/*\". */\n accept?: string;\n /** Replaces decodeImage (tests). */\n decode?: (file: File) => Promise<CropSource>;\n}\n\n/** m:ss.s */\nexport function formatTime(s: number): string {\n const m = Math.floor(s / 60);\n const sec = s - m * 60;\n return `${m}:${sec < 10 ? \"0\" : \"\"}${sec.toFixed(1)}`;\n}\n\n/**\n * Sets a video's cover (its poster upload): scrub to an exact frame (a coarse\n * strip, a slider and frame steps over GET /frame) and use it whole or\n * cropped (a frame op), or upload an image and crop it at the video's aspect\n * (a put), or let the worker choose the frame. Covers keep the video's aspect.\n */\nexport function VideoPosterPicker(p: VideoPosterPickerProps) {\n const { t } = useMessages();\n const [saving, setSaving] = useState(false);\n return (\n <Dialog open={p.open} onOpenChange={(o) => !saving && p.onOpenChange(o)}>\n <DialogContent className={cn(\"gap-0 overflow-hidden p-0 sm:max-w-2xl\", p.className)} showCloseButton={!saving} data-ckui=\"poster-picker\">\n <DialogHeader className=\"px-5 pt-5 pb-4 pr-12\">\n <DialogTitle>{p.title ?? t(\"poster.title\")}</DialogTitle>\n <DialogDescription>{t(\"poster.description\")}</DialogDescription>\n </DialogHeader>\n {p.open && <PosterBody {...p} onSaving={setSaving} />}\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction PosterBody(p: VideoPosterPickerProps & { onSaving: (b: boolean) => void }) {\n const { t, error: errorText } = useMessages();\n const client = useContentKitClient(p.client);\n const report = useErrorReporter(p.onError);\n const loaded = useVideoImages({ client, ref: p.item, video: p.video, poster: p.path, read: p.read });\n const other = p.frames ?? null;\n const source = useVideoImages({ client, ref: other?.item ?? p.item, video: other?.path ?? p.video, poster: p.path, read: other ? undefined : p.read });\n const sourceRef = other?.item ?? p.item;\n useEffect(() => void (loaded.error && report(loaded.error, \"poster.load\")), [loaded.error, report]);\n const sourceError = other ? source.error : undefined;\n useEffect(() => void (sourceError && report(sourceError, \"poster.load\")), [sourceError, report]);\n const { poster: current } = loaded;\n const video = source.video;\n // A frame of the item's own video is a frame op; one of another video is an uploaded image.\n const selection = other ? undefined : current?.frame;\n const [mode, setMode] = useState<\"frame\" | \"upload\">(!other && current && !selection ? \"upload\" : \"frame\");\n const [time, setTime] = useState<number>();\n const [crop, setCrop] = useState<CropSource | null>(null);\n const [cropFor, setCropFor] = useState<\"frame\" | \"upload\">(\"frame\");\n const [decodeError, setDecodeError] = useState<string>();\n const input = useRef<HTMLInputElement>(null);\n const duration = video && !video.staged ? (video.dur ?? 0) : 0;\n const aspect = p.aspect ?? (video?.w && video.h ? aspectOf(video.w, video.h) : \"16:9\");\n const shown = time ?? selection?.t ?? duration * 0.25;\n // Frames come from the placed video (GET /frame answers conflict while it is staged).\n const path = duration > 0 ? video!.path : undefined;\n const poster = useVideoPoster({\n client,\n ref: p.item,\n path: p.path,\n onSaved: (f) => {\n loaded.set(f);\n p.onChange?.(f);\n p.onOpenChange(false);\n },\n onError: (e) => report(e, \"poster.save\"),\n });\n const busy = poster.state.status === \"saving\";\n const onSaving = useRef(p.onSaving);\n onSaving.current = p.onSaving;\n useEffect(() => onSaving.current(busy), [busy]);\n const onFrameError = (e: unknown) => report(e, \"poster.frame\");\n const frame = useVideoFrame({ client, ref: sourceRef, path, time: duration > 0 ? shown : undefined, width: 960, onError: onFrameError });\n const strip = useFrameStrip({ client, ref: sourceRef, path, duration, count: 8, width: 160, onError: onFrameError });\n const saveFrame = (t: number, edit?: Edit | null) => (other ? poster.saveFrameFrom(sourceRef, path!, t, edit) : poster.saveFrame(t, edit));\n useEffect(() => () => crop?.revoke?.(), [crop]);\n\n const openCrop = async (source: \"frame\" | \"upload\", f?: File) => {\n setDecodeError(undefined);\n try {\n if (source === \"upload\") setCrop(await (p.decode ?? decodeImage)(f!));\n else {\n const blob = await client.media.getFrame(sourceRef, path!, shown, 1280);\n const url = URL.createObjectURL(blob);\n // Frame edits are in the frame's pixels (the video's w×h), whatever the preview's size.\n setCrop({ url, width: video!.w!, height: video!.h!, revoke: () => URL.revokeObjectURL(url) });\n }\n setCropFor(source);\n } catch (e) {\n setDecodeError(errorText(e));\n report(e, source === \"upload\" ? \"slot.decode\" : \"poster.frame\");\n }\n };\n const pickFile = (f?: File) => f && void openCrop(\"upload\", f);\n const [over, setOver] = useState(false);\n const drop = {\n onDragOver: (e: DragEvent) => {\n if (busy || !e.dataTransfer.types.includes(\"Files\")) return;\n e.preventDefault();\n setOver(true);\n },\n onDragLeave: () => setOver(false),\n onDrop: (e: DragEvent) => {\n e.preventDefault();\n setOver(false);\n pickFile(e.dataTransfer.files[0]);\n },\n };\n const failed = poster.state.status === \"error\" ? poster.state.error : (loaded.error ?? sourceError ?? (frame.error && !frame.url ? frame.error : undefined));\n const error = decodeError ?? (failed ? errorText(failed) : undefined);\n\n return (\n <>\n <div className=\"grid gap-4 px-5\">\n <div className=\"inline-flex w-fit rounded-lg bg-muted p-0.5\" role=\"tablist\" aria-label={t(\"poster.title\")}>\n {([\"frame\", \"upload\"] as const).map((m) => (\n <button\n key={m}\n type=\"button\"\n role=\"tab\"\n aria-selected={mode === m}\n disabled={busy}\n onClick={() => setMode(m)}\n className={cn(\n \"inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-sm font-medium text-muted-foreground transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/50\",\n mode === m && \"bg-background text-foreground shadow-xs\",\n )}\n >\n <HugeiconsIcon icon={m === \"frame\" ? Video01Icon : ImageUpload01Icon} strokeWidth={2} className=\"size-4\" />\n {t(m === \"frame\" ? \"poster.frame\" : \"poster.upload\")}\n </button>\n ))}\n </div>\n\n {mode === \"frame\" &&\n (source.loading && !video ? (\n <Stage loading aspect={aspect} />\n ) : !duration ? (\n <p className=\"rounded-lg bg-muted px-4 py-6 text-center text-sm text-muted-foreground\" data-ckui=\"processing\">\n {t(\"poster.processing\")}\n </p>\n ) : (\n <>\n <Stage url={frame.url} loading={frame.loading || (!frame.url && !frame.error)} aspect={aspect} />\n <div className=\"grid gap-2\">\n <div className=\"flex items-center gap-1.5\">\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"poster.previousFrame\")} disabled={busy || shown <= 0} onClick={() => setTime(Math.max(0, shown - FRAME_STEP))}>\n <HugeiconsIcon icon={ArrowLeft01Icon} strokeWidth={2} />\n </Button>\n <Slider\n aria-label={t(\"poster.time\")}\n className=\"mx-1 flex-1\"\n min={0}\n max={duration}\n step={0.01}\n value={shown}\n disabled={busy}\n onValueChange={(v) => setTime(Array.isArray(v) ? v[0]! : (v as number))}\n />\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t(\"poster.nextFrame\")}\n disabled={busy || shown >= duration}\n onClick={() => setTime(Math.min(duration, shown + FRAME_STEP))}\n >\n <HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />\n </Button>\n <span className=\"w-14 text-right text-xs text-muted-foreground tabular-nums\" data-ckui=\"time\">\n {formatTime(shown)}\n </span>\n </div>\n <div className=\"grid grid-cols-8 gap-1\" data-ckui=\"frame-strip\">\n {strip.map((f) => {\n const near = Math.abs(f.time - shown) <= duration / 16;\n return (\n <button\n key={f.time}\n type=\"button\"\n disabled={busy}\n aria-label={t(\"poster.jumpTo\", { time: formatTime(f.time) })}\n onClick={() => setTime(f.time)}\n style={{ aspectRatio: String(ratio(aspect)) }}\n className={cn(\n \"relative overflow-hidden rounded-md bg-muted outline-none ring-offset-1 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring\",\n near && \"ring-2 ring-primary\",\n )}\n >\n {f.url && <img src={f.url} alt=\"\" className=\"absolute inset-0 size-full object-contain\" />}\n </button>\n );\n })}\n </div>\n </div>\n </>\n ))}\n\n {mode === \"upload\" && (\n <label\n className={cn(\n \"flex aspect-video cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border text-sm text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5\",\n over && \"border-primary bg-primary/5\",\n busy && \"pointer-events-none opacity-60\",\n )}\n data-ckui=\"poster-drop\"\n {...drop}\n >\n <HugeiconsIcon icon={ImageUpload01Icon} strokeWidth={1.5} className=\"size-8\" />\n {t(\"poster.drop\")}\n <input\n ref={input}\n type=\"file\"\n accept={p.accept ?? \"image/*\"}\n className=\"sr-only\"\n data-ckui=\"file\"\n disabled={busy}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n pickFile(f);\n }}\n />\n </label>\n )}\n\n {error && (\n <Alert variant=\"destructive\" role=\"alert\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <SaveProgress state={poster.state} label={t(\"poster.rendering\")} />\n </div>\n\n <DialogFooter className=\"px-5 pt-4 pb-5 sm:justify-between\">\n {other ? (\n <span />\n ) : (\n <Button variant=\"ghost\" disabled={busy || !!selection?.auto} onClick={() => void poster.saveAuto()} title={t(\"poster.autoHint\")}>\n {t(\"poster.auto\")}\n </Button>\n )}\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row\">\n <Button variant=\"outline\" disabled={busy} onClick={() => p.onOpenChange(false)}>\n {t(\"common.cancel\")}\n </Button>\n {mode === \"frame\" && duration > 0 && (\n <>\n <Button variant=\"outline\" disabled={busy} onClick={() => void openCrop(\"frame\")}>\n {t(\"poster.crop\")}\n </Button>\n <Button disabled={busy} onClick={() => void saveFrame(shown)}>\n {busy ? t(\"common.saving\") : t(\"poster.useFrame\")}\n </Button>\n </>\n )}\n </div>\n </DialogFooter>\n\n <ImageCropDialog\n open={!!crop}\n onOpenChange={(o) => !o && !busy && setCrop(null)}\n source={crop}\n aspect={aspect}\n rotatable={cropFor === \"upload\"}\n targetWidth={1920}\n title={t(\"poster.cropTitle\")}\n busy={busy}\n progress={poster.state.status === \"saving\" ? poster.state.progress : undefined}\n rendering={poster.state.status === \"saving\" && poster.state.rendering}\n error={poster.state.status === \"error\" && crop ? errorText(poster.state.error) : undefined}\n onConfirm={(edit) => {\n if (cropFor === \"frame\") void saveFrame(shown, edit);\n else if (crop?.file) void poster.saveUpload(crop.file, edit);\n }}\n />\n </>\n );\n}\n\nfunction Stage({ url, loading, aspect: shape }: { url?: string; loading?: boolean; aspect: AspectRatio }) {\n const aspect = ratio(shape) ?? 16 / 9;\n return (\n <div className=\"relative mx-auto max-h-[50svh] max-w-full overflow-hidden rounded-lg bg-zinc-950\" style={{ aspectRatio: String(aspect), width: `min(100%, calc(50svh * ${aspect}))` }} data-ckui=\"frame-stage\">\n {url && <img src={url} alt=\"\" className=\"absolute inset-0 size-full object-contain\" />}\n {loading && (\n <div className=\"absolute inset-0 flex items-center justify-center text-white/70\">\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-6 animate-spin\" />\n </div>\n )}\n </div>\n );\n}\n\nexport function SaveProgress({ state, label }: { state: VideoSaveState; label: string }) {\n const { t } = useMessages();\n if (state.status !== \"saving\") return null;\n const indeterminate = state.rendering || !state.progress;\n const text = indeterminate ? label : progressLabel(t, state.progress, false);\n return (\n <Progress value={indeterminate ? null : progressValue(state.progress, false)} className=\"gap-2\" aria-label={text} data-ckui=\"save-progress\">\n <span className=\"text-xs text-muted-foreground\">{text}</span>\n </Progress>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,IAAM,IAAa,IAAI;AAwCvB,SAAgB,EAAW,GAAmB;CAC5C,IAAM,IAAI,KAAK,MAAM,IAAI,EAAE,GACrB,IAAM,IAAI,IAAI;CACpB,OAAO,GAAG,EAAE,GAAG,IAAM,KAAK,MAAM,KAAK,EAAI,QAAQ,CAAC;AACpD;AAQA,SAAgB,EAAkB,GAA2B;CAC3D,IAAM,EAAE,MAAM,EAAY,GACpB,CAAC,GAAQ,KAAa,EAAS,EAAK;CAC1C,OACE,kBAAC,GAAD;EAAQ,MAAM,EAAE;EAAM,eAAe,MAAM,CAAC,KAAU,EAAE,aAAa,CAAC;EACpE,UAAA,kBAAC,GAAD;GAAe,WAAW,EAAG,0CAA0C,EAAE,SAAS;GAAG,iBAAiB,CAAC;GAAQ,aAAU;GAAzH,UAAA,CACE,kBAAC,GAAD;IAAc,WAAU;IAAxB,UAAA,CACE,kBAAC,GAAD,EAAA,UAAc,EAAE,SAAS,EAAE,cAAc,EAAe,CAAA,GACxD,kBAAC,GAAD,EAAA,UAAoB,EAAE,oBAAoB,EAAqB,CAAA,CACnD;GACb,CAAA,GAAA,EAAE,QAAQ,kBAAC,GAAD;IAAY,GAAI;IAAG,UAAU;GAAY,CAAA,CACvC;;CACT,CAAA;AAEZ;AAEA,SAAS,EAAW,GAAgE;CAClF,IAAM,EAAE,MAAG,OAAO,MAAc,EAAY,GACtC,IAAS,EAAoB,EAAE,MAAM,GACrC,IAAS,EAAiB,EAAE,OAAO,GACnC,IAAS,EAAe;EAAE;EAAQ,KAAK,EAAE;EAAM,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAM,MAAM,EAAE;CAAK,CAAC,GAC7F,IAAQ,EAAE,UAAU,MACpB,IAAS,EAAe;EAAE;EAAQ,KAAK,GAAO,QAAQ,EAAE;EAAM,OAAO,GAAO,QAAQ,EAAE;EAAO,QAAQ,EAAE;EAAM,MAAM,IAAQ,KAAA,IAAY,EAAE;CAAK,CAAC,GAC/I,IAAY,GAAO,QAAQ,EAAE;CACnC,QAAgB,MAAM,EAAO,SAAS,EAAO,EAAO,OAAO,aAAa,IAAI,CAAC,EAAO,OAAO,CAAM,CAAC;CAClG,IAAM,IAAc,IAAQ,EAAO,QAAQ,KAAA;CAC3C,QAAgB,MAAM,KAAe,EAAO,GAAa,aAAa,IAAI,CAAC,GAAa,CAAM,CAAC;CAC/F,IAAM,EAAE,QAAQ,MAAY,GACtB,IAAQ,EAAO,OAEf,IAAY,IAAQ,KAAA,IAAY,GAAS,OACzC,CAAC,GAAM,MAAW,EAA6B,CAAC,KAAS,KAAW,CAAC,IAAY,WAAW,OAAO,GACnG,CAAC,IAAM,KAAW,EAAiB,GACnC,CAAC,GAAM,KAAW,EAA4B,IAAI,GAClD,CAAC,GAAS,MAAc,EAA6B,OAAO,GAC5D,CAAC,IAAa,KAAkB,EAAiB,GACjD,KAAQ,GAAyB,IAAI,GACrC,IAAW,KAAS,CAAC,EAAM,SAAU,EAAM,OAAO,IAAK,GACvD,IAAS,EAAE,WAAW,GAAO,KAAK,EAAM,IAAI,EAAS,EAAM,GAAG,EAAM,CAAC,IAAI,SACzE,IAAQ,MAAQ,GAAW,KAAK,IAAW,KAE3C,IAAO,IAAW,IAAI,EAAO,OAAO,KAAA,GACpC,IAAS,GAAe;EAC5B;EACA,KAAK,EAAE;EACP,MAAM,EAAE;EACR,UAAU,MAAM;GAGd,AAFA,EAAO,IAAI,CAAC,GACZ,EAAE,WAAW,CAAC,GACd,EAAE,aAAa,EAAK;EACtB;EACA,UAAU,MAAM,EAAO,GAAG,aAAa;CACzC,CAAC,GACK,IAAO,EAAO,MAAM,WAAW,UAC/B,IAAW,GAAO,EAAE,QAAQ;CAElC,AADA,EAAS,UAAU,EAAE,UACrB,QAAgB,EAAS,QAAQ,CAAI,GAAG,CAAC,CAAI,CAAC;CAC9C,IAAM,KAAgB,MAAe,EAAO,GAAG,cAAc,GACvD,IAAQ,GAAc;EAAE;EAAQ,KAAK;EAAW;EAAM,MAAM,IAAW,IAAI,IAAQ,KAAA;EAAW,OAAO;EAAK,SAAS;CAAa,CAAC,GACjI,KAAQ,GAAc;EAAE;EAAQ,KAAK;EAAW;EAAM;EAAU,OAAO;EAAG,OAAO;EAAK,SAAS;CAAa,CAAC,GAC7G,KAAa,GAAW,MAAwB,IAAQ,EAAO,cAAc,GAAW,GAAO,GAAG,CAAI,IAAI,EAAO,UAAU,GAAG,CAAI;CACxI,cAAsB,GAAM,SAAS,GAAG,CAAC,CAAI,CAAC;CAE9C,IAAM,IAAW,OAAO,GAA4B,MAAa;EAC/D,EAAe,KAAA,CAAS;EACxB,IAAI;GACF,IAAI,MAAW,UAAU,EAAQ,OAAO,EAAE,UAAU,EAAA,CAAa,CAAE,CAAC;QAC/D;IACH,IAAM,IAAO,MAAM,EAAO,MAAM,SAAS,GAAW,GAAO,GAAO,IAAI,GAChE,IAAM,IAAI,gBAAgB,CAAI;IAEpC,EAAQ;KAAE;KAAK,OAAO,EAAO;KAAI,QAAQ,EAAO;KAAI,cAAc,IAAI,gBAAgB,CAAG;IAAE,CAAC;GAC9F;GACA,GAAW,CAAM;EACnB,SAAS,GAAG;GAEV,AADA,EAAe,EAAU,CAAC,CAAC,GAC3B,EAAO,GAAG,MAAW,WAAW,gBAAgB,cAAc;EAChE;CACF,GACM,KAAY,MAAa,KAAK,KAAK,EAAS,UAAU,CAAC,GACvD,CAAC,IAAM,KAAW,EAAS,EAAK,GAChC,KAAO;EACX,aAAa,MAAiB;GACxB,MAAS,EAAE,aAAa,MAAM,SAAS,OAAO,MAClD,EAAE,eAAe,GACjB,EAAQ,EAAI;EACd;EACA,mBAAmB,EAAQ,EAAK;EAChC,SAAS,MAAiB;GAGxB,AAFA,EAAE,eAAe,GACjB,EAAQ,EAAK,GACb,EAAS,EAAE,aAAa,MAAM,EAAE;EAClC;CACF,GACM,IAAS,EAAO,MAAM,WAAW,UAAU,EAAO,MAAM,QAAS,EAAO,SAAS,MAAgB,EAAM,SAAS,CAAC,EAAM,MAAM,EAAM,QAAQ,KAAA,IAC3I,IAAQ,OAAgB,IAAS,EAAU,CAAM,IAAI,KAAA;CAE3D,OACE,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;KAA8C,MAAK;KAAU,cAAY,EAAE,cAAc;KACpG,UAAA,CAAC,SAAS,QAAQ,CAAC,CAAW,KAAK,MACnC,kBAAC,UAAD;MAEE,MAAK;MACL,MAAK;MACL,iBAAe,MAAS;MACxB,UAAU;MACV,eAAe,GAAQ,CAAC;MACxB,WAAW,EACT,iLACA,MAAS,KAAK,yCAChB;MAVF,UAAA,CAYE,kBAAC,GAAD;OAAe,MAAM,MAAM,UAAU,KAAc;OAAmB,aAAa;OAAG,WAAU;MAAU,CAAA,GACzG,EAAE,MAAM,UAAU,iBAAiB,eAAe,CAC7C;KAbD,GAAA,CAaC,CACT;IACE,CAAA;IAEJ,MAAS,YACP,EAAO,WAAW,CAAC,IAClB,kBAAC,GAAD;KAAO,SAAA;KAAgB;IAAS,CAAA,IAC7B,IAKH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;KAAO,KAAK,EAAM;KAAK,SAAS,EAAM,WAAY,CAAC,EAAM,OAAO,CAAC,EAAM;KAAgB;IAAS,CAAA,GAChG,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,GAAD;QAAQ,SAAQ;QAAQ,MAAK;QAAU,cAAY,EAAE,sBAAsB;QAAG,UAAU,KAAQ,KAAS;QAAG,eAAe,EAAQ,KAAK,IAAI,GAAG,IAAQ,CAAU,CAAC;QAChK,UAAA,kBAAC,GAAD;SAAe,MAAM;SAAiB,aAAa;QAAI,CAAA;OACjD,CAAA;OACR,kBAAC,IAAD;QACE,cAAY,EAAE,aAAa;QAC3B,WAAU;QACV,KAAK;QACL,KAAK;QACL,MAAM;QACN,OAAO;QACP,UAAU;QACV,gBAAgB,MAAM,EAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAO,CAAY;OACvE,CAAA;OACD,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,cAAY,EAAE,kBAAkB;QAChC,UAAU,KAAQ,KAAS;QAC3B,eAAe,EAAQ,KAAK,IAAI,GAAU,IAAQ,CAAU,CAAC;QAE7D,UAAA,kBAAC,GAAD;SAAe,MAAM;SAAkB,aAAa;QAAI,CAAA;OAClD,CAAA;OACR,kBAAC,QAAD;QAAM,WAAU;QAA6D,aAAU;QACpF,UAAA,EAAW,CAAK;OACb,CAAA;MACH;KACL,CAAA,GAAA,kBAAC,OAAD;MAAK,WAAU;MAAyB,aAAU;MAC/C,UAAA,GAAM,KAAK,MAAM;OAChB,IAAM,IAAO,KAAK,IAAI,EAAE,OAAO,CAAK,KAAK,IAAW;OACpD,OACE,kBAAC,UAAD;QAEE,MAAK;QACL,UAAU;QACV,cAAY,EAAE,iBAAiB,EAAE,MAAM,EAAW,EAAE,IAAI,EAAE,CAAC;QAC3D,eAAe,EAAQ,EAAE,IAAI;QAC7B,OAAO,EAAE,aAAa,OAAO,EAAM,CAAM,CAAC,EAAE;QAC5C,WAAW,EACT,+IACA,KAAQ,qBACV;QAEC,UAAA,EAAE,OAAO,kBAAC,OAAD;SAAK,KAAK,EAAE;SAAK,KAAI;SAAG,WAAU;QAA6C,CAAA;OACnF,GAZD,EAAE,IAYD;MAEZ,CAAC;KACE,CAAA,CACF;IACL,CAAA,CAAA,EAAA,CAAA,IAxDF,kBAAC,KAAD;KAAG,WAAU;KAA0E,aAAU;KAC9F,UAAA,EAAE,mBAAmB;IACrB,CAAA;IAyDN,MAAS,YACR,kBAAC,SAAD;KACE,WAAW,EACT,qNACA,MAAQ,+BACR,KAAQ,gCACV;KACA,aAAU;KACV,GAAI;KAPN,UAAA;MASE,kBAAC,GAAD;OAAe,MAAM;OAAmB,aAAa;OAAK,WAAU;MAAU,CAAA;MAC7E,EAAE,aAAa;MAChB,kBAAC,SAAD;OACE,KAAK;OACL,MAAK;OACL,QAAQ,EAAE,UAAU;OACpB,WAAU;OACV,aAAU;OACV,UAAU;OACV,WAAW,MAAM;QACf,IAAM,IAAI,EAAE,OAAO,QAAQ;QAE3B,AADA,EAAE,OAAO,QAAQ,IACjB,EAAS,CAAC;OACZ;MACD,CAAA;KACI;;IAGR,KACC,kBAAC,IAAD;KAAO,SAAQ;KAAc,MAAK;KAAlC,UAAA,CACE,kBAAC,GAAD;MAAe,MAAM;MAAa,aAAa;KAAI,CAAA,GACnD,kBAAC,IAAD,EAAA,UAAmB,EAAwB,CAAA,CACtC;;IAET,kBAAC,GAAD;KAAc,OAAO,EAAO;KAAO,OAAO,EAAE,kBAAkB;IAAI,CAAA;GAC/D;;EAEL,kBAAC,IAAD;GAAc,WAAU;GAAxB,UAAA,CACG,IACC,kBAAC,QAAD,CAAO,CAAA,IAEP,kBAAC,GAAD;IAAQ,SAAQ;IAAQ,UAAU,KAAQ,CAAC,CAAC,GAAW;IAAM,eAAe,KAAK,EAAO,SAAS;IAAG,OAAO,EAAE,iBAAiB;IAC3H,UAAA,EAAE,aAAa;GACV,CAAA,GAEV,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,UAAU;KAAM,eAAe,EAAE,aAAa,EAAK;KAC1E,UAAA,EAAE,eAAe;IACZ,CAAA,GACP,MAAS,WAAW,IAAW,KAC9B,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,UAAU;KAAM,eAAe,KAAK,EAAS,OAAO;KAC3E,UAAA,EAAE,aAAa;IACV,CAAA,GACR,kBAAC,GAAD;KAAQ,UAAU;KAAM,eAAe,KAAK,EAAU,CAAK;KACxD,UAAO,EAAP,IAAS,kBAAqB,iBAAiB;IAC1C,CAAA,CACR,EAAA,CAAA,CAED;GACO,CAAA,CAAA;;EAEd,kBAAC,IAAD;GACE,MAAM,CAAC,CAAC;GACR,eAAe,MAAM,CAAC,KAAK,CAAC,KAAQ,EAAQ,IAAI;GAChD,QAAQ;GACA;GACR,WAAW,MAAY;GACvB,aAAa;GACb,OAAO,EAAE,kBAAkB;GACrB;GACN,UAAU,EAAO,MAAM,WAAW,WAAW,EAAO,MAAM,WAAW,KAAA;GACrE,WAAW,EAAO,MAAM,WAAW,YAAY,EAAO,MAAM;GAC5D,OAAO,EAAO,MAAM,WAAW,WAAW,IAAO,EAAU,EAAO,MAAM,KAAK,IAAI,KAAA;GACjF,YAAY,MAAS;IACnB,AAAI,MAAY,UAAS,EAAe,GAAO,CAAI,IAC1C,GAAM,QAAM,EAAY,WAAW,EAAK,MAAM,CAAI;GAC7D;EACD,CAAA;CACD,EAAA,CAAA;AAEN;AAEA,SAAS,EAAM,EAAE,QAAK,YAAS,QAAQ,KAAmE;CACxG,IAAM,IAAS,EAAM,CAAK,KAAK,KAAK;CACpC,OACE,kBAAC,OAAD;EAAK,WAAU;EAAmF,OAAO;GAAE,aAAa,OAAO,CAAM;GAAG,OAAO,0BAA0B,EAAO;EAAI;EAAG,aAAU;EAAjM,UAAA,CACG,KAAO,kBAAC,OAAD;GAAK,KAAK;GAAK,KAAI;GAAG,WAAU;EAA6C,CAAA,GACpF,KACC,kBAAC,OAAD;GAAK,WAAU;GACb,UAAA,kBAAC,GAAD;IAAe,MAAM;IAAe,aAAa;IAAG,WAAU;GAAuB,CAAA;EAClF,CAAA,CAEJ;;AAET;AAEA,SAAgB,EAAa,EAAE,UAAO,YAAmD;CACvF,IAAM,EAAE,SAAM,EAAY;CAC1B,IAAI,EAAM,WAAW,UAAU,OAAO;CACtC,IAAM,IAAgB,EAAM,aAAa,CAAC,EAAM,UAC1C,IAAO,IAAgB,IAAQ,EAAc,GAAG,EAAM,UAAU,EAAK;CAC3E,OACE,kBAAC,GAAD;EAAU,OAAO,IAAgB,OAAO,EAAc,EAAM,UAAU,EAAK;EAAG,WAAU;EAAQ,cAAY;EAAM,aAAU;EAC1H,UAAA,kBAAC,QAAD;GAAM,WAAU;GAAiC,UAAA;EAAW,CAAA;CACpD,CAAA;AAEd"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AspectRatio } from '../client/aspect.js';
|
|
2
|
+
import { ComponentProps, ReactNode } from 'react';
|
|
3
|
+
import { PublicPreset } from '../client/public.js';
|
|
4
|
+
import { DensityRange } from '../client/rendition.js';
|
|
5
|
+
export interface VideoPosterProps extends Omit<ComponentProps<"div">, "children"> {
|
|
6
|
+
/** The poster's public preset (its aspect is the video's), or an image URL. */
|
|
7
|
+
poster?: PublicPreset | string | null;
|
|
8
|
+
/** The box's "W:H"; default the preset's aspect, else "16:9". */
|
|
9
|
+
aspect?: AspectRatio;
|
|
10
|
+
/** Density range for picking the poster's width; default the provider's (2–3×). */
|
|
11
|
+
density?: DensityRange;
|
|
12
|
+
alt?: string;
|
|
13
|
+
/** Shown without a poster (or when it fails to load); default a muted box with a video icon. */
|
|
14
|
+
fallback?: ReactNode;
|
|
15
|
+
/** Overlays (duration, badges). */
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A poster at its aspect, uncropped, spanning its container's width, at the
|
|
20
|
+
* width its rendered width × density needs. It never plays: use it for
|
|
21
|
+
* videos the viewer cannot play (a locked post); playable ones
|
|
22
|
+
* preview inline in VideoPlayer and MediaGallery.
|
|
23
|
+
*/
|
|
24
|
+
export declare function VideoPoster({ poster, aspect, density, alt, fallback, className, style, children, ...div }: VideoPosterProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { RenditionImg as e } from "./rendition-img.js";
|
|
2
|
+
import { publicRenditions as t } from "../client/public.js";
|
|
3
|
+
import { ratio as n } from "../client/aspect.js";
|
|
4
|
+
import { ContentKitUiRoot as r } from "../scope.js";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
|
+
import { cn as o } from "cn";
|
|
7
|
+
import { Video01Icon as s } from "@hugeicons/core-free-icons";
|
|
8
|
+
import { HugeiconsIcon as c } from "@hugeicons/react";
|
|
9
|
+
//#region src/components/video-poster.tsx
|
|
10
|
+
function l({ poster: l, aspect: u, density: d, alt: f = "", fallback: p, className: m, style: h, children: g, ..._ }) {
|
|
11
|
+
let v = typeof l == "string" ? null : l, y = t(v), b = u ?? (v?.aspect || "16:9"), x = "absolute inset-0 size-full object-contain", S = p ?? /* @__PURE__ */ i("div", {
|
|
12
|
+
className: "absolute inset-0 flex items-center justify-center text-muted-foreground/70",
|
|
13
|
+
children: /* @__PURE__ */ i(c, {
|
|
14
|
+
icon: s,
|
|
15
|
+
strokeWidth: 1.5,
|
|
16
|
+
className: "size-10"
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
return /* @__PURE__ */ a(r, {
|
|
20
|
+
..._,
|
|
21
|
+
className: o("relative w-full overflow-hidden rounded-lg bg-muted", m),
|
|
22
|
+
style: {
|
|
23
|
+
aspectRatio: String(n(b) ?? 16 / 9),
|
|
24
|
+
...h
|
|
25
|
+
},
|
|
26
|
+
"data-ckui": "video-poster",
|
|
27
|
+
children: [typeof l == "string" ? /* @__PURE__ */ i("img", {
|
|
28
|
+
src: l,
|
|
29
|
+
alt: f,
|
|
30
|
+
loading: "lazy",
|
|
31
|
+
decoding: "async",
|
|
32
|
+
className: x
|
|
33
|
+
}) : y.length ? /* @__PURE__ */ i(e, {
|
|
34
|
+
outputs: y,
|
|
35
|
+
density: d,
|
|
36
|
+
alt: f,
|
|
37
|
+
loading: "lazy",
|
|
38
|
+
className: x,
|
|
39
|
+
fallback: S
|
|
40
|
+
}) : S, g]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { l as VideoPoster };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=video-poster.js.map
|