From 0441112aab06f6323b30a290ccceaaff926cac7d Mon Sep 17 00:00:00 2001 From: winoffrg Date: Tue, 16 Jun 2026 00:58:45 +0530 Subject: [PATCH 1/4] feat: assets cleanup and video player design --- apps/www/components/blocks/preview-pane.tsx | 16 ++-- .../players/video-player/player-container.tsx | 23 ++++- .../stream-panel/content-catalog.ts | 3 +- .../stream-panel/use-stream-panel-sync.ts | 2 +- apps/www/lib/stream-presets.ts | 51 +++++----- .../components/asset-metadata-overlay.tsx | 46 +++++++++ .../video-player/components/media-player.tsx | 19 +++- .../video-player/components/playlist.tsx | 53 ++++++----- .../registry/default/ui/root-container.tsx | 95 +++++++++++++++---- 9 files changed, 229 insertions(+), 79 deletions(-) create mode 100644 apps/www/registry/default/blocks/video-player/components/asset-metadata-overlay.tsx diff --git a/apps/www/components/blocks/preview-pane.tsx b/apps/www/components/blocks/preview-pane.tsx index df31b81..ab268ea 100644 --- a/apps/www/components/blocks/preview-pane.tsx +++ b/apps/www/components/blocks/preview-pane.tsx @@ -2,7 +2,7 @@ import { domAnimation, LazyMotion, m } from "motion/react" import { usePathname, useRouter, useSearchParams } from "next/navigation" -import React, { useCallback, useLayoutEffect, useState } from "react" +import React, { useCallback, useLayoutEffect, useRef, useState } from "react" import { StreamPanelProvider } from "@/components/stream-panel" import { useThemeToggle } from "@/components/theme-toggle" @@ -25,6 +25,7 @@ export function BlockPreviewWithToolbar({ const [expanded, setExpanded] = useState(() => { return searchParams.get(EXPANDED_QUERY_PARAM) === "true" }) + const expandedRef = useRef(expanded) const { isDark, toggleTheme } = useThemeToggle({ blur: false, start: "top-right", @@ -51,11 +52,10 @@ export function BlockPreviewWithToolbar({ ) const handleExpandToggle = useCallback(() => { - setExpanded((previousExpanded) => { - const nextExpanded = !previousExpanded - updateExpandedQuery(nextExpanded) - return nextExpanded - }) + const nextExpanded = !expandedRef.current + expandedRef.current = nextExpanded + setExpanded(nextExpanded) + updateExpandedQuery(nextExpanded) }, [updateExpandedQuery]) const handleReload = useCallback(() => { @@ -63,7 +63,9 @@ export function BlockPreviewWithToolbar({ }, []) useLayoutEffect(() => { - setExpanded(searchParams.get(EXPANDED_QUERY_PARAM) === "true") + const nextExpanded = searchParams.get(EXPANDED_QUERY_PARAM) === "true" + expandedRef.current = nextExpanded + setExpanded(nextExpanded) }, [pathname, searchParams]) // DEV: This controls the left doc section to be hidden when preview is expanded diff --git a/apps/www/components/players/video-player/player-container.tsx b/apps/www/components/players/video-player/player-container.tsx index 9d4af1d..ca51b9b 100644 --- a/apps/www/components/players/video-player/player-container.tsx +++ b/apps/www/components/players/video-player/player-container.tsx @@ -3,7 +3,7 @@ import type { RefObject } from "react" import { ChevronDownIcon, MonitorPlayIcon, RotateCwIcon } from "lucide-react" -import { useRef } from "react" +import { useMemo, useRef } from "react" import { useFullscreen, useToggle } from "react-use" import { @@ -27,6 +27,10 @@ export function VideoPlayerContainer() { const { isPortrait } = useOrientation() const isMobilePortrait = isMobile && isPortrait const playerRef = useRef(null) + const controlsVisibility = useVideoPlayerControlsVisibility({ + disabled: isMobilePortrait, + isMobile, + }) return ( @@ -50,6 +54,7 @@ export function VideoPlayerContainer() { muted: true, }} ref={playerRef} + {...controlsVisibility} > @@ -168,3 +173,19 @@ function useSelectedStreamName() { ?.name ?? "Custom Stream" ) } + +function useVideoPlayerControlsVisibility({ + disabled, + isMobile, +}: { + disabled: boolean + isMobile: boolean +}) { + return useMemo( + () => ({ + controlsHideDelay: disabled ? 0 : 1800, + hideCursorOnIdle: !disabled && !isMobile, + }), + [disabled, isMobile] + ) +} diff --git a/apps/www/components/stream-panel/content-catalog.ts b/apps/www/components/stream-panel/content-catalog.ts index 4ebdf97..7d12b3d 100644 --- a/apps/www/components/stream-panel/content-catalog.ts +++ b/apps/www/components/stream-panel/content-catalog.ts @@ -42,6 +42,7 @@ export interface BlenderOpenFilmImages { backdrop?: string logo?: string poster?: string + thumbnail?: string } export interface BlenderStreamResponse extends BlenderPlaylistItem { @@ -421,7 +422,7 @@ function toBlenderOpenFilmAsset( duration: item.duration, id: item.id, images: item.images, - poster: item.images?.backdrop ?? item.images?.poster, + poster: item.images?.thumbnail ?? item.images?.poster, source: "blender-open-film", subtitle: item.subtitle, title: item.title, diff --git a/apps/www/components/stream-panel/use-stream-panel-sync.ts b/apps/www/components/stream-panel/use-stream-panel-sync.ts index eab7e86..1ce490f 100644 --- a/apps/www/components/stream-panel/use-stream-panel-sync.ts +++ b/apps/www/components/stream-panel/use-stream-panel-sync.ts @@ -278,8 +278,8 @@ export function useStreamPanelSync({ format: "progressive", group: "Special", id, - name: "Custom Stream", src, + title: "Custom Stream", type: playerType, } loadSource(asset as unknown as Asset, { loading: assetOptions }) diff --git a/apps/www/lib/stream-presets.ts b/apps/www/lib/stream-presets.ts index 1b550d2..7dbfb88 100644 --- a/apps/www/lib/stream-presets.ts +++ b/apps/www/lib/stream-presets.ts @@ -36,11 +36,10 @@ export interface StreamPreset { format: "dash" | "hls" | "progressive" group: StreamGroup id: string - name: string poster?: string src: string thumbnail?: string - title?: string + title: string type: "audio" | "video" } @@ -53,9 +52,9 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "HLS", id: "mux-big-buck-bunny", - name: "Big Buck Bunny", poster: "https://files.vidstack.io/sprite-fight/poster.webp", src: "https://stream.mux.com/VZtzUzGRv02OhRnZCxcNg49OilvolTqdnFLEqBsTwaxU.m3u8", + title: "Big Buck Bunny", type: "video", }, { @@ -72,8 +71,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "HLS", id: "apple-advanced-hls", - name: "Apple Advanced Stream", src: "https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8", + title: "Apple Advanced Stream", type: "video", }, { @@ -82,8 +81,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "HLS", id: "apple-bipbop-hls", - name: "Apple Bipbop", src: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8", + title: "Apple Bipbop", type: "video", }, { @@ -92,8 +91,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "HLS", id: "shaka-bbb-dark-truths-hls", - name: "Dark Truths", src: "https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8", + title: "Dark Truths", type: "video", }, @@ -105,8 +104,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DASH", id: "shaka-angel-one-dash", - name: "Angel One", src: "https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd", + title: "Angel One", type: "video", }, { @@ -115,8 +114,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DASH", id: "shaka-sintel-4k", - name: "Sintel 4K", src: "https://storage.googleapis.com/shaka-demo-assets/sintel/dash.mpd", + title: "Sintel 4K", type: "video", }, { @@ -125,10 +124,10 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DASH", id: "bitmovin-dash", - name: "Art of Motion", src: "https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-progressive/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd", thumbnail: "https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-progressive/thumbnails/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.vtt", + title: "Art of Motion", type: "video", }, { @@ -137,8 +136,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DASH", id: "shaka-tears-surround", - name: "Tears of Steel", src: "https://storage.googleapis.com/shaka-demo-assets/tos-surround/dash.mpd", + title: "Tears of Steel", type: "video", }, @@ -150,8 +149,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Live", id: "dash-if-live", - name: "DASH-IF Live Sim", src: "https://livesim2.dashif.org/livesim2/utc_head/testpic_2s/Manifest.mpd", + title: "DASH-IF Live Sim", type: "video", }, { @@ -160,8 +159,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Live", id: "shaka-live-dash", - name: "Shaka Player History", src: "https://storage.googleapis.com/shaka-live-assets/player-source.mpd", + title: "Shaka Player History", type: "video", }, { @@ -170,8 +169,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "Live", id: "shaka-live-hls", - name: "Shaka Player History (HLS)", src: "https://storage.googleapis.com/shaka-live-assets/player-source.m3u8", + title: "Shaka Player History (HLS)", type: "video", }, { @@ -180,8 +179,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Live", id: "dash-if-ll-live", - name: "Low Latency DASH", src: "https://livesim2.dashif.org/livesim2/chunkdur_1/ato_7/testpic4_8s/Manifest300.mpd", + title: "Low Latency DASH", type: "video", }, @@ -200,8 +199,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DRM", id: "shaka-angel-widevine", - name: "Angel One", src: "https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd", + title: "Angel One", type: "video", }, { @@ -218,8 +217,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DRM", id: "shaka-angel-clearkey", - name: "Angel One", src: "https://storage.googleapis.com/shaka-demo-assets/angel-one-clearkey/dash.mpd", + title: "Angel One", type: "video", }, { @@ -235,8 +234,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "DRM", id: "shaka-angel-hls-widevine", - name: "Angel One (HLS)", src: "https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8", + title: "Angel One (HLS)", type: "video", }, { @@ -258,8 +257,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "DRM", id: "shaka-sintel-widevine", - name: "Sintel 4K", src: "https://storage.googleapis.com/shaka-demo-assets/sintel-widevine/dash.mpd", + title: "Sintel 4K", type: "video", }, { @@ -268,8 +267,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "DRM", id: "bitmovin-hls-aes128", - name: "Art of Motion", src: "https://cdn.bitmovin.com/content/assets/art-of-motion_drm/m3u8s/11331.m3u8", + title: "Art of Motion", type: "video", }, @@ -281,8 +280,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Audio", id: "shaka-dig-uke", - name: "Dig the Uke", src: "https://storage.googleapis.com/shaka-demo-assets/dig-the-uke-clear/dash.mpd", + title: "Dig the Uke", type: "audio", }, { @@ -291,8 +290,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "hls", group: "Audio", id: "apple-hls-audio-aac", - name: "HLS Audio (AAC)", src: "https://storage.googleapis.com/shaka-demo-assets/raw-hls-audio-only/manifest.m3u8", + title: "HLS Audio (AAC)", type: "audio", }, @@ -304,8 +303,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "progressive", group: "Progressive", id: "mp4-bunny-progressive", - name: "Big Buck Bunny", src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + title: "Big Buck Bunny", type: "video", }, { @@ -314,8 +313,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "progressive", group: "Progressive", id: "mp4-elephants-dream", - name: "Elephants Dream", src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + title: "Elephants Dream", type: "video", }, @@ -327,8 +326,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Special", id: "shaka-sintel-trick", - name: "Sintel", src: "https://storage.googleapis.com/shaka-demo-assets/sintel-trickplay/dash.mpd", + title: "Sintel", type: "video", }, { @@ -337,8 +336,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Special", id: "dash-if-thumbnails", - name: "Big Buck Bunny", src: "https://dash.akamaized.net/akamai/bbb_30fps/bbb_with_tiled_thumbnails.mpd", + title: "Big Buck Bunny", type: "video", }, { @@ -347,8 +346,8 @@ export const STREAM_PRESETS: StreamPreset[] = [ format: "dash", group: "Special", id: "bitmovin-vr", - name: "VR Playhouse", src: "https://cdn.bitmovin.com/content/assets/playhouse-vr/mpds/105560.mpd", + title: "VR Playhouse", type: "video", }, ] diff --git a/apps/www/registry/default/blocks/video-player/components/asset-metadata-overlay.tsx b/apps/www/registry/default/blocks/video-player/components/asset-metadata-overlay.tsx new file mode 100644 index 0000000..364a626 --- /dev/null +++ b/apps/www/registry/default/blocks/video-player/components/asset-metadata-overlay.tsx @@ -0,0 +1,46 @@ +"use client" + +import type { VideoPlayerAsset } from "@/registry/default/blocks/video-player/components/media-player" + +import { useAsset } from "@/registry/default/hooks/use-asset" +import { ControlsTopContainer } from "@/registry/default/ui/player-layout" + +export function AssetMetadataOverlay() { + const { currentItem } = useAsset() + const asset = currentItem?.properties + const description = asset?.description?.trim() + const title = asset?.title?.trim() + + if (!title && !description) return null + + return ( + <> +
+ +
+ {title && ( +

+ {title} +

+ )} + {description && ( +

+ {description} +

+ )} +
+
+ + ) +} diff --git a/apps/www/registry/default/blocks/video-player/components/media-player.tsx b/apps/www/registry/default/blocks/video-player/components/media-player.tsx index a935762..646857d 100644 --- a/apps/www/registry/default/blocks/video-player/components/media-player.tsx +++ b/apps/www/registry/default/blocks/video-player/components/media-player.tsx @@ -10,6 +10,7 @@ import type { } from "@/registry/default/hooks/use-asset" import { cn } from "@/lib/utils" +import { AssetMetadataOverlay } from "@/registry/default/blocks/video-player/components/asset-metadata-overlay" import { BottomControls } from "@/registry/default/blocks/video-player/components/bottom-controls" import { Button } from "@/registry/default/blocks/video-player/components/button" import { MediaProvider } from "@/registry/default/blocks/video-player/lib/media-kit" @@ -22,19 +23,25 @@ import { FallbackPoster } from "@/registry/default/ui/fallback-poster" import { LimeplayLogo } from "@/registry/default/ui/limeplay-logo" import { Media } from "@/registry/default/ui/media" import * as Layout from "@/registry/default/ui/player-layout" -import { RootContainer } from "@/registry/default/ui/root-container" +import { + RootContainer, + type RootContainerProps, +} from "@/registry/default/ui/root-container" export interface VideoPlayerAsset extends Asset { description?: string poster?: string title?: string + year?: string } export interface VideoPlayerProps { autoLoad?: boolean children?: React.ReactNode className?: string + controlsHideDelay?: RootContainerProps["controlsHideDelay"] debug?: boolean + hideCursorOnIdle?: RootContainerProps["hideCursorOnIdle"] initialIndex?: number loading?: UseAssetOptions /** @@ -51,7 +58,9 @@ export const VideoPlayer = React.forwardRef( autoLoad, children, className, + controlsHideDelay, debug, + hideCursorOnIdle, initialIndex, loading, mediaProps, @@ -71,7 +80,12 @@ export const VideoPlayer = React.forwardRef( source={source} sourceKey={sourceKey} /> - + @@ -87,6 +101,7 @@ export const VideoPlayer = React.forwardRef( {children} + diff --git a/apps/www/registry/default/blocks/video-player/components/playlist.tsx b/apps/www/registry/default/blocks/video-player/components/playlist.tsx index 81f9e62..e77beaf 100644 --- a/apps/www/registry/default/blocks/video-player/components/playlist.tsx +++ b/apps/www/registry/default/blocks/video-player/components/playlist.tsx @@ -1,7 +1,7 @@ "use client" import { CardsThreeIcon, PlayIcon } from "@phosphor-icons/react" -import { useMemo } from "react" +import { type ComponentProps, useEffect, useMemo } from "react" import type { VideoPlayerAsset } from "@/registry/default/blocks/video-player/components/media-player" @@ -15,7 +15,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Button } from "@/registry/default/blocks/video-player/components/button" -import { useAssetStore } from "@/registry/default/hooks/use-asset" +import { useMediaStore } from "@/registry/default/hooks/use-media" import { usePlayerStore } from "@/registry/default/hooks/use-player" import { usePlaylistStore } from "@/registry/default/hooks/use-playlist" @@ -24,10 +24,8 @@ export function Playlist() { (state) => state.currentItem as null | { id: string; properties: VideoPlayerAsset } ) - const preloadAsset = useAssetStore((state) => state.preloadAsset) as ( - asset: VideoPlayerAsset - ) => Promise - const preloadManagers = usePlayerStore((state) => state.preloadManagers) + const containerRef = usePlayerStore((state) => state.containerRef) + const setForceIdle = useMediaStore((state) => state.setForceIdle) const queue = usePlaylistStore( (state) => state.queue as { id: string; properties: VideoPlayerAsset }[] ) @@ -45,20 +43,25 @@ export function Playlist() { ) }, [queue, shuffle, shuffleOrder]) + useEffect(() => { + return () => { + setForceIdle(false) + } + }, [setForceIdle]) + if (orderedItems.length < 2) return null const handleAssetSelect = async (assetId: string) => { await skipToId(assetId) } - const handleAssetHover = async (assetId: string, asset: VideoPlayerAsset) => { - if (!preloadManagers.has(assetId) && currentItem?.id !== assetId) { - await preloadAsset(asset) - } + const dropdownCollisionProps = { + collisionBoundary: containerRef ?? undefined, + collisionPadding: 12, } return ( - +