@@ -20,6 +20,7 @@ import {
2020} from 'media-chrome/react'
2121import * as React from 'react'
2222import { Await , Link , useFetcher , useRevalidator } from 'react-router'
23+ import { ClientOnly } from 'remix-utils/client-only'
2324import { useEventSource } from 'remix-utils/sse/react'
2425import { toast } from 'sonner'
2526import { useTheme } from '#app/routes/theme/index.tsx'
@@ -758,84 +759,93 @@ function EpicVideo({
758759 onDelete = { handleDelete }
759760 />
760761 ) : null
762+ const offlinePlayerFallback = (
763+ < div className = "text-background flex h-full w-full items-center justify-center" >
764+ < Loading > { title } </ Loading >
765+ </ div >
766+ )
761767 return (
762768 < div >
763769 < div className = "shadow-lg" >
764770 { shouldUseOfflineVideo ? (
765771 < div className = "flex aspect-video w-full items-center justify-center bg-black" >
766- < MediaController
767- tabIndex = { 0 }
768- aria-label = { `${ title } video player` }
769- onPointerDown = { ( event ) => {
770- event . currentTarget . focus ( )
771- } }
772- className = "focus-visible:ring-ring flex h-full w-full flex-col justify-end outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
773- style = {
774- {
775- '--media-primary-color' : 'hsl(var(--background))' ,
776- '--media-text-color' : 'hsl(var(--background))' ,
777- '--media-secondary-color' : 'transparent' ,
778- '--media-control-background' : 'transparent' ,
779- '--media-control-hover-background' :
780- 'hsl(var(--background) / 0.18)' ,
781- '--media-control-height' : '18px' ,
782- '--media-range-padding' : '2px' ,
783- '--media-time-range-hover-height' : '18px' ,
784- '--media-time-range-hover-bottom' : '-4px' ,
785- '--media-range-track-height' : '3px' ,
786- '--media-range-thumb-height' : '8px' ,
787- '--media-range-thumb-width' : '8px' ,
788- '--media-range-track-background' :
789- 'hsl(var(--background) / 0.35)' ,
790- '--media-range-track-pointer-background' :
791- 'hsl(var(--background) / 0.85)' ,
792- } as React . CSSProperties
793- }
794- >
795- < video
796- ref = { nativeVideoRef }
797- slot = "media"
798- aria-label = { title }
799- className = "h-full w-full"
800- playsInline
801- preload = "metadata"
802- src = { offlineVideoUrl }
803- onTimeUpdate = { handleOfflineTimeUpdate }
804- />
805- < div className = "bg-foreground/40 text-background w-full space-y-1 px-3 pt-1 pb-1.5 text-sm leading-none backdrop-blur select-none" >
806- < MediaTimeRange className = "w-full" />
807- < MediaControlBar className = "w-full items-center gap-3" >
808- < div className = "flex items-center gap-3" >
809- < MediaPlayButton />
810- < MediaSeekBackwardButton
811- ref = { setSeekOffset }
812- seekOffset = { 10 }
813- />
814- < MediaSeekForwardButton
815- ref = { setSeekOffset }
816- seekOffset = { 10 }
817- />
818- < MediaTimeDisplay
819- showDuration
820- className = "text-background text-xs tabular-nums"
821- />
822- < MediaMuteButton />
823- < MediaVolumeRange className = "w-24" />
772+ < ClientOnly fallback = { offlinePlayerFallback } >
773+ { ( ) => (
774+ < MediaController
775+ tabIndex = { 0 }
776+ aria-label = { `${ title } video player` }
777+ onPointerDown = { ( event ) => {
778+ event . currentTarget . focus ( )
779+ } }
780+ className = "focus-visible:ring-ring flex h-full w-full flex-col justify-end outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
781+ style = {
782+ {
783+ '--media-primary-color' : 'hsl(var(--background))' ,
784+ '--media-text-color' : 'hsl(var(--background))' ,
785+ '--media-secondary-color' : 'transparent' ,
786+ '--media-control-background' : 'transparent' ,
787+ '--media-control-hover-background' :
788+ 'hsl(var(--background) / 0.18)' ,
789+ '--media-control-height' : '18px' ,
790+ '--media-range-padding' : '2px' ,
791+ '--media-time-range-hover-height' : '18px' ,
792+ '--media-time-range-hover-bottom' : '-4px' ,
793+ '--media-range-track-height' : '3px' ,
794+ '--media-range-thumb-height' : '8px' ,
795+ '--media-range-thumb-width' : '8px' ,
796+ '--media-range-track-background' :
797+ 'hsl(var(--background) / 0.35)' ,
798+ '--media-range-track-pointer-background' :
799+ 'hsl(var(--background) / 0.85)' ,
800+ } as React . CSSProperties
801+ }
802+ >
803+ < video
804+ ref = { nativeVideoRef }
805+ slot = "media"
806+ aria-label = { title }
807+ className = "h-full w-full"
808+ playsInline
809+ preload = "metadata"
810+ src = { offlineVideoUrl }
811+ onTimeUpdate = { handleOfflineTimeUpdate }
812+ />
813+ < div className = "bg-foreground/40 text-background w-full space-y-1 px-3 pt-1 pb-1.5 text-sm leading-none backdrop-blur select-none" >
814+ < MediaTimeRange className = "w-full" />
815+ < MediaControlBar className = "w-full items-center gap-3" >
816+ < div className = "flex items-center gap-3" >
817+ < MediaPlayButton />
818+ < MediaSeekBackwardButton
819+ ref = { setSeekOffset }
820+ seekOffset = { 10 }
821+ />
822+ < MediaSeekForwardButton
823+ ref = { setSeekOffset }
824+ seekOffset = { 10 }
825+ />
826+ < MediaTimeDisplay
827+ showDuration
828+ className = "text-background text-xs tabular-nums"
829+ />
830+ < MediaMuteButton />
831+ < MediaVolumeRange className = "w-24" />
832+ </ div >
833+ < div className = "ml-auto flex items-center gap-3" >
834+ < MediaPlaybackRateButton
835+ className = "text-background text-xs"
836+ rates = { [
837+ 0.5 , 0.75 , 0.8 , 0.9 , 1 , 1.25 , 1.5 , 1.75 , 2 , 2.5 , 3 ,
838+ 3.5 , 4 ,
839+ ] }
840+ />
841+ < MediaPipButton />
842+ < MediaFullscreenButton />
843+ </ div >
844+ </ MediaControlBar >
824845 </ div >
825- < div className = "ml-auto flex items-center gap-3" >
826- < MediaPlaybackRateButton
827- className = "text-background text-xs"
828- rates = { [
829- 0.5 , 0.75 , 0.8 , 0.9 , 1 , 1.25 , 1.5 , 1.75 , 2 , 2.5 , 3 , 3.5 ,
830- 4 ,
831- ] }
832- />
833- < MediaPipButton />
834- < MediaFullscreenButton />
835- </ div >
836- </ MediaControlBar >
837- </ div >
838- </ MediaController >
846+ </ MediaController >
847+ ) }
848+ </ ClientOnly >
839849 </ div >
840850 ) : ! isOnline ? (
841851 < OfflineVideoUnavailable />
0 commit comments