This repository has been archived by the owner on Feb 13, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Viewer token & Video Player (#26)
- Loading branch information
Showing
12 changed files
with
303 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"use server"; | ||
|
||
import { getSelf } from "@/lib/auth-service"; | ||
import { isBlockedByUser } from "@/lib/block-service"; | ||
import { getUserById } from "@/lib/user-service"; | ||
import { AccessToken } from "livekit-server-sdk"; | ||
import { v4 } from "uuid"; | ||
|
||
export const createViewerToken = async (hostIdentity: string) => { | ||
let self; | ||
|
||
try { | ||
self = await getSelf(); | ||
} catch { | ||
const id = v4(); | ||
const username = `guest#${Math.floor(Math.random() * 1000)}`; | ||
self = { id, username }; | ||
} | ||
|
||
const host = await getUserById(hostIdentity); | ||
|
||
if (!host) { | ||
throw new Error("User not Found"); | ||
} | ||
|
||
const isBlocked = await isBlockedByUser(host.id); | ||
|
||
if (isBlocked) { | ||
throw new Error("User is blocked"); | ||
} | ||
|
||
const isHost = self.id === host.id; | ||
|
||
const token = new AccessToken( | ||
process.env.LIVEKIT_API_KEY!, | ||
process.env.LIVEKIT_API_SECRET!, | ||
{ | ||
identity: isHost ? `host-${self.id}` : self.id, | ||
name: self.username, | ||
} | ||
); | ||
|
||
token.addGrant({ | ||
room: host.id, | ||
roomJoin: true, | ||
canPublish: false, | ||
canPublishData: true, | ||
}); | ||
|
||
return await Promise.resolve(token.toJwt()); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"use client"; | ||
|
||
import { useViewerToken } from "@/hooks/use-viewer-token"; | ||
import { Stream, User } from "@prisma/client"; | ||
import { LiveKitRoom } from "@livekit/components-react"; | ||
import { cn } from "@/lib/utils"; | ||
import { Video, VideoSkeleton } from "./video"; | ||
|
||
interface StreamPlayerProps { | ||
user: User & { stream: Stream | null }; | ||
stream: Stream; | ||
isFollowing: boolean; | ||
} | ||
|
||
export const StreamPlayer = ({ | ||
user, | ||
stream, | ||
isFollowing, | ||
}: StreamPlayerProps) => { | ||
const { token, name, identity } = useViewerToken(user.id); | ||
|
||
if (!token || !name || !identity) { | ||
return <StreamPlayerSkeleton />; | ||
} | ||
|
||
return ( | ||
<> | ||
<LiveKitRoom | ||
serverUrl={process.env.NEXT_PUBLIC_LIVEKIT_WS_URL} | ||
token={token} | ||
className="grid grid-cols-1 lg:gap-y-0 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-6 h-full" | ||
> | ||
<div className="space-y-4 col-span-1 lg:col-span-2 xl:col-span-2 2xl:col-span-5 lg:overflow-y-auto hidden-scrollbar pb-10"> | ||
<Video hostName={user.username} hostIdentity={user.id} /> | ||
</div> | ||
</LiveKitRoom> | ||
</> | ||
); | ||
}; | ||
|
||
export const StreamPlayerSkeleton = () => { | ||
return ( | ||
<div className="grid grid-cols-1 lg:gap-y-0 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-6 h-full"> | ||
<div className="space-y-4 col-span-1 lg:col-span-2 xl:col-span-2 2xl:col-span-5 lg:overflow-y-auto hidden-scrollbar pb-10"> | ||
<VideoSkeleton /> | ||
{/*<HeaderSkeleton />*/} | ||
</div> | ||
<div className="col-span-1 bg-background">{/*<ChatSkeleton />*/}</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Participant } from "livekit-client"; | ||
import React, { useRef } from "react"; | ||
|
||
interface LiveVideoProps { | ||
participant: Participant; | ||
} | ||
|
||
export const LiveVideo = ({ participant }: LiveVideoProps) => { | ||
const videoRef = useRef<HTMLVideoElement>(null); | ||
const wrapperRef = useRef<HTMLDivElement>(null); | ||
|
||
return ( | ||
<div ref={wrapperRef} className="relative h-full flex"> | ||
<video ref={videoRef} width="100%" /> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Loader } from "lucide-react"; | ||
import React from "react"; | ||
|
||
interface LoadingVideoProps { | ||
label: string; | ||
} | ||
|
||
export const LoadingVideo = ({ label }: LoadingVideoProps) => { | ||
return ( | ||
<div className="h-full flex flex-col space-y-4 justify-center items-center"> | ||
<Loader className="h-10 w-10 text-muted-foreground animate-spin" /> | ||
<p className="text-muted-foreground capitalize">{label}</p> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { WifiOff } from "lucide-react"; | ||
|
||
interface OfflineVideoProps { | ||
username: string; | ||
} | ||
|
||
export const OfflineVideo = ({ username }: OfflineVideoProps) => { | ||
return ( | ||
<div className="h-full flex flex-col space-y-4 justify-center items-center"> | ||
<WifiOff className="h-10 w-10 text-muted-foreground" /> | ||
<p className="text-muted-foreground">{username} is offline</p> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"use client"; | ||
|
||
import { ConnectionState, Track } from "livekit-client"; | ||
import { | ||
useConnectionState, | ||
useRemoteParticipant, | ||
useTracks, | ||
} from "@livekit/components-react"; | ||
import { OfflineVideo } from "./offline-video"; | ||
import { LoadingVideo } from "./loading-video"; | ||
import { LiveVideo } from "./live-video"; | ||
import { Skeleton } from "../ui/skeleton"; | ||
|
||
interface VideoProps { | ||
hostName: string; | ||
hostIdentity: string; | ||
} | ||
|
||
export const Video = ({ hostName, hostIdentity }: VideoProps) => { | ||
const connectionState = useConnectionState(); | ||
const participant = useRemoteParticipant(hostIdentity); | ||
const tracks = useTracks([ | ||
Track.Source.Camera, | ||
Track.Source.Microphone, | ||
]).filter((track) => track.participant.identity === hostIdentity); | ||
|
||
let content; | ||
|
||
if (!participant && connectionState === ConnectionState.Connected) { | ||
content = <OfflineVideo username={hostName} />; | ||
} else if (!participant || tracks.length === 0) { | ||
content = <LoadingVideo label={connectionState} />; | ||
} else { | ||
content = <LiveVideo participant={participant} />; | ||
} | ||
|
||
return <div className="aspect-video border-b group relative">{content}</div>; | ||
}; | ||
|
||
export const VideoSkeleton = () => { | ||
return ( | ||
<div className="aspect-video border-x border-background"> | ||
<Skeleton className="h-full w-full rounded-none" /> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { createViewerToken } from "@/actions/token"; | ||
import { JwtPayload, jwtDecode } from "jwt-decode"; | ||
import { useEffect, useState } from "react"; | ||
import { toast } from "sonner"; | ||
|
||
export const useViewerToken = (hostIdentity: string) => { | ||
const [token, setToken] = useState(""); | ||
const [name, setName] = useState(""); | ||
const [identity, setIdentity] = useState(""); | ||
|
||
useEffect(() => { | ||
const createToken = async () => { | ||
try { | ||
const viewerToken = await createViewerToken(hostIdentity); | ||
setToken(viewerToken); | ||
|
||
const decodedToken = jwtDecode(viewerToken) as JwtPayload & { | ||
name?: string; | ||
}; | ||
const name = decodedToken?.name; | ||
const identity = decodedToken.jti; | ||
|
||
if (identity) { | ||
setIdentity(identity); | ||
} | ||
|
||
if (name) { | ||
setName(name); | ||
} | ||
} catch (error) { | ||
toast.error("Something went wrong"); | ||
} | ||
}; | ||
|
||
createToken(); | ||
}, [hostIdentity]); | ||
|
||
return { token, name, identity }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,17 @@ | ||
/** @type {import('next').NextConfig} */ | ||
const nextConfig = {} | ||
const nextConfig = { | ||
images: { | ||
domains: ["utfs.io"], | ||
}, | ||
webpack: (config) => { | ||
config.module.rules.push({ | ||
test: /\.mjs$/, | ||
include: /node_modules/, | ||
type: "javascript/auto", | ||
}); | ||
|
||
module.exports = nextConfig | ||
return config; | ||
}, | ||
}; | ||
|
||
module.exports = nextConfig; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters