This tutorial is based on Livepeer React version 3.9 or earlier, which is now
deprecated. Please ensure that you use Livepeer React version 4 or later, with
the new Livepeer JavaScript SDK. The integration process may appear different,
but the underlying concepts remain same.
The Filecoin Virtual Machine (FVM) is a runtime environment designed for
executing smart contracts on the Filecoin network. It enables developers to
write and deploy custom code on Filecoin blockchain unleashing the ability to
write software that automates the storage, retrieval, and ultimately the
transformation of data in a Web3-native way. This allows for decentralized
applications to provide permanent storage guarantees for user content through
smart contracts that store video data securely on Filecoin. FVM allows
developers to write smart contracts on the Filecoin blockchain to automate
storage, retrieval, and transformation of data.
For end-users, Web3 apps provide greater security, privacy, and control over
their data. By leveraging the power of blockchain technology, users can be sure
that their data is stored in a tamper-proof and decentralized manner, which
means that no single entity has complete control over their data. This provides
greater protection against data breaches, hacks, and other security threats.
When combined with Livepeer, developers can build decentralized video
applications, archive videos, create video NFTs, and more. In this tutorial, you
will learn how to build a decentralized video application with FVM and Livepeer.
Prerequisites
Before you start with this tutorial, make sure you have the following tool
installed on your machine:
In addition to the above tools, this tutorial assumes that you have a basic
understanding of Solidity and Next.js
Setting up Next.js App
First, create a directory for the project and then initialize a Next.js app
using the following command in your terminal:
This will create a new Next.js app in the current directory and install all the
necessary dependencies. Once the project is created successfully, run the
following command to install a few other dependencies.
npm install @livepeer/react lighthouse-web3/sdk react-icons ethers moment
Adding TailwindCSS
Tailwind CSS is a utility-first CSS framework that enables you to rapidly build
user interfaces. We will use it to style our app. First, we need to install the
tailwindcss
, postcss
, and autoprefixer
dependencies. These dependencies are
necessary for TailwindCSS to work properly in a Next.js app.
Run the following command to install them:
npm install --dev tailwindcss postcss autoprefixer
Once the dependencies are installed, we need to initiate the Tailwind CSS. This
will create the necessary configuration files and allow you to customize the
default Tailwind CSS styles. To do that, run the below code in your terminal.
The above command will generate two files named tailwind.config.js
and
postcss.config.js
. These files contain the configuration for Tailwind CSS and
PostCSS, respectively. Next, open the tailwind.config.js
file in code editor
of your choice and replace its contents with the following code:
module.exports = {
content: [
"./pages//*.{js,ts,jsx,tsx}",
"./components//*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
The above code tells Tailwind CSS which files to process. At last, add the
tailwind directives for each of Tailwind’s layers to the ./styles/globals.css
file.
@tailwind base;
@tailwind components;
@tailwind utilities;
The smart contract
Now that the project setup is completed, we can start writing smart contracts
for our application. In this article, I will be using Solidity.
A smart contract is a decentralized program that responds to events by
executing business logic.
To quickly create and deploy a contract, you can use Remix - a browser-based IDE
developed by the Ethereum Foundation. Here’s how you can get started:
- Open your web browser and go to
remix.ethereum.org.
- Create a new workspace by selecting Blank.
- Copy and paste the below contract code into the editor.
pragma solidity ^0.8.0;
contract YouTube {
uint256 public videoCount = 0;
string public name = "YouTube";
mapping(uint256 => Video) public videos;
struct Video {
uint256 id;
string hash;
string title;
string description;
string location;
string category;
string thumbnailHash;
string date;
address author;
}
event VideoUploaded(
uint256 id,
string hash,
string title,
string description,
string location,
string category,
string thumbnailHash,
string date,
address author
);
constructor() {}
function uploadVideo(
string memory _videoHash,
string memory _title,
string memory _description,
string memory _location,
string memory _category,
string memory _thumbnailHash,
string memory _date
) public {
require(bytes(_videoHash).length > 0);
require(bytes(_title).length > 0);
require(msg.sender != address(0));
videoCount++;
videos[videoCount] = Video(
videoCount,
_videoHash,
_title,
_description,
_location,
_category,
_thumbnailHash,
_date,
msg.sender
);
emit VideoUploaded(
videoCount,
_videoHash,
_title,
_description,
_location,
_category,
_thumbnailHash,
_date,
msg.sender
);
}
}
- Switch to the Deploy tab.
- Add Hyperspace testnet
to your Metamask account and choose your network from the Environment tab.
- If everything goes well, you should see a success message at the bottom of the
IDE window along with your contract address.
- To download the artifacts folder from Remix, click on the backup folder and
look for the folder inside the
.workspace
directory.
The Frontend
Now that we have completed smart contracts, it is time to work on the front end
of the application. Let’s start with the Authentication of the app.
Authentication
The first step is to set up authentication in our app that allows users to
connect their wallets. Create a new folder named landing
inside of the pages
folder and create a new file inside of it named index.js. This file will have
the code for the landing page in our application, which will also allow users to
connect their wallets.
Erase everything inside of index.js
in the page directory and inside import
the Landing
file to the file. Here is what your index.js file should look
like.
import React from "react";
import Landing from "./landing";
export default function index() {
return <Landing />;
}
Now, on the landing page, we will create a simple hero component with connect
wallet button that will allow users to connect their wallets and access our
application.
Add the below code to the landing page. I have already added comments so you can
understand them properly.
import React, { useState } from "react";
function Landing() {
const connectWallet = async () => {
try {
const { ethereum } = window;
if (!ethereum) {
alert("Please install MetaMask");
return;
}
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
localStorage.setItem("walletAddress", accounts[0]);
} catch (error) {
console.log(error);
}
};
return (
<>
{}
<section className="relative bg-black flex flex-col h-screen justify-center items-center">
<div className="max-w-7xl mx-auto px-4 sm:px-6">
<div className="pt-32 pb-12 md:pt-40 md:pb-20">
<div className="text-center pb-12 md:pb-16">
<h1
className="text-5xl text-white md:text-6xl font-extrabold leading-tighter tracking-tighter mb-4"
data-aos="zoom-y-out"
>
It is YouTube, but{" "}
<span className="bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-teal-400">
Decentralized
</span>
</h1>
<div className="max-w-3xl mx-auto">
<p
className="text-xl text-gray-400 mb-8"
data-aos="zoom-y-out"
data-aos-delay="150"
>
A YouTube Clone built on top of FVM, allow users to create,
share and watch videos, without worrying about their privacy.
</p>
<button
className="items-center bg-white rounded-full font-medium p-4 shadow-lg"
onClick={() => {
connectWallet();
}}
>
<span>Connect wallet</span>
</button>
</div>
</div>
</div>
</div>
</section>
</>
);
}
export default Landing;
If everything goes fine you should see a similar screen. You should also be able
to connect your MetaMask wallet.
Uploading videos
Now that users are able to connect their wallets, it is time to add upload video
functionality to our app.
Create a new folder in the pages directory named upload
and add a file named
index.js
. Inside the file add the below code.
import React, { useState, useRef } from "react";
import { BiCloud, BiMusic, BiPlus } from "react-icons/bi";
export default function Upload() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState("");
const [location, setLocation] = useState("");
const [thumbnail, setThumbnail] = useState("");
const [video, setVideo] = useState("");
const thumbnailRef = useRef();
const videoRef = useRef();
return (
<div className="w-full h-screen bg-[#1a1c1f] flex flex-row">
<div className="flex-1 flex flex-col">
<div className="mt-5 mr-10 flex justify-end">
<div className="flex items-center">
<button className="bg-transparent text-[#9CA3AF] py-2 px-6 border rounded-lg border-gray-600 mr-6">
Discard
</button>
<button
onClick={() => {
handleSubmit();
}}
className="bg-blue-500 hover:bg-blue-700 text-white py-2 rounded-lg flex px-4 justify-between flex-row items-center"
>
<BiCloud />
<p className="ml-2">Upload</p>
</button>
</div>
</div>
<div className="flex flex-col m-10 mt-5 lg:flex-row">
<div className="flex lg:w-3/4 flex-col ">
<label className="text-[#9CA3AF] text-sm">Title</label>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Rick Astley - Never Gonna Give You Up (Official Music Video)"
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
<label className="text-[#9CA3AF] mt-10">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Never Gonna Give You Up was a global smash on its release in July 1987, topping the charts in 25 countries including Rick’s native UK and the US Billboard Hot 100. It also won the Brit Award for Best single in 1988. Stock Aitken and Waterman wrote and produced the track which was the lead-off single and lead track from Rick’s debut LP “Whenever You Need Somebody."
className="w-[90%] text-white h-32 placeholder:text-gray-600 rounded-md mt-2 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
<div className="flex flex-row mt-10 w-[90%] justify-between">
<div className="flex flex-col w-2/5 ">
<label className="text-[#9CA3AF] text-sm">Location</label>
<input
value={location}
onChange={(e) => setLocation(e.target.value)}
type="text"
placeholder="Bali - Indonesia"
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
</div>
<div className="flex flex-col w-2/5 ">
<label className="text-[#9CA3AF] text-sm">Category</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
>
<option>Music</option>
<option>Sports</option>
<option>Gaming</option>
<option>News</option>
<option>Entertainment</option>
<option>Education</option>
<option>Science & Technology</option>
<option>Travel</option>
<option>Other</option>
</select>
</div>
</div>
<label className="text-[#9CA3AF] mt-10 text-sm">Thumbnail</label>
<div
onClick={() => {
thumbnailRef.current.click();
}}
className="border-2 w-64 border-gray-600 border-dashed rounded-md mt-2 p-2 h-36 items-center justify-center flex"
>
{thumbnail ? (
<img
onClick={() => {
thumbnailRef.current.click();
}}
src={URL.createObjectURL(thumbnail)}
alt="thumbnail"
className="h-full rounded-md"
/>
) : (
<BiPlus size={40} color="gray" />
)}
</div>
<input
type="file"
className="hidden"
ref={thumbnailRef}
onChange={(e) => {
setThumbnail(e.target.files[0]);
}}
/>
</div>
<div
onClick={() => {
videoRef.current.click();
}}
className={
video
? " w-96 rounded-md h-64 items-center justify-center flex"
: "border-2 border-gray-600 w-96 border-dashed rounded-md mt-8 h-64 items-center justify-center flex"
}
>
{video ? (
<video
controls
src={URL.createObjectURL(video)}
className="h-full rounded-md"
/>
) : (
<p className="text-[#9CA3AF]">Upload Video</p>
)}
</div>
</div>
<input
type="file"
className="hidden"
ref={videoRef}
accept={"video/*"}
onChange={(e) => {
setVideo(e.target.files[0]);
console.log(e.target.files[0]);
}}
/>
</div>
</div>
);
}
And you should see a similar screen if you navigate to
http://localhost:3000/upload.
This is a basic upload page, for now, we just have the inputs and save their value
of them in the state.
Before working on the handle submit function, create a new folder named utils
,
and inside of it create a file named getContract
. This file will be used to
interact with your contract on the upload page. Add the below code to it and
make sure to replace the contract address and artifacts.
import ContractAbi from "../artifacts/contracts/YouTube.sol/YouTube.json";
import { ethers } from "ethers";
export default function getContract() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
let contract = new ethers.Contract(
"YOUR_CONTRACT_ADDRESS",
ContractAbi.abi,
signer
);
return contract;
}
Integrating IPFS (Web3 Storage)
Now we need an IPFS client to upload thumbnails. There are many services that
offer IPFS service, but for this tutorial, we will use
lighthouse.storage.
Create a new account in lighthouse.storage and then navigate to the API Key
page. Create a new token and copy the generated token as we will need it later.
Next, we need to integrate Livepeer in order to upload the videos and serve them
through Livepeer CDN.
Integrating Livepeer
Livepeer is a decentralized video processing network and developer platform
which you can use to build video applications. It is very fast, easy to
integrate, and cheap. In this tutorial, we will be using Livepeer to upload
videos and serve it.
Navigate to https://livepeer.studio/register
and create a new account on Livepeer Studio.
Once you have created an account, in the dashboard, click on the Developers on
the sidebar.
Then, click on Create API Key, give a name to your key and then copy it as we
will need it later.
Now back to the code, create a new file inside of the root directory named
livepeer.js
and add the below code to initialize a React client.
import { createReactClient, studioProvider } from "@livepeer/react";
const LivepeerClient = createReactClient({
provider: studioProvider({ apiKey: "YOUR_API_KEY" }),
});
export default LivepeerClient;
Make sure to replace the YOUR_API_KEY
with the key which you just copied from
the Livepeer dashboard. And also replace the code inside of _app.js
in the
page directory with the below code.
import "../styles/globals.css";
import { LivepeerConfig } from "@livepeer/react";
import LivepeerClient from "../livepeer";
function MyApp({ Component, pageProps }) {
return (
<LivepeerConfig client={LivepeerClient}>
<Component {...pageProps} />
</LivepeerConfig>
);
}
export default MyApp;
And that is it, you can now use Livepeer to upload assets/videos.
Back to the upload page, add the following functions to the upload.js
.
const goBack = () => {
window.history.back();
};
const uploadToLighthouse = async (e, type) => {
setIsUploading(true);
const output = await lighthouse.upload(
e,
process.env.NEXT_PUBLIC_LIGHTHOUSE_KEY
);
let cid = output.data.Hash;
if (type == "thumbnail") {
setThumbnail(cid);
} else {
setVideo(cid);
}
setIsUploading(false);
};
const handleSubmit = async () => {
let data = {
video,
title,
description,
location,
category,
thumbnail,
UploadedDate: Date.now(),
};
await saveVideo(data);
};
const saveVideo = async (data) => {
let contract = await getContract();
await contract.uploadVideo(
data.video,
data.title,
data.description,
data.location,
data.category,
data.thumbnail,
false,
data.UploadedDate
);
};
Finally, this is how your code should look like:
import React, { useState, useEffect, useRef } from "react";
import { Sidebar, Header } from "../../layout";
import { BiCloud, BiPlus } from "react-icons/bi";
import { getContract } from "../../utils";
import lighthouse from "@lighthouse-web3/sdk";
export default function Upload() {
const [title, setTitle] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [category, setCategory] = useState<string>("");
const [location, setLocation] = useState<string>("");
const [thumbnail, setThumbnail] = useState<string>();
const [video, setVideo] = useState<string>("");
const [isUploading, setIsUploading] = useState<boolean>();
const thumbnailRef = useRef<HTMLInputElement>(null);
const goBack = () => {
window.history.back();
};
const uploadToLighthouse = async (e, type) => {
setIsUploading(true);
const output = await lighthouse.upload(
e,
process.env.NEXT_PUBLIC_LIGHTHOUSE_KEY
);
let cid = output.data.Hash;
if (type == "thumbnail") {
setThumbnail(cid);
} else {
setVideo(cid);
}
setIsUploading(false);
};
const handleSubmit = async () => {
let data = {
video,
title,
description,
location,
category,
thumbnail,
UploadedDate: Date.now(),
};
await saveVideo(data);
};
const saveVideo = async (data) => {
let contract = await getContract();
await contract.uploadVideo(
data.video,
data.title,
data.description,
data.location,
data.category,
data.thumbnail,
false,
data.UploadedDate
);
};
return (
<div className="w-full h-screen bg-[#1a1c1f] flex flex-row">
<div className="flex-1 flex flex-col">
<div className="mt-5 mr-10 flex justify-end">
<div className="flex items-center">
<button className="bg-transparent text-[#9CA3AF] py-2 px-6 border rounded-lg border-gray-600 mr-6">
Discard
</button>
<button
onClick={() => {
handleSubmit();
}}
className="bg-blue-500 hover:bg-blue-700 text-white py-2 rounded-lg flex px-4 justify-between flex-row items-center"
>
<BiCloud />
<p className="ml-2">Upload</p>
</button>
</div>
</div>
<div className="flex flex-col m-10 mt-5 lg:flex-row">
<div className="flex lg:w-3/4 flex-col ">
<label className="text-[#9CA3AF] text-sm">Title</label>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Rick Astley - Never Gonna Give You Up (Official Music Video)"
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
<label className="text-[#9CA3AF] mt-10">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Never Gonna Give You Up was a global smash on its release in July 1987, topping the charts in 25 countries including Rick’s native UK and the US Billboard Hot 100. It also won the Brit Award for Best single in 1988. Stock Aitken and Waterman wrote and produced the track which was the lead-off single and lead track from Rick’s debut LP “Whenever You Need Somebody."
className="w-[90%] text-white h-32 placeholder:text-gray-600 rounded-md mt-2 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
<div className="flex flex-row mt-10 w-[90%] justify-between">
<div className="flex flex-col w-2/5 ">
<label className="text-[#9CA3AF] text-sm">Location</label>
<input
value={location}
onChange={(e) => setLocation(e.target.value)}
type="text"
placeholder="Bali - Indonesia"
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
/>
</div>
<div className="flex flex-col w-2/5 ">
<label className="text-[#9CA3AF] text-sm">Category</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-[90%] text-white placeholder:text-gray-600 rounded-md mt-2 h-12 p-2 border bg-[#1a1c1f] border-[#444752] focus:outline-none"
>
<option>Music</option>
<option>Sports</option>
<option>Gaming</option>
<option>News</option>
<option>Entertainment</option>
<option>Education</option>
<option>Science & Technology</option>
<option>Travel</option>
<option>Other</option>
</select>
</div>
</div>
<label className="text-[#9CA3AF] mt-10 text-sm">Thumbnail</label>
<div
onClick={() => {
thumbnailRef.current.click();
}}
className="border-2 w-64 border-gray-600 border-dashed rounded-md mt-2 p-2 h-36 items-center justify-center flex"
>
{thumbnail ? (
<img
onClick={() => {
thumbnailRef.current.click();
}}
src={URL.createObjectURL(thumbnail)}
alt="thumbnail"
className="h-full rounded-md"
/>
) : (
<BiPlus size={40} color="gray" />
)}
</div>
<input
type="file"
className="hidden"
ref={thumbnailRef}
onChange={(e) => {
uploadToLighthouse(e);
}}
/>
</div>
<div
onClick={() => {
videoRef.current.click();
}}
className={
video
? " w-96 rounded-md h-64 items-center justify-center flex"
: "border-2 border-gray-600 w-96 border-dashed rounded-md mt-8 h-64 items-center justify-center flex"
}
>
{video ? (
<video
controls
src={URL.createObjectURL(video)}
className="h-full rounded-md"
/>
) : (
<p className="text-[#9CA3AF]">Upload Video</p>
)}
</div>
</div>
<input
type="file"
className="hidden"
ref={videoRef}
accept={"video/*"}
onChange={(e) => {
uploadToLighthouse(e);
}}
/>
</div>
</div>
);
}
Save the file and we are done with the upload functionality. You should now be
able to upload videos to the contract.
Fetching videos from Blockchain
Create a new file named index.js
inside of a new folder named home
. And for
now you can add the below code to the file.
import React, { useEffect, useState } from "react";
import { useApolloClient, gql } from "@apollo/client";
export default function Main() {
const [videos, setVideos] = useState([]);
const getVideos = async () => {
let contract = await getContract();
let videosCount = await contract.videoCount();
console.log(String(videosCount));
let videos = [];
for (var i = videosCount; i >= 1; i--) {
let video = await contract.videos(i);
videos.push(video);
}
setVideos(videos);
};
useEffect(() => {
getVideos();
}, []);
return (
<div className="w-full bg-[#1a1c1f] flex flex-row">
<div className="flex-1 h-screen flex flex-col">
<div className="flex flex-row flex-wrap">
{videos.map((video) => (
<div className="w-80">
<p>{video.title}</p>
</div>
))}
</div>
</div>
</div>
);
}
Save the file and you should see a similar output.
As you can see for now we are just fetching the video title. So let’s create a
reusable component to display the videos nicely.
Make sure to upload a few videos so you can see the above output
Create a folder named components
, and then create a new file named Video.js
inside of it. Add the below code to the file. It is a very basic video component.
import React from "react";
import { BiCheck } from "react-icons/bi";
import moment from "moment";
export default function Video({ horizontal, video }) {
return (
<div
className={`${
horizontal
? "flex flex-row mx-5 mb-5 item-center justify-center"
: "flex flex-col m-5"
} `}
>
<img
className={
horizontal
? "object-cover rounded-lg w-60 "
: "object-cover rounded-lg w-full h-40"
}
src={`https://ipfs.io/ipfs/${video.thumbnailHash}`}
alt=""
/>
<div className={horizontal && "ml-3 w-80"}>
<h4 className="text-md font-bold dark:text-white mt-3">
{video.title}
</h4>
<p className="text-sm flex items-center text-[#878787] mt-1">
{video.category + " • " + moment(video.createdAt * 1000).fromNow()}
</p>
<p className="text-sm flex items-center text-[#878787] mt-1">
{video?.author?.slice(0, 9)}...{" "}
<BiCheck size="20px" color="green" className="ml-1" />
</p>
</div>
</div>
);
}
Import the Video component to the home file and replace the map function with
the below code.
{
videos.map((video) => (
<div
className="w-80"
onClick={() => {
window.location.href = `/video?id=${video.id}`;
}}
>
<Video video={video} />
</div>
));
}
Save the file and now you should see a nice-looking homepage, similar to below
image.
Video page
Now that we are able to fetch the videos on the home screen. Let’s work on the
video page where the user will be redirected if they click on any video
component.
Create a new file in the components folder named Player
and add the below code
to it. We are using Livepeer player to create a video player component.
import React from "react";
import { useAsset, Player } from "@livepeer/react";
interface PlayerProps {
id: any;
}
const VideoPlayer: React.FC<PlayerProps> = ({ id }) => {
return (
<Player.Root src={"ipfs://" + id}>
<Player.Container>
<Player.Video />
</Player.Container>
</Player.Root>
);
};
export default VideoPlayer;
Create another file in the same directory named VideoContainer
. Imagine this
component as the left side of the youtube video page, which contains a player,
video title, upload date, and description. Add the below code to the file.
import React from "react";
import Player from "./Player";
export default function VideoComponent({ video }) {
return (
<div>
<Player hash={video.hash} />
<div className="flex justify-between flex-row py-4">
<div>
<h3 className="text-2xl dark:text-white">{video.title}</h3>
<p className="text-gray-500 mt-1">
{video.category} •{" "}
{new Date(video.createdAt * 1000).toLocaleString("en-IN")}
</p>
</div>
</div>
</div>
);
}
At last, create a new folder named video inside of the pages
directory and
create a new file index.js
of it. For now, you can add the following code to
the file.
import { useRouter } from "next/router";
import { Header, Sidebar } from "../../layout";
import React, { useEffect, useState } from "react";
import { Background, Player, Video as RelatedVideos } from "../../components";
import lighthouse from "@lighthouse-web3/sdk";
import Link from "next/link";
import moment from "moment";
import { BiCheck } from "react-icons/bi";
import Avvvatars from "avvvatars-react";
import { IVideo } from "../../types";
import { getContract } from "../../utils";
export default function Video() {
const router = useRouter();
const { id } = router.query;
const [video, setVideo] = useState<IVideo | null>(null);
const [relatedVideos, setRelatedVideos] = useState<IVideo[]>([]);
const fetchVideos = async () => {
if (id) {
let contract = await getContract();
let video = await contract.videos(id);
let videosCount = await contract.videoCount();
let videos = [];
for (var i = videosCount; i >= 1; i--) {
let video = await contract.videos(i);
videos.push(video);
}
setRelatedVideos(videos);
setVideo(video);
}
};
useEffect(() => {
fetchVideos();
}, [id]);
return (
<Background className="flex h-screen w-full flex-row">
<Sidebar />
<div className="flex flex-1 flex-col">
<Header />
{video && (
<div className="m-10 flex flex-col justify-between lg:flex-row">
<div className="w-6/6 lg:w-4/6">
<Player id={video.hash} />
<div className="border-border-light dark:border-border-dark flex flex-row justify-between border-b-2 py-4">
<div>
<h3 className="text-transform: text-2xl capitalize dark:text-white">
{video.title}
</h3>
<p className="mt-1 text-gray-500 ">{video.category} </p>
</div>
</div>
<div>
<div className="mt-5 flex flex-row items-center ">
<div className="w-12">
<Avvvatars value={video.author.slice(2, 13)} size={50} />
</div>
<div className="ml-3 flex flex-col">
<p className="text-md mt-1 flex items-center text-black dark:text-white">
{video.author.slice(0, 13)}...{" "}
<BiCheck size="20px" className="fill-gray ml-1" />
</p>
<p className="text-subtitle-light flex items-center text-sm ">
Video by {video.author}
</p>
</div>
</div>
<p className="text-text-light dark:text-text-dark text-textSubTitle mt-4 ml-16 text-sm">
{video.description}
</p>
</div>
</div>
<div className="w-2/6">
<h4 className="text-md ml-5 mb-3 font-bold text-black dark:text-white">
Related Videos
</h4>
{relatedVideos.map((video) => (
<Link href={`/video/${video.id}`} key={video.id}>
<RelatedVideos video={video} horizontal={true} />
</Link>
))}
</div>
</div>
)}
</div>
</Background>
);
}
Save the file and click on any videos on the home screen. You should be
redirected to the video screen similar to the below page.
That is it for this tutorial, visit
FVM
docs to learn more about its capabilities