Skip to content

Commit

Permalink
Add livepeer video integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Prajjawalk committed Apr 3, 2024
1 parent 396d077 commit 6b0b4ce
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 99 deletions.
120 changes: 120 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ const config: HardhatUserConfig = {
// configuration for harhdat-verify plugin
etherscan: {
apiKey: `${etherscanApiKey}`,
customChains: [
{
network: "baseSepolia",
chainId: 84532,
urls: {
apiURL: "https://api-sepolia.basescan.org/api",
browserURL: "https://sepolia.basescan.org/",
},
},
],
},
// configuration for etherscan-verify from hardhat-deploy plugin
verify: {
Expand Down
37 changes: 11 additions & 26 deletions packages/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ export default async function Home({ searchParams }: NextServerPageProps) {
}

let frame: React.ReactElement;
let totalviewers;
let totalNFTMints;
let totalLiveViewers;

const intialFrame = (
<FrameContainer postUrl="/frames" pathname="/" state={initialState} previousFrame={previousFrame}>
<FrameImage>
<div tw="w-full bg-slate-700 text-white justify-center items-center">NFTfy your favourite livestream with</div>
<div tw="w-full bg-slate-700 text-white justify-center items-center">
NFTfy your favourite livepeer video with
</div>
<div tw="w-full h-full bg-slate-700 text-white justify-center items-center">LiveStreamNFT</div>
<div tw="w-full bg-slate-700 text-white justify-center items-center">
Enter playback ID and click `Generate` to generate gif.
</div>
<div tw="w-full bg-slate-700 text-white justify-center items-center">
You can try out with `0b79ukgd9vf7t0ae`
</div>
</FrameImage>
<FrameButton>Generate</FrameButton>
<FrameInput text="Enter playback ID" />
Expand All @@ -59,22 +61,6 @@ export default async function Home({ searchParams }: NextServerPageProps) {
</FrameContainer>
);

const analyticsFrame = (
<FrameContainer postUrl="/frames" pathname="/" state={initialState} previousFrame={previousFrame}>
<FrameImage>
<div tw="w-full h-full bg-slate-700 text-white justify-center items-center">Viewers analytics</div>
<div tw="w-full h-full bg-slate-700 text-white justify-center items-center">Total Viewers: {totalviewers}</div>
<div tw="w-full h-full bg-slate-700 text-white justify-center items-center">
Total NFT mints: {totalNFTMints}
</div>
<div tw="w-full h-full bg-slate-700 text-white justify-center items-center">
Total Live viewers: {totalLiveViewers}
</div>
</FrameImage>
<FrameButton target={"/frames?reset=true"}>Home</FrameButton>
</FrameContainer>
);

const errorFrame = (error: string) => (
<FrameContainer postUrl="/frames" pathname="/" state={initialState} previousFrame={previousFrame}>
<FrameImage>{error}</FrameImage>
Expand Down Expand Up @@ -114,19 +100,18 @@ export default async function Home({ searchParams }: NextServerPageProps) {
<FrameButton target={"/txdata"} action="tx">
Mint NFT
</FrameButton>
{frameMessage.transactionId ? (
<FrameButton action="link" target={`${process.env.BLOCKEXPLORER_URL}/${frameMessage.transactionId}`}>
Transaction
</FrameButton>
) : null}
<FrameButton action="link" target={existingRequest.data}>
Download
</FrameButton>
</FrameContainer>
);
}
break;
case "analytics":
totalLiveViewers = 1;
totalNFTMints = 3;
totalviewers = 5;
frame = analyticsFrame;
break;
case "error":
// if retry is true, then try to generate again and show checkStatusFrame
if (searchParams?.retry === "true") {
Expand Down
108 changes: 54 additions & 54 deletions packages/nextjs/app/slow-fetch/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,30 @@ const fdk = new PinataFDK({
pinata_gateway: String(process.env.PINATA_GATEWAY),
});

async function pollUrlUntilResponse(url: string, expectedResponse: string, interval: number, maxAttempts: number) {
let attempts = 0;
// const axios = require("axios");
while (attempts < maxAttempts) {
try {
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${process.env.LIVEPEER_API_KEY}`,
},
});
if (response.data.status.phase == expectedResponse) {
console.log("got the download url...");
return response.data.downloadUrl;
}
} catch (error) {
// Handle errors if necessary
console.error("Error:", error);
throw new Error("Error polling url");
}
await new Promise(resolve => setTimeout(resolve, interval));
attempts++;
}
throw new Error(`Max attempts (${maxAttempts}) reached without receiving the expected response.`);
}
// async function pollUrlUntilResponse(url: string, expectedResponse: string, interval: number, maxAttempts: number) {
// let attempts = 0;
// // const axios = require("axios");
// while (attempts < maxAttempts) {
// try {
// const response = await axios.get(url, {
// headers: {
// Authorization: `Bearer ${process.env.LIVEPEER_API_KEY}`,
// },
// });
// if (response.data.status.phase == expectedResponse) {
// console.log("got the download url...");
// return response.data.downloadUrl;
// }
// } catch (error) {
// // Handle errors if necessary
// console.error("Error:", error);
// throw new Error("Error polling url");
// }
// await new Promise(resolve => setTimeout(resolve, interval));
// attempts++;
// }
// throw new Error(`Max attempts (${maxAttempts}) reached without receiving the expected response.`);
// }

export async function POST(req: NextRequest) {
const body = await req.json();
Expand All @@ -62,49 +62,49 @@ export async function POST(req: NextRequest) {
console.log(analyticsRes);
const playbackId = frameMessage.inputText;
if (!playbackId) {
return NextResponse.json({ message: "Video URL is required" }, { status: 400 });
return NextResponse.json({ message: "Playback ID is required" }, { status: 400 });
}

const livepeer = new Livepeer({
apiKey: process.env.LIVEPEER_API_KEY,
});

//clipping random livestream
console.log("creating clip");
const result = await livepeer.stream.createClip({
/**
* Playback ID of the stream or asset to clip
*/
playbackId: playbackId,
/**
* Start time of the clip in milliseconds
*/
startTime: Date.now() - 7000,
/**
* End time of the clip in milliseconds
*/
endTime: Date.now() - 5000,
});
console.log(result.object?.asset);

const url = `https://livepeer.studio/api/asset/${result.object?.asset.id}`;
const expectedResponse = "ready";
const pollingInterval = 5000; // 5 seconds (in milliseconds)
const maxAttempts = 100;

const downloadUrl = await pollUrlUntilResponse(url, expectedResponse, pollingInterval, maxAttempts);
// console.log("creating clip");
// const result = await livepeer.stream.createClip({
// /**
// * Playback ID of the stream or asset to clip
// */
// playbackId: playbackId,
// /**
// * Start time of the clip in milliseconds
// */
// startTime: Date.now() - 7000,
// /**
// * End time of the clip in milliseconds
// */
// endTime: Date.now() - 5000,
// });
// console.log(result.object?.asset);

// const url = `https://livepeer.studio/api/asset/${result.object?.asset.id}`;
// const expectedResponse = "ready";
// const pollingInterval = 5000; // 5 seconds (in milliseconds)
// const maxAttempts = 100;

// const downloadUrl = await pollUrlUntilResponse(url, expectedResponse, pollingInterval, maxAttempts);

// fetch the playback info on the server
// const playbackInfo = await livepeer.playback.get(playbackId);
// const videoUrl = playbackInfo.playbackInfo?.meta.source[0]?.url;
const videoUrl = downloadUrl;
const playbackInfo = await livepeer.playback.get(playbackId);
const videoUrl = playbackInfo.playbackInfo?.meta.source[0]?.url;
// const videoUrl = downloadUrl;

// console.log("playback info ", playbackInfo.playbackInfo?.meta.source[0]);

// Download MP4 video file
const mp4FilePath = "video.mp4";
const mp4FilePath = `video-${playbackId}.mp4`;
const mp4FileStream = fs.createWriteStream(mp4FilePath);
const response = await axios.get(videoUrl, { responseType: "stream" });
const response = await axios.get(String(videoUrl), { responseType: "stream" });

response.data.pipe(mp4FileStream);

Expand All @@ -117,7 +117,7 @@ export async function POST(req: NextRequest) {
});

// Convert MP4 to GIF using FFmpeg
const gifFilePath = "public/output.gif";
const gifFilePath = `public/output-${playbackId}.gif`;
await new Promise<void>((resolve, reject) => {
exec(
`ffmpeg -i ${mp4FilePath} -vf "fps=10,scale=320:-1:flags=lanczos" -c:v gif -loop 0 ${gifFilePath}`,
Expand Down
4 changes: 0 additions & 4 deletions packages/nextjs/app/slow-fetch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ export type RandomNumberRequestStateValue =
status: "success";
timestamp: number;
}
| {
status: "analytics";
timestamp: number;
}
| {
status: "pending";
timestamp: number;
Expand Down
Loading

0 comments on commit 6b0b4ce

Please sign in to comment.