Integrate videos directly from Bunny.net CDN for faster streaming
Implements Bunny.net CDN integration for video streaming and utilizes its API to fetch and display videos. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/DXDnT5V
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
import { type Video, type InsertVideo } from "@shared/schema";
|
||||
|
||||
interface BunnyVideo {
|
||||
guid: string;
|
||||
title: string;
|
||||
length: number;
|
||||
status: number;
|
||||
dateUploaded: string;
|
||||
views: number;
|
||||
thumbnailFileName?: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
interface BunnyLibraryResponse {
|
||||
items: BunnyVideo[];
|
||||
currentPage: number;
|
||||
itemsPerPage: number;
|
||||
totalItems: number;
|
||||
}
|
||||
|
||||
export class BunnyService {
|
||||
private apiKey: string;
|
||||
private libraryId: string;
|
||||
private hostname: string;
|
||||
|
||||
constructor() {
|
||||
this.apiKey = process.env.BUNNY_API_KEY!;
|
||||
this.libraryId = process.env.BUNNY_LIBRARY_ID!;
|
||||
this.hostname = process.env.BUNNY_HOSTNAME!;
|
||||
|
||||
if (!this.apiKey || !this.libraryId || !this.hostname) {
|
||||
throw new Error("Missing Bunny.net configuration");
|
||||
}
|
||||
}
|
||||
|
||||
private async makeRequest(endpoint: string): Promise<any> {
|
||||
const url = `https://video.bunnycdn.com/library/${this.libraryId}/${endpoint}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'AccessKey': this.apiKey,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Bunny API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
private bunnyVideoToVideo(bunnyVideo: BunnyVideo): Video {
|
||||
// Generate thumbnail URL from Bunny CDN
|
||||
const thumbnailUrl = bunnyVideo.thumbnailFileName
|
||||
? `https://${this.hostname}/${bunnyVideo.guid}/${bunnyVideo.thumbnailFileName}`
|
||||
: `https://${this.hostname}/${bunnyVideo.guid}/thumbnail.jpg`;
|
||||
|
||||
// Generate video URL for streaming
|
||||
const videoUrl = `https://${this.hostname}/${bunnyVideo.guid}/playlist.m3u8`;
|
||||
|
||||
return {
|
||||
id: bunnyVideo.guid,
|
||||
title: bunnyVideo.title || 'Untitled Video',
|
||||
description: null, // Bunny API doesn't return description in list view
|
||||
thumbnailUrl,
|
||||
videoUrl,
|
||||
duration: Math.floor(bunnyVideo.length || 0),
|
||||
views: bunnyVideo.views || 0,
|
||||
category: bunnyVideo.category || null,
|
||||
createdAt: new Date(bunnyVideo.dateUploaded)
|
||||
};
|
||||
}
|
||||
|
||||
async getVideos(page: number = 1, itemsPerPage: number = 20, search?: string): Promise<{ videos: Video[], total: number }> {
|
||||
try {
|
||||
let endpoint = `videos?page=${page}&itemsPerPage=${itemsPerPage}&orderBy=date`;
|
||||
|
||||
if (search) {
|
||||
endpoint += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
|
||||
const response: BunnyLibraryResponse = await this.makeRequest(endpoint);
|
||||
|
||||
// Filter only successfully processed videos (status 4 = finished)
|
||||
const processedVideos = response.items.filter(video => video.status === 4);
|
||||
|
||||
const videos = processedVideos.map(video => this.bunnyVideoToVideo(video));
|
||||
|
||||
return {
|
||||
videos,
|
||||
total: response.totalItems
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching videos from Bunny:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getVideo(guid: string): Promise<Video | null> {
|
||||
try {
|
||||
const bunnyVideo: BunnyVideo = await this.makeRequest(`videos/${guid}`);
|
||||
return this.bunnyVideoToVideo(bunnyVideo);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching video ${guid} from Bunny:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getCategories(): Promise<string[]> {
|
||||
try {
|
||||
// Get all videos and extract unique categories
|
||||
const { videos } = await this.getVideos(1, 1000);
|
||||
const categories = Array.from(new Set(videos.map(v => v.category).filter(Boolean) as string[]));
|
||||
return categories;
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories from Bunny:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user