Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import { LockClosedIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
2import {
3 ArrowsPointingInIcon,
4 ArrowsPointingOutIcon,
5 PauseIcon,
6 PlayIcon,
7 SpeakerWaveIcon,
8 SpeakerXMarkIcon
9} from "@heroicons/react/24/solid";
10import type { Src } from "@livepeer/react";
11import * as Player from "@livepeer/react/player";
12import { memo, type ReactNode } from "react";
13import { Spinner } from "@/components/Shared/UI";
14
15const PlayerLoading = () => (
16 <div className="absolute inset-0 flex flex-col items-center justify-center">
17 <Spinner size="md" />
18 </div>
19);
20
21interface PlayerErrorProps {
22 matcher: Player.ErrorIndicatorProps["matcher"];
23 icon: ReactNode;
24 title: string;
25}
26
27const PlayerError = ({ matcher, icon, title }: PlayerErrorProps) => {
28 return (
29 <Player.ErrorIndicator
30 className="absolute inset-0 flex flex-col items-center justify-center bg-black"
31 matcher={matcher}
32 >
33 <div className="flex flex-col items-center space-y-2 text-lg text-white">
34 {icon}
35 <b>{title}</b>
36 </div>
37 </Player.ErrorIndicator>
38 );
39};
40
41interface VideoProps {
42 src: Src[] | null;
43 poster?: string;
44}
45
46const Video = ({ src, poster }: VideoProps) => {
47 if (!src) {
48 return null;
49 }
50
51 return (
52 <Player.Root src={src}>
53 <Player.Container className="size-full overflow-hidden rounded-xl bg-black">
54 <Player.Video className="size-full" poster={poster} />
55 <Player.LoadingIndicator>
56 <PlayerLoading />
57 </Player.LoadingIndicator>
58 <PlayerError
59 icon={<NoSymbolIcon className="size-8" />}
60 matcher="offline"
61 title="Stream is offline"
62 />
63 <PlayerError
64 icon={<LockClosedIcon className="size-8" />}
65 matcher="access-control"
66 title="Stream is private"
67 />
68 <Player.Controls className="flex flex-col-reverse gap-1 bg-gradient-to-b from-black/5 via-80% via-black/30 to-black/60 px-3 py-2 duration-1000 md:px-3">
69 <div className="flex justify-between gap-4">
70 <div className="flex flex-1 items-center gap-3">
71 <Player.PlayPauseTrigger className="size-6 flex-shrink-0 transition hover:scale-110">
72 <Player.PlayingIndicator asChild matcher={false}>
73 <PlayIcon className="size-5 text-white" />
74 </Player.PlayingIndicator>
75 <Player.PlayingIndicator asChild>
76 <PauseIcon className="size-5 text-white" />
77 </Player.PlayingIndicator>
78 </Player.PlayPauseTrigger>
79 <Player.LiveIndicator className="flex items-center gap-2">
80 <div className="size-1.5 rounded-full bg-red-500" />
81 <b className="text-white text-xs">LIVE</b>
82 </Player.LiveIndicator>
83 <Player.LiveIndicator className="flex" matcher={false}>
84 <Player.Time className="text-white text-xs" />
85 </Player.LiveIndicator>
86 <Player.MuteTrigger className="size-6 flex-shrink-0 transition hover:scale-110">
87 <Player.VolumeIndicator asChild matcher={false}>
88 <SpeakerXMarkIcon className="size-5 text-white" />
89 </Player.VolumeIndicator>
90 <Player.VolumeIndicator asChild matcher={true}>
91 <SpeakerWaveIcon className="size-5 text-white" />
92 </Player.VolumeIndicator>
93 </Player.MuteTrigger>
94 <Player.Volume className="relative flex max-w-28 flex-1 cursor-pointer items-center">
95 <Player.Track className="relative h-1 grow rounded-full bg-white/30">
96 <Player.Range className="absolute h-full rounded-full bg-white" />
97 </Player.Track>
98 <Player.Thumb className="block size-2.5 rounded-full bg-white outline-hidden" />
99 </Player.Volume>
100 </div>
101 <div className="flex items-center justify-end gap-2.5 sm:flex-1 md:flex-[1.5]">
102 <Player.FullscreenTrigger className="size-6 flex-shrink-0 transition hover:scale-110">
103 <Player.FullscreenIndicator asChild>
104 <ArrowsPointingInIcon className="size-5 text-white" />
105 </Player.FullscreenIndicator>
106 <Player.FullscreenIndicator asChild matcher={false}>
107 <ArrowsPointingOutIcon className="size-5 text-white" />
108 </Player.FullscreenIndicator>
109 </Player.FullscreenTrigger>
110 </div>
111 </div>
112 <Player.Seek className="relative flex h-4 w-full cursor-pointer items-center">
113 <Player.Track className="relative h-1 grow rounded-full bg-white/30">
114 <Player.SeekBuffer className="absolute h-full rounded-full bg-white/60" />
115 <Player.Range className="absolute h-full rounded-full bg-white" />
116 </Player.Track>
117 <Player.Thumb className="block size-2.5 rounded-full bg-white outline-hidden" />
118 </Player.Seek>
119 </Player.Controls>
120 </Player.Container>
121 </Player.Root>
122 );
123};
124
125export default memo(Video);